firebase log level
This commit is contained in:
76
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/Heartbeat.swift
generated
Normal file
76
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/Heartbeat.swift
generated
Normal file
@ -0,0 +1,76 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An enumeration of time periods.
|
||||
enum TimePeriod: Int, CaseIterable, Codable {
|
||||
/// The raw value is the number of calendar days within each time period.
|
||||
/// More types can be enabled in future iterations (i.e. `weekly = 7, monthly = 28`).
|
||||
case daily = 1
|
||||
|
||||
/// The number of seconds in a given time period.
|
||||
var timeInterval: TimeInterval {
|
||||
Double(rawValue) * 86400 /* seconds in day */
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing SDK usage.
|
||||
struct Heartbeat: Codable, Equatable {
|
||||
/// The version of the heartbeat.
|
||||
private static let version: Int = 0
|
||||
|
||||
/// An anonymous string of information (i.e. user agent) to associate the heartbeat with.
|
||||
let agent: String
|
||||
|
||||
/// The date when the heartbeat was recorded.
|
||||
let date: Date
|
||||
|
||||
/// The heartbeat's model version.
|
||||
let version: Int
|
||||
|
||||
/// An array of `TimePeriod`s that the heartbeat is tagged with. See `TimePeriod`.
|
||||
///
|
||||
/// Heartbeats represent anonymous data points that measure SDK usage in moving averages for
|
||||
/// various time periods. Because a single heartbeat can help calculate moving averages for
|
||||
/// multiple
|
||||
/// time periods, this property serves to capture all the time periods that the heartbeat can
|
||||
/// represent in
|
||||
/// a moving average.
|
||||
let timePeriods: [TimePeriod]
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameters:
|
||||
/// - agent: An anonymous string of information to associate the heartbeat with.
|
||||
/// - date: The date when the heartbeat was recorded.
|
||||
/// - version: The heartbeat's version. Defaults to the current version.
|
||||
init(agent: String,
|
||||
date: Date,
|
||||
timePeriods: [TimePeriod] = [],
|
||||
version: Int = version) {
|
||||
self.agent = agent
|
||||
self.date = date
|
||||
self.timePeriods = timePeriods
|
||||
self.version = version
|
||||
}
|
||||
}
|
||||
|
||||
extension Heartbeat: HeartbeatsPayloadConvertible {
|
||||
func makeHeartbeatsPayload() -> HeartbeatsPayload {
|
||||
let userAgentPayloads = [
|
||||
HeartbeatsPayload.UserAgentPayload(agent: agent, dates: [date]),
|
||||
]
|
||||
return HeartbeatsPayload(userAgentPayloads: userAgentPayloads)
|
||||
}
|
||||
}
|
||||
157
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift
generated
Normal file
157
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift
generated
Normal file
@ -0,0 +1,157 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An object that provides API to log and flush heartbeats from a synchronized storage container.
|
||||
public final class HeartbeatController {
|
||||
/// Used for standardizing dates for calendar-day comparison.
|
||||
private enum DateStandardizer {
|
||||
private static let calendar: Calendar = {
|
||||
var calendar = Calendar(identifier: .iso8601)
|
||||
calendar.locale = Locale(identifier: "en_US_POSIX")
|
||||
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
|
||||
return calendar
|
||||
}()
|
||||
|
||||
static func standardize(_ date: Date) -> (Date) {
|
||||
return calendar.startOfDay(for: date)
|
||||
}
|
||||
}
|
||||
|
||||
/// The thread-safe storage object to log and flush heartbeats from.
|
||||
private let storage: HeartbeatStorageProtocol
|
||||
/// The max capacity of heartbeats to store in storage.
|
||||
private let heartbeatsStorageCapacity: Int = 30
|
||||
/// Current date provider. It is used for testability.
|
||||
private let dateProvider: () -> 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)
|
||||
}
|
||||
|
||||
/// Convenience initializer. Mirrors the semantics of the public initializer with the added
|
||||
/// benefit of
|
||||
/// injecting a custom date provider for improved testability.
|
||||
/// - Parameters:
|
||||
/// - id: The id to associate this controller's heartbeat storage with.
|
||||
/// - dateProvider: A date provider.
|
||||
convenience init(id: String, dateProvider: @escaping () -> Date) {
|
||||
let storage = HeartbeatStorage.getInstance(id: id)
|
||||
self.init(storage: storage, dateProvider: dateProvider)
|
||||
}
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameters:
|
||||
/// - storage: A heartbeat storage container.
|
||||
/// - dateProvider: A date provider. Defaults to providing the current date.
|
||||
init(storage: HeartbeatStorageProtocol,
|
||||
dateProvider: @escaping () -> Date = Date.init) {
|
||||
self.storage = storage
|
||||
self.dateProvider = { Self.dateStandardizer.standardize(dateProvider()) }
|
||||
}
|
||||
|
||||
/// Asynchronously logs a new heartbeat, if needed.
|
||||
///
|
||||
/// - Note: This API is thread-safe.
|
||||
/// - Parameter agent: The string agent (i.e. Firebase User Agent) to associate the logged
|
||||
/// heartbeat with.
|
||||
public func log(_ agent: String) {
|
||||
let date = dateProvider()
|
||||
|
||||
storage.readAndWriteAsync { heartbeatsBundle in
|
||||
var heartbeatsBundle = heartbeatsBundle ??
|
||||
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.
|
||||
let timePeriods = heartbeatsBundle.lastAddedHeartbeatDates.filter { timePeriod, lastDate in
|
||||
date.timeIntervalSince(lastDate) >= timePeriod.timeInterval
|
||||
}
|
||||
.map { timePeriod, _ in timePeriod }
|
||||
|
||||
if !timePeriods.isEmpty {
|
||||
// A heartbeat should only be logged if there is a time period(s) to
|
||||
// associate it with.
|
||||
let heartbeat = Heartbeat(agent: agent, date: date, timePeriods: timePeriods)
|
||||
heartbeatsBundle.append(heartbeat)
|
||||
}
|
||||
|
||||
return heartbeatsBundle
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronously flushes heartbeats from storage into a heartbeats payload.
|
||||
///
|
||||
/// - Note: This API is thread-safe.
|
||||
/// - Returns: The flushed heartbeats in the form of `HeartbeatsPayload`.
|
||||
@discardableResult
|
||||
public func flush() -> HeartbeatsPayload {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
// Synchronously gets and returns the stored heartbeats, resetting storage
|
||||
// using the given transform.
|
||||
let heartbeatsBundle = try storage.getAndSet(using: resetTransform)
|
||||
// If no heartbeats bundle was stored, return an empty payload.
|
||||
return heartbeatsBundle?.makeHeartbeatsPayload() ?? HeartbeatsPayload.emptyPayload
|
||||
} catch {
|
||||
// If the operation throws, assume no heartbeat(s) were retrieved or set.
|
||||
return HeartbeatsPayload.emptyPayload
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronously flushes the heartbeat for today.
|
||||
///
|
||||
/// If no heartbeat was logged today, the returned payload is empty.
|
||||
///
|
||||
/// - Note: This API is thread-safe.
|
||||
/// - Returns: A heartbeats payload for the flushed heartbeat.
|
||||
@discardableResult
|
||||
public func flushHeartbeatFromToday() -> HeartbeatsPayload {
|
||||
let todaysDate = dateProvider()
|
||||
var todaysHeartbeat: Heartbeat?
|
||||
|
||||
storage.readAndWriteSync { heartbeatsBundle in
|
||||
guard var heartbeatsBundle = heartbeatsBundle else {
|
||||
return nil // Storage was empty.
|
||||
}
|
||||
|
||||
todaysHeartbeat = heartbeatsBundle.removeHeartbeat(from: todaysDate)
|
||||
|
||||
return heartbeatsBundle
|
||||
}
|
||||
|
||||
// Note that `todaysHeartbeat` is updated in the above read/write block.
|
||||
if todaysHeartbeat != nil {
|
||||
return todaysHeartbeat!.makeHeartbeatsPayload()
|
||||
} else {
|
||||
return HeartbeatsPayload.emptyPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatLoggingTestUtils.swift
generated
Normal file
140
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatLoggingTestUtils.swift
generated
Normal file
@ -0,0 +1,140 @@
|
||||
// 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.
|
||||
|
||||
#if DEBUG
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A utility class intended to be used only in testing contexts.
|
||||
@objc(FIRHeartbeatLoggingTestUtils)
|
||||
@objcMembers
|
||||
public class HeartbeatLoggingTestUtils: NSObject {
|
||||
/// This should mirror the `Constants` enum in the `HeartbeatLogging` module.
|
||||
/// See `HeartbeatLogging/Sources/StorageFactory.swift`.
|
||||
public enum Constants {
|
||||
/// The name of the file system directory where heartbeat data is stored.
|
||||
public static let heartbeatFileStorageDirectoryPath = "google-heartbeat-storage"
|
||||
/// The name of the user defaults suite where heartbeat data is stored.
|
||||
public static let heartbeatUserDefaultsSuiteName = "com.google.heartbeat.storage"
|
||||
}
|
||||
|
||||
public static var dateFormatter: DateFormatter {
|
||||
HeartbeatsPayload.dateFormatter
|
||||
}
|
||||
|
||||
public static var emptyHeartbeatsPayload: _ObjC_HeartbeatsPayload {
|
||||
let literalData = """
|
||||
{
|
||||
"version": 2,
|
||||
"heartbeats": []
|
||||
}
|
||||
"""
|
||||
.data(using: .utf8)!
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .formatted(HeartbeatsPayload.dateFormatter)
|
||||
|
||||
let heartbeatsPayload = try! decoder.decode(HeartbeatsPayload.self, from: literalData)
|
||||
return _ObjC_HeartbeatsPayload(heartbeatsPayload)
|
||||
}
|
||||
|
||||
public static var nonEmptyHeartbeatsPayload: _ObjC_HeartbeatsPayload {
|
||||
let literalData = """
|
||||
{
|
||||
"version": 2,
|
||||
"heartbeats": [
|
||||
{
|
||||
"agent": "dummy_agent_1",
|
||||
"dates": ["2021-11-01", "2021-11-02"]
|
||||
},
|
||||
{
|
||||
"agent": "dummy_agent_2",
|
||||
"dates": ["2021-11-03"]
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.data(using: .utf8)!
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .formatted(HeartbeatsPayload.dateFormatter)
|
||||
|
||||
let heartbeatsPayload = try! decoder.decode(HeartbeatsPayload.self, from: literalData)
|
||||
return _ObjC_HeartbeatsPayload(heartbeatsPayload)
|
||||
}
|
||||
|
||||
@objc(assertEncodedPayloadString:isEqualToLiteralString:withError:)
|
||||
public static func assertEqualPayloadStrings(_ encoded: String, _ literal: String) throws {
|
||||
var encodedData = Data(base64URLEncoded: encoded)!
|
||||
if encodedData.count > 0 {
|
||||
encodedData = try! encodedData.unzipped()
|
||||
}
|
||||
|
||||
let literalData = literal.data(using: .utf8)!
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .formatted(HeartbeatsPayload.dateFormatter)
|
||||
|
||||
let payloadFromEncoded = try? decoder.decode(HeartbeatsPayload.self, from: encodedData)
|
||||
|
||||
let payloadFromLiteral = try? decoder.decode(HeartbeatsPayload.self, from: literalData)
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .formatted(HeartbeatsPayload.dateFormatter)
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
|
||||
let payloadDataFromEncoded = try! encoder.encode(payloadFromEncoded)
|
||||
let payloadDataFromLiteral = try! encoder.encode(payloadFromLiteral)
|
||||
|
||||
assert(
|
||||
payloadFromEncoded == payloadFromLiteral,
|
||||
"""
|
||||
Mismatched payloads!
|
||||
|
||||
Payload 1:
|
||||
\(String(data: payloadDataFromEncoded, encoding: .utf8) ?? "")
|
||||
|
||||
Payload 2:
|
||||
\(String(data: payloadDataFromLiteral, encoding: .utf8) ?? "")
|
||||
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes all underlying storage containers used by the module.
|
||||
/// - Throws: An error if the storage container could not be removed.
|
||||
public static func removeUnderlyingHeartbeatStorageContainers() throws {
|
||||
#if os(tvOS)
|
||||
UserDefaults().removePersistentDomain(forName: Constants.heartbeatUserDefaultsSuiteName)
|
||||
#else
|
||||
|
||||
let applicationSupportDirectory = FileManager.default
|
||||
.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
|
||||
let heartbeatsDirectoryURL = applicationSupportDirectory
|
||||
.appendingPathComponent(
|
||||
Constants.heartbeatFileStorageDirectoryPath, isDirectory: true
|
||||
)
|
||||
do {
|
||||
try FileManager.default.removeItem(at: heartbeatsDirectoryURL)
|
||||
} catch CocoaError.fileNoSuchFile {
|
||||
// Do nothing.
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
#endif // os(tvOS)
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_FIREBASE_CORE_INTERNAL_TESTING_UTILS
|
||||
180
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift
generated
Normal file
180
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift
generated
Normal file
@ -0,0 +1,180 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A type that can perform atomic operations using block-based transformations.
|
||||
protocol HeartbeatStorageProtocol {
|
||||
func readAndWriteSync(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?)
|
||||
func readAndWriteAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?)
|
||||
func getAndSet(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) throws
|
||||
-> HeartbeatsBundle?
|
||||
}
|
||||
|
||||
/// Thread-safe storage object designed for transforming heartbeat data that is persisted to disk.
|
||||
final class HeartbeatStorage: 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
|
||||
/// The encoder used for encoding heartbeat data.
|
||||
private let encoder: JSONEncoder = .init()
|
||||
/// The decoder used for decoding heartbeat data.
|
||||
private let decoder: JSONDecoder = .init()
|
||||
/// The queue for synchronizing storage operations.
|
||||
private let queue: DispatchQueue
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameters:
|
||||
/// - id: A string identifier.
|
||||
/// - storage: The underlying storage container where heartbeat data is stored.
|
||||
init(id: String,
|
||||
storage: Storage) {
|
||||
self.id = id
|
||||
self.storage = storage
|
||||
queue = DispatchQueue(label: "com.heartbeat.storage.\(id)")
|
||||
}
|
||||
|
||||
// MARK: - Instance Management
|
||||
|
||||
/// Statically allocated cache of `HeartbeatStorage` instances keyed by string IDs.
|
||||
private static var cachedInstances: [String: WeakContainer<HeartbeatStorage>] = [:]
|
||||
|
||||
/// Gets an existing `HeartbeatStorage` instance with the given `id` if one exists. Otherwise,
|
||||
/// makes a new instance with the given `id`.
|
||||
///
|
||||
/// - 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a `HeartbeatStorage` instance using a given `String` identifier.
|
||||
///
|
||||
/// The created persistent storage object is platform dependent. For tvOS, user defaults
|
||||
/// is used as the underlying storage container due to system storage limits. For all other
|
||||
/// platforms,
|
||||
/// the file system is used.
|
||||
///
|
||||
/// - Parameter id: A `String` identifier used to create the `HeartbeatStorage`.
|
||||
/// - Returns: A `HeartbeatStorage` instance.
|
||||
private static func makeHeartbeatStorage(id: String) -> HeartbeatStorage {
|
||||
#if os(tvOS)
|
||||
let storage = UserDefaultsStorage.makeStorage(id: id)
|
||||
#else
|
||||
let storage = FileStorage.makeStorage(id: id)
|
||||
#endif // os(tvOS)
|
||||
return HeartbeatStorage(id: id, storage: storage)
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Removes the instance if it was cached.
|
||||
Self.cachedInstances.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
// MARK: - HeartbeatStorageProtocol
|
||||
|
||||
/// Synchronously 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 readAndWriteSync(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) {
|
||||
queue.sync {
|
||||
let oldHeartbeatsBundle = try? load(from: storage)
|
||||
let newHeartbeatsBundle = transform(oldHeartbeatsBundle)
|
||||
try? save(newHeartbeatsBundle, to: storage)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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?) {
|
||||
queue.async { [self] in
|
||||
let oldHeartbeatsBundle = try? load(from: storage)
|
||||
let newHeartbeatsBundle = transform(oldHeartbeatsBundle)
|
||||
try? save(newHeartbeatsBundle, to: storage)
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronously gets the current heartbeat data from storage and resets the storage using the
|
||||
/// given transform block.
|
||||
///
|
||||
/// This API is like any `getAndSet`-style API in that it gets (and returns) the current value and
|
||||
/// uses
|
||||
/// a block to transform the current value (or, soon-to-be old value) to a new value.
|
||||
///
|
||||
/// - Parameter transform: An optional block used to reset the currently stored heartbeat.
|
||||
/// - Returns: The heartbeat data that was stored (before the `transform` was applied).
|
||||
@discardableResult
|
||||
func getAndSet(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) throws
|
||||
-> HeartbeatsBundle? {
|
||||
let heartbeatsBundle: HeartbeatsBundle? = try queue.sync {
|
||||
let oldHeartbeatsBundle = try? load(from: storage)
|
||||
let newHeartbeatsBundle = transform(oldHeartbeatsBundle)
|
||||
try save(newHeartbeatsBundle, to: storage)
|
||||
return oldHeartbeatsBundle
|
||||
}
|
||||
return heartbeatsBundle
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - Throws: An error if storage could not be read or the data could not be decoded.
|
||||
private func load(from storage: Storage) throws -> HeartbeatsBundle? {
|
||||
let data = try storage.read()
|
||||
if data.isEmpty {
|
||||
return nil
|
||||
} else {
|
||||
let heartbeatData = try data.decoded(using: decoder) as HeartbeatsBundle
|
||||
return heartbeatData
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves the encoding of the given value to the given storage container.
|
||||
/// - Parameters:
|
||||
/// - heartbeatsBundle: The heartbeats bundle to encode and save.
|
||||
/// - storage: The storage container to write to.
|
||||
private func save(_ heartbeatsBundle: HeartbeatsBundle?, to storage: Storage) throws {
|
||||
if let heartbeatsBundle {
|
||||
let data = try heartbeatsBundle.encoded(using: encoder)
|
||||
try storage.write(data)
|
||||
} else {
|
||||
try storage.write(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Data {
|
||||
/// Returns the decoded value of this `Data` using the given decoder. Defaults to `JSONDecoder`.
|
||||
/// - Returns: The decoded value.
|
||||
func decoded<T>(using decoder: JSONDecoder = .init()) throws -> T where T: Decodable {
|
||||
try decoder.decode(T.self, from: self)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Encodable {
|
||||
/// Returns the `Data` encoding of this value using the given encoder.
|
||||
/// - Parameter encoder: An encoder used to encode the value. Defaults to `JSONEncoder`.
|
||||
/// - Returns: The data encoding of the value.
|
||||
func encoded(using encoder: JSONEncoder = .init()) throws -> Data {
|
||||
try encoder.encode(self)
|
||||
}
|
||||
}
|
||||
151
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatsBundle.swift
generated
Normal file
151
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatsBundle.swift
generated
Normal file
@ -0,0 +1,151 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A type that can be converted to a `HeartbeatsPayload`.
|
||||
protocol HeartbeatsPayloadConvertible {
|
||||
func makeHeartbeatsPayload() -> HeartbeatsPayload
|
||||
}
|
||||
|
||||
/// A codable collection of heartbeats that has a fixed capacity and optimizations for storing
|
||||
/// heartbeats of
|
||||
/// multiple time periods.
|
||||
struct HeartbeatsBundle: Codable, HeartbeatsPayloadConvertible {
|
||||
/// The maximum number of heartbeats that can be stored in the buffer.
|
||||
let capacity: Int
|
||||
/// A cache used for keeping track of the last heartbeat date recorded for a given time period.
|
||||
///
|
||||
/// The cache contains the last added date for each time period. The reason only the date is
|
||||
/// cached is
|
||||
/// because it's the only piece of information that should be used by clients to determine whether
|
||||
/// or not
|
||||
/// to append a new heartbeat.
|
||||
private(set) var lastAddedHeartbeatDates: [TimePeriod: Date]
|
||||
/// A ring buffer of heartbeats.
|
||||
private var buffer: RingBuffer<Heartbeat>
|
||||
|
||||
/// A default cache provider that provides a dictionary of all time periods mapping to a default
|
||||
/// date.
|
||||
static var cacheProvider: () -> [TimePeriod: Date] {
|
||||
let timePeriodsAndDates = TimePeriod.allCases.map { ($0, Date.distantPast) }
|
||||
return { Dictionary(uniqueKeysWithValues: timePeriodsAndDates) }
|
||||
}
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameters:
|
||||
/// - capacity: The heartbeat capacity of the initialized collection.
|
||||
/// - cache: A cache of time periods mapping to dates. Defaults to using static `cacheProvider`.
|
||||
init(capacity: Int,
|
||||
cache: [TimePeriod: Date] = cacheProvider()) {
|
||||
buffer = RingBuffer(capacity: capacity)
|
||||
self.capacity = capacity
|
||||
lastAddedHeartbeatDates = cache
|
||||
}
|
||||
|
||||
/// Appends a heartbeat to this collection.
|
||||
/// - Parameter heartbeat: The heartbeat to append.
|
||||
mutating func append(_ heartbeat: Heartbeat) {
|
||||
guard capacity > 0 else {
|
||||
return // Do not append if capacity is non-positive.
|
||||
}
|
||||
|
||||
do {
|
||||
// Push the heartbeat to the back of the buffer.
|
||||
if let overwrittenHeartbeat = try buffer.push(heartbeat) {
|
||||
// If a heartbeat was overwritten, update the cache to ensure it's date
|
||||
// is removed.
|
||||
lastAddedHeartbeatDates = lastAddedHeartbeatDates.mapValues { date in
|
||||
overwrittenHeartbeat.date == date ? .distantPast : date
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache with the new heartbeat's date.
|
||||
for timePeriod in heartbeat.timePeriods {
|
||||
lastAddedHeartbeatDates[timePeriod] = heartbeat.date
|
||||
}
|
||||
|
||||
} catch let error as RingBuffer<Heartbeat>.Error {
|
||||
// A ring buffer error occurred while pushing to the buffer so the bundle
|
||||
// is reset.
|
||||
self = HeartbeatsBundle(capacity: capacity)
|
||||
|
||||
// Create a diagnostic heartbeat to capture the failure and add it to the
|
||||
// buffer. The failure is added as a key/value pair to the agent string.
|
||||
// Given that the ring buffer has been reset, it is not expected for the
|
||||
// second push attempt to fail.
|
||||
let errorDescription = error.errorDescription.replacingOccurrences(of: " ", with: "-")
|
||||
let diagnosticHeartbeat = Heartbeat(
|
||||
agent: "\(heartbeat.agent) error/\(errorDescription)",
|
||||
date: heartbeat.date,
|
||||
timePeriods: heartbeat.timePeriods
|
||||
)
|
||||
|
||||
let secondPushAttempt = Result {
|
||||
try buffer.push(diagnosticHeartbeat)
|
||||
}
|
||||
|
||||
if case .success = secondPushAttempt {
|
||||
// Update cache with the new heartbeat's date.
|
||||
for timePeriod in diagnosticHeartbeat.timePeriods {
|
||||
lastAddedHeartbeatDates[timePeriod] = diagnosticHeartbeat.date
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore other error.
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the heartbeat associated with the given date.
|
||||
/// - Parameter date: The date of the heartbeat needing removal.
|
||||
/// - Returns: The heartbeat that was removed or `nil` if there was no heartbeat to remove.
|
||||
@discardableResult
|
||||
mutating func removeHeartbeat(from date: Date) -> Heartbeat? {
|
||||
var removedHeartbeat: Heartbeat?
|
||||
|
||||
var poppedHeartbeats: [Heartbeat] = []
|
||||
|
||||
while let poppedHeartbeat = buffer.pop() {
|
||||
if poppedHeartbeat.date == date {
|
||||
removedHeartbeat = poppedHeartbeat
|
||||
break
|
||||
}
|
||||
poppedHeartbeats.append(poppedHeartbeat)
|
||||
}
|
||||
|
||||
for poppedHeartbeat in poppedHeartbeats.reversed() {
|
||||
do {
|
||||
try buffer.push(poppedHeartbeat)
|
||||
} catch {
|
||||
// Ignore error.
|
||||
}
|
||||
}
|
||||
|
||||
return removedHeartbeat
|
||||
}
|
||||
|
||||
/// Makes and returns a `HeartbeatsPayload` from this heartbeats bundle.
|
||||
/// - Returns: A heartbeats payload.
|
||||
func makeHeartbeatsPayload() -> HeartbeatsPayload {
|
||||
let agentAndDates = buffer.map { heartbeat in
|
||||
(heartbeat.agent, [heartbeat.date])
|
||||
}
|
||||
|
||||
let userAgentPayloads = [String: [Date]](agentAndDates, uniquingKeysWith: +)
|
||||
.map(HeartbeatsPayload.UserAgentPayload.init)
|
||||
.sorted { $0.agent < $1.agent } // Sort payloads by user agent.
|
||||
|
||||
return HeartbeatsPayload(userAgentPayloads: userAgentPayloads)
|
||||
}
|
||||
}
|
||||
181
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatsPayload.swift
generated
Normal file
181
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatsPayload.swift
generated
Normal file
@ -0,0 +1,181 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
@_implementationOnly import GoogleUtilities_NSData
|
||||
#else
|
||||
@_implementationOnly import GoogleUtilities
|
||||
#endif // SWIFT_PACKAGE
|
||||
|
||||
/// A type that provides a string representation for use in an HTTP header.
|
||||
public protocol HTTPHeaderRepresentable {
|
||||
func headerValue() -> String
|
||||
}
|
||||
|
||||
/// A value type representing a payload of heartbeat data intended for sending in network requests.
|
||||
///
|
||||
/// This type's structure is optimized for type-safe encoding into a HTTP payload format.
|
||||
/// The current encoding format for the payload's current version is:
|
||||
///
|
||||
/// {
|
||||
/// "version": 2,
|
||||
/// "heartbeats": [
|
||||
/// {
|
||||
/// "agent": "dummy_agent_1",
|
||||
/// "dates": ["2021-11-01", "2021-11-02"]
|
||||
/// },
|
||||
/// {
|
||||
/// "agent": "dummy_agent_2",
|
||||
/// "dates": ["2021-11-03"]
|
||||
/// }
|
||||
/// ]
|
||||
/// }
|
||||
///
|
||||
public struct HeartbeatsPayload: Codable, Sendable {
|
||||
/// The version of the payload. See go/firebase-apple-heartbeats for details regarding current
|
||||
/// version.
|
||||
static let version: Int = 2
|
||||
|
||||
/// A payload component composed of a user agent and array of dates (heartbeats).
|
||||
struct UserAgentPayload: Codable {
|
||||
/// An anonymous agent string.
|
||||
let agent: String
|
||||
/// An array of dates where each date represents a "heartbeat".
|
||||
let dates: [Date]
|
||||
}
|
||||
|
||||
/// An array of user agent payloads.
|
||||
let userAgentPayloads: [UserAgentPayload]
|
||||
/// The version of the payload structure.
|
||||
let version: Int
|
||||
|
||||
/// Alternative keys for properties so encoding follows platform-wide payload structure.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case userAgentPayloads = "heartbeats"
|
||||
case version
|
||||
}
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameters:
|
||||
/// - userAgentPayloads: An array of payloads containing heartbeat data corresponding to a
|
||||
/// given user agent.
|
||||
/// - version: A version of the payload. Defaults to the static default.
|
||||
init(userAgentPayloads: [UserAgentPayload] = [], version: Int = version) {
|
||||
self.userAgentPayloads = userAgentPayloads
|
||||
self.version = version
|
||||
}
|
||||
|
||||
/// A Boolean value indicating whether the payload is empty.
|
||||
public var isEmpty: Bool {
|
||||
userAgentPayloads.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - HTTPHeaderRepresentable
|
||||
|
||||
extension HeartbeatsPayload: HTTPHeaderRepresentable {
|
||||
/// Returns a processed payload string intended for use in a HTTP header.
|
||||
/// - Returns: A string value from the heartbeats payload.
|
||||
public func headerValue() -> String {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .formatted(Self.dateFormatter)
|
||||
#if DEBUG
|
||||
// Sort keys in debug builds to simplify output comparisons in unit tests.
|
||||
encoder.outputFormatting = .sortedKeys
|
||||
#endif // DEBUG
|
||||
|
||||
guard let data = try? encoder.encode(self) else {
|
||||
// If encoding fails, fall back to encoding with an empty payload.
|
||||
return Self.emptyPayload.headerValue()
|
||||
}
|
||||
|
||||
do {
|
||||
let gzippedData = try data.zipped()
|
||||
return gzippedData.base64URLEncodedString()
|
||||
} catch {
|
||||
// If gzipping fails, fall back to encoding with base64URL.
|
||||
return data.base64URLEncodedString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Static Defaults
|
||||
|
||||
extension HeartbeatsPayload {
|
||||
/// Convenience instance that represents an empty payload.
|
||||
static let emptyPayload = HeartbeatsPayload()
|
||||
|
||||
/// A default date formatter that uses `yyyy-MM-dd` format.
|
||||
public static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
extension HeartbeatsPayload: Equatable {}
|
||||
extension HeartbeatsPayload.UserAgentPayload: Equatable {}
|
||||
|
||||
// MARK: - Data
|
||||
|
||||
public extension Data {
|
||||
/// Returns a Base-64 URL-safe encoded string.
|
||||
///
|
||||
/// - parameter options: The options to use for the encoding. Default value is `[]`.
|
||||
/// - returns: The Base-64 URL-safe encoded string.
|
||||
func base64URLEncodedString(options: Data.Base64EncodingOptions = []) -> String {
|
||||
base64EncodedString()
|
||||
.replacingOccurrences(of: "/", with: "_")
|
||||
.replacingOccurrences(of: "+", with: "-")
|
||||
.replacingOccurrences(of: "=", with: "")
|
||||
}
|
||||
|
||||
/// Initialize a `Data` from a Base-64 URL encoded String using the given options.
|
||||
///
|
||||
/// Returns nil when the input is not recognized as valid Base-64.
|
||||
/// - parameter base64URLString: The string to parse.
|
||||
/// - parameter options: Encoding options. Default value is `[]`.
|
||||
init?(base64URLEncoded base64URLString: String, options: Data.Base64DecodingOptions = []) {
|
||||
var base64Encoded = base64URLString
|
||||
.replacingOccurrences(of: "_", with: "/")
|
||||
.replacingOccurrences(of: "-", with: "+")
|
||||
|
||||
// Pad the string with "=" signs until the string's length is a multiple of 4.
|
||||
while !base64Encoded.count.isMultiple(of: 4) {
|
||||
base64Encoded.append("=")
|
||||
}
|
||||
|
||||
self.init(base64Encoded: base64Encoded, options: options)
|
||||
}
|
||||
|
||||
/// Returns the compressed data.
|
||||
/// - Returns: The compressed data.
|
||||
/// - Throws: An error if compression failed.
|
||||
func zipped() throws -> Data {
|
||||
try NSData.gul_data(byGzippingData: self)
|
||||
}
|
||||
|
||||
/// Returns the uncompressed data.
|
||||
/// - Returns: The decompressed data.
|
||||
/// - Throws: An error if decompression failed.
|
||||
func unzipped() throws -> Data {
|
||||
try NSData.gul_data(byInflatingGzippedData: self)
|
||||
}
|
||||
}
|
||||
111
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/RingBuffer.swift
generated
Normal file
111
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/RingBuffer.swift
generated
Normal file
@ -0,0 +1,111 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A generic circular queue structure.
|
||||
struct RingBuffer<Element>: Sequence {
|
||||
/// An array of heartbeats treated as a circular queue and initialized with a fixed capacity.
|
||||
private var circularQueue: [Element?]
|
||||
/// The current "tail" and insert point for the `circularQueue`.
|
||||
private var tailIndex: Array<Element?>.Index
|
||||
|
||||
/// Error types for `RingBuffer` operations.
|
||||
enum Error: LocalizedError {
|
||||
case outOfBoundsPush(pushIndex: Array<Element?>.Index, endIndex: Array<Element?>.Index)
|
||||
|
||||
var errorDescription: String {
|
||||
switch self {
|
||||
case let .outOfBoundsPush(pushIndex, endIndex):
|
||||
return "Out-of-bounds push at index \(pushIndex) to ring buffer with" +
|
||||
"end index of \(endIndex)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameter capacity: An `Int` representing the capacity.
|
||||
init(capacity: Int) {
|
||||
circularQueue = Array(repeating: nil, count: capacity)
|
||||
tailIndex = circularQueue.startIndex
|
||||
}
|
||||
|
||||
/// Pushes an element to the back of the buffer, returning the element (`Element?`) that was
|
||||
/// overwritten.
|
||||
/// - Parameter element: The element to push to the back of the buffer.
|
||||
/// - Returns: The element that was overwritten or `nil` if nothing was overwritten.
|
||||
/// - Complexity: O(1)
|
||||
@discardableResult
|
||||
mutating func push(_ element: Element) throws -> Element? {
|
||||
guard circularQueue.count > 0 else {
|
||||
// Do not push if `circularQueue` is a fixed empty array.
|
||||
return nil
|
||||
}
|
||||
|
||||
guard circularQueue.indices.contains(tailIndex) else {
|
||||
// We have somehow entered an invalid state (#10025).
|
||||
throw Self.Error.outOfBoundsPush(
|
||||
pushIndex: tailIndex,
|
||||
endIndex: circularQueue.endIndex
|
||||
)
|
||||
}
|
||||
|
||||
let replaced = circularQueue[tailIndex]
|
||||
circularQueue[tailIndex] = element
|
||||
|
||||
// Increment index, wrapping around to the start if needed.
|
||||
tailIndex += 1
|
||||
if tailIndex >= circularQueue.endIndex {
|
||||
tailIndex = circularQueue.startIndex
|
||||
}
|
||||
|
||||
return replaced
|
||||
}
|
||||
|
||||
/// Pops an element from the back of the buffer, returning the element (`Element?`) that was
|
||||
/// popped.
|
||||
/// - Returns: The element that was popped or `nil` if there was no element to pop.
|
||||
/// - Complexity: O(1)
|
||||
@discardableResult
|
||||
mutating func pop() -> Element? {
|
||||
guard circularQueue.count > 0 else {
|
||||
// Do not pop if `circularQueue` is a fixed empty array.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrement index, wrapping around to the back if needed.
|
||||
tailIndex -= 1
|
||||
if tailIndex < circularQueue.startIndex {
|
||||
tailIndex = circularQueue.endIndex - 1
|
||||
}
|
||||
|
||||
guard let popped = circularQueue[tailIndex] else {
|
||||
return nil // There is no element to pop.
|
||||
}
|
||||
|
||||
circularQueue[tailIndex] = nil
|
||||
|
||||
return popped
|
||||
}
|
||||
|
||||
func makeIterator() -> IndexingIterator<[Element]> {
|
||||
circularQueue
|
||||
.compactMap { $0 } // Remove `nil` elements.
|
||||
.makeIterator()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Codable
|
||||
|
||||
extension RingBuffer: Codable where Element: Codable {}
|
||||
145
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/Storage.swift
generated
Normal file
145
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/Storage.swift
generated
Normal file
@ -0,0 +1,145 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A type that reads from and writes to an underlying storage container.
|
||||
protocol Storage {
|
||||
/// Reads and returns the data stored by this storage type.
|
||||
/// - Returns: The data read from storage.
|
||||
/// - Throws: An error if the read failed.
|
||||
func read() throws -> Data
|
||||
|
||||
/// Writes the given data to this storage type.
|
||||
/// - Throws: An error if the write failed.
|
||||
func write(_ data: Data?) throws
|
||||
}
|
||||
|
||||
/// Error types for `Storage` operations.
|
||||
enum StorageError: Error {
|
||||
case readError
|
||||
case writeError
|
||||
}
|
||||
|
||||
// MARK: - FileStorage
|
||||
|
||||
/// A object that provides API for reading and writing to a file system resource.
|
||||
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) {
|
||||
self.url = url
|
||||
self.fileManager = fileManager
|
||||
}
|
||||
|
||||
/// Reads and returns the data from this object's associated file resource.
|
||||
///
|
||||
/// - Returns: The data stored on disk.
|
||||
/// - Throws: An error if reading the contents of the file resource fails (i.e. file doesn't
|
||||
/// exist).
|
||||
func read() throws -> Data {
|
||||
do {
|
||||
return try Data(contentsOf: url)
|
||||
} catch {
|
||||
throw StorageError.readError
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the given data to this object's associated file resource.
|
||||
///
|
||||
/// When the given `data` is `nil`, this object's associated file resource is emptied.
|
||||
///
|
||||
/// - Parameter data: The `Data?` to write to this object's associated file resource.
|
||||
func write(_ data: Data?) throws {
|
||||
do {
|
||||
try createDirectories(in: url.deletingLastPathComponent())
|
||||
if let data {
|
||||
try data.write(to: url, options: .atomic)
|
||||
} else {
|
||||
let emptyData = Data()
|
||||
try emptyData.write(to: url, options: .atomic)
|
||||
}
|
||||
} catch {
|
||||
throw StorageError.writeError
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates all directories in the given file system URL.
|
||||
///
|
||||
/// If the directory for the given URL already exists, the error is ignored because the directory
|
||||
/// has already been created.
|
||||
///
|
||||
/// - Parameter url: The URL to create directories in.
|
||||
private func createDirectories(in url: URL) throws {
|
||||
do {
|
||||
try fileManager.createDirectory(
|
||||
at: url,
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
} catch CocoaError.fileWriteFileExists {
|
||||
// Directory already exists.
|
||||
} catch { throw error }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UserDefaultsStorage
|
||||
|
||||
/// 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 key mapping to the object's associated resource in `defaults`.
|
||||
private let key: String
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameters:
|
||||
/// - defaults: The defaults container.
|
||||
/// - key: The key mapping to the value stored in the defaults container.
|
||||
init(defaults: UserDefaults, key: String) {
|
||||
self.defaults = defaults
|
||||
self.key = key
|
||||
}
|
||||
|
||||
/// Reads and returns the data from this object's associated defaults resource.
|
||||
///
|
||||
/// - Returns: The data stored on disk.
|
||||
/// - Throws: An error if no data has been stored to the defaults container.
|
||||
func read() throws -> Data {
|
||||
if let data = defaults.data(forKey: key) {
|
||||
return data
|
||||
} else {
|
||||
throw StorageError.readError
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the given data to this object's associated defaults.
|
||||
///
|
||||
/// When the given `data` is `nil`, the associated default is removed.
|
||||
///
|
||||
/// - Parameter data: The `Data?` to write to this object's associated defaults.
|
||||
func write(_ data: Data?) throws {
|
||||
if let data {
|
||||
defaults.set(data, forKey: key)
|
||||
} else {
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/StorageFactory.swift
generated
Normal file
66
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/StorageFactory.swift
generated
Normal file
@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
private enum Constants {
|
||||
/// The name of the file system directory where heartbeat data is stored.
|
||||
static let heartbeatFileStorageDirectoryPath = "google-heartbeat-storage"
|
||||
/// The name of the user defaults suite where heartbeat data is stored.
|
||||
static let heartbeatUserDefaultsSuiteName = "com.google.heartbeat.storage"
|
||||
}
|
||||
|
||||
/// A factory type for `Storage`.
|
||||
protocol StorageFactory {
|
||||
static func makeStorage(id: String) -> Storage
|
||||
}
|
||||
|
||||
// MARK: - FileStorage + StorageFactory
|
||||
|
||||
extension FileStorage: StorageFactory {
|
||||
static func makeStorage(id: String) -> Storage {
|
||||
let rootDirectory = FileManager.default.applicationSupportDirectory
|
||||
let heartbeatDirectoryPath = Constants.heartbeatFileStorageDirectoryPath
|
||||
|
||||
// Sanitize the `id` so the heartbeat file name does not include a ":".
|
||||
let sanitizedID = id.replacingOccurrences(of: ":", with: "_")
|
||||
let heartbeatFilePath = "heartbeats-\(sanitizedID)"
|
||||
|
||||
let storageURL = rootDirectory
|
||||
.appendingPathComponent(heartbeatDirectoryPath, isDirectory: true)
|
||||
.appendingPathComponent(heartbeatFilePath, isDirectory: false)
|
||||
|
||||
return FileStorage(url: storageURL)
|
||||
}
|
||||
}
|
||||
|
||||
extension FileManager {
|
||||
var applicationSupportDirectory: URL {
|
||||
urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UserDefaultsStorage + StorageFactory
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
20
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/WeakContainer.swift
generated
Normal file
20
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/HeartbeatLogging/WeakContainer.swift
generated
Normal file
@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A structure used to weakly box reference types.
|
||||
struct WeakContainer<Object: AnyObject> {
|
||||
weak var object: Object?
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An object that provides API to log and flush heartbeats from a synchronized storage container.
|
||||
@objc(FIRHeartbeatController)
|
||||
@objcMembers
|
||||
public class _ObjC_HeartbeatController: NSObject {
|
||||
/// The underlying Swift object.
|
||||
private let heartbeatController: HeartbeatController
|
||||
|
||||
/// Public initializer.
|
||||
/// - Parameter id: The `id` to associate this controller's heartbeat storage with.
|
||||
public init(id: String) {
|
||||
heartbeatController = HeartbeatController(id: id)
|
||||
}
|
||||
|
||||
/// Asynchronously logs a new heartbeat, if needed.
|
||||
///
|
||||
/// - Note: This API is thread-safe.
|
||||
/// - Parameter agent: The string agent (i.e. Firebase User Agent) to associate the logged
|
||||
/// heartbeat with.
|
||||
public func log(_ agent: String) {
|
||||
heartbeatController.log(agent)
|
||||
}
|
||||
|
||||
/// Synchronously flushes heartbeats from storage into a heartbeats payload.
|
||||
///
|
||||
/// - Note: This API is thread-safe.
|
||||
/// - Returns: A heartbeats payload for the flushed heartbeat(s).
|
||||
public func flush() -> _ObjC_HeartbeatsPayload {
|
||||
let heartbeatsPayload = heartbeatController.flush()
|
||||
return _ObjC_HeartbeatsPayload(heartbeatsPayload)
|
||||
}
|
||||
|
||||
/// Synchronously flushes the heartbeat for today.
|
||||
///
|
||||
/// If no heartbeat was logged today, the returned payload is empty.
|
||||
///
|
||||
/// - Note: This API is thread-safe.
|
||||
/// - Returns: A heartbeats payload for the flushed heartbeat.
|
||||
public func flushHeartbeatFromToday() -> _ObjC_HeartbeatsPayload {
|
||||
let heartbeatsPayload = heartbeatController.flushHeartbeatFromToday()
|
||||
return _ObjC_HeartbeatsPayload(heartbeatsPayload)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A model object representing a payload of heartbeat data intended for sending in network
|
||||
/// requests.
|
||||
@objc(FIRHeartbeatsPayload)
|
||||
public class _ObjC_HeartbeatsPayload: NSObject, HTTPHeaderRepresentable {
|
||||
/// The underlying Swift structure.
|
||||
private let heartbeatsPayload: HeartbeatsPayload
|
||||
|
||||
/// Designated initializer.
|
||||
/// - Parameter heartbeatsPayload: A native-Swift heartbeats payload.
|
||||
public init(_ heartbeatsPayload: HeartbeatsPayload) {
|
||||
self.heartbeatsPayload = heartbeatsPayload
|
||||
}
|
||||
|
||||
/// Returns a processed payload string intended for use in a HTTP header.
|
||||
/// - Returns: A string value from the heartbeats payload.
|
||||
@objc public func headerValue() -> String {
|
||||
heartbeatsPayload.headerValue()
|
||||
}
|
||||
|
||||
/// A Boolean value indicating whether the payload is empty.
|
||||
@objc public var isEmpty: Bool {
|
||||
heartbeatsPayload.isEmpty
|
||||
}
|
||||
}
|
||||
26
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/Resources/PrivacyInfo.xcprivacy
generated
Normal file
26
Pods/FirebaseCoreInternal/FirebaseCore/Internal/Sources/Resources/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
</array>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>1C8F.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
202
Pods/FirebaseCoreInternal/LICENSE
generated
Normal file
202
Pods/FirebaseCoreInternal/LICENSE
generated
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
302
Pods/FirebaseCoreInternal/README.md
generated
Normal file
302
Pods/FirebaseCoreInternal/README.md
generated
Normal file
@ -0,0 +1,302 @@
|
||||
<p align="center">
|
||||
<a href="https://cocoapods.org/pods/Firebase">
|
||||
<img src="https://img.shields.io/github/v/release/Firebase/firebase-ios-sdk?style=flat&label=CocoaPods"/>
|
||||
</a>
|
||||
<a href="https://swiftpackageindex.com/firebase/firebase-ios-sdk">
|
||||
<img src="https://img.shields.io/github/v/release/Firebase/firebase-ios-sdk?style=flat&label=Swift%20Package%20Index&color=red"/>
|
||||
</a>
|
||||
<a href="https://cocoapods.org/pods/Firebase">
|
||||
<img src="https://img.shields.io/github/license/Firebase/firebase-ios-sdk?style=flat"/>
|
||||
</a><br/>
|
||||
<a href="https://swiftpackageindex.com/firebase/firebase-ios-sdk">
|
||||
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffirebase%2Ffirebase-ios-sdk%2Fbadge%3Ftype%3Dplatforms"/>
|
||||
</a>
|
||||
<a href="https://swiftpackageindex.com/firebase/firebase-ios-sdk">
|
||||
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffirebase%2Ffirebase-ios-sdk%2Fbadge%3Ftype%3Dswift-versions"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# Firebase Apple Open Source Development
|
||||
|
||||
This repository contains the source code for all Apple platform Firebase SDKs except FirebaseAnalytics.
|
||||
|
||||
Firebase is an app development platform with tools to help you build, grow, and
|
||||
monetize your app. More information about Firebase can be found on the
|
||||
[official Firebase website](https://firebase.google.com).
|
||||
|
||||
## Installation
|
||||
|
||||
See the subsections below for details about the different installation methods. Where
|
||||
available, it's recommended to install any libraries with a `Swift` suffix to get the
|
||||
best experience when writing your app in Swift.
|
||||
|
||||
1. [Standard pod install](#standard-pod-install)
|
||||
2. [Swift Package Manager](#swift-package-manager)
|
||||
3. [Installing from the GitHub repo](#installing-from-github)
|
||||
4. [Experimental Carthage](#carthage-ios-only)
|
||||
|
||||
### Standard pod install
|
||||
|
||||
For instructions on the standard pod install, visit:
|
||||
[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
Instructions for [Swift Package Manager](https://swift.org/package-manager/) support can be
|
||||
found in the [SwiftPackageManager.md](SwiftPackageManager.md) Markdown file.
|
||||
|
||||
### Installing from GitHub
|
||||
|
||||
These instructions can be used to access the Firebase repo at other branches,
|
||||
tags, or commits.
|
||||
|
||||
#### Background
|
||||
|
||||
See [the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
|
||||
for instructions and options about overriding pod source locations.
|
||||
|
||||
#### Accessing Firebase Source Snapshots
|
||||
|
||||
All official releases are tagged in this repo and available via CocoaPods. To access a local
|
||||
source snapshot or unreleased branch, use Podfile directives like the following:
|
||||
|
||||
To access FirebaseFirestore via a branch:
|
||||
```ruby
|
||||
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'main'
|
||||
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'main'
|
||||
```
|
||||
|
||||
To access FirebaseMessaging via a checked-out version of the firebase-ios-sdk repo:
|
||||
```ruby
|
||||
pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
|
||||
pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
|
||||
```
|
||||
|
||||
### Carthage (iOS only)
|
||||
|
||||
Instructions for the experimental Carthage distribution can be found at
|
||||
[Carthage.md](Carthage.md).
|
||||
|
||||
### Using Firebase from a Framework or a library
|
||||
|
||||
For details on using Firebase from a Framework or a library, refer to [firebase_in_libraries.md](docs/firebase_in_libraries.md).
|
||||
|
||||
## Development
|
||||
|
||||
To develop Firebase software in this repository, ensure that you have at least
|
||||
the following software:
|
||||
|
||||
* Xcode 15.2 (or later)
|
||||
|
||||
CocoaPods is still the canonical way to develop, but much of the repo now supports
|
||||
development with Swift Package Manager.
|
||||
|
||||
### CocoaPods
|
||||
|
||||
Install the following:
|
||||
* CocoaPods 1.12.0 (or later)
|
||||
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
|
||||
|
||||
For the pod that you want to develop:
|
||||
|
||||
```ruby
|
||||
pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios
|
||||
```
|
||||
|
||||
Note: If the CocoaPods cache is out of date, you may need to run
|
||||
`pod repo update` before the `pod gen` command.
|
||||
|
||||
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
|
||||
those platforms. Since 10.2, Xcode does not properly handle multi-platform
|
||||
CocoaPods workspaces.
|
||||
|
||||
Firestore has a self-contained Xcode project. See
|
||||
[Firestore/README](Firestore/README.md) Markdown file.
|
||||
|
||||
#### Development for Catalyst
|
||||
* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios`
|
||||
* Check the Mac box in the App-iOS Build Settings
|
||||
* Sign the App in the Settings Signing & Capabilities tab
|
||||
* Click Pods in the Project Manager
|
||||
* Add Signing to the iOS host app and unit test targets
|
||||
* Select the Unit-unit scheme
|
||||
* Run it to build and test
|
||||
|
||||
Alternatively, disable signing in each target:
|
||||
* Go to Build Settings tab
|
||||
* Click `+`
|
||||
* Select `Add User-Defined Setting`
|
||||
* Add `CODE_SIGNING_REQUIRED` setting with a value of `NO`
|
||||
|
||||
### Swift Package Manager
|
||||
* To enable test schemes: `./scripts/setup_spm_tests.sh`
|
||||
* `open Package.swift` or double click `Package.swift` in Finder.
|
||||
* Xcode will open the project
|
||||
* Choose a scheme for a library to build or test suite to run
|
||||
* Choose a target platform by selecting the run destination along with the scheme
|
||||
|
||||
### Adding a New Firebase Pod
|
||||
|
||||
Refer to [AddNewPod](AddNewPod.md) Markdown file for details.
|
||||
|
||||
### Managing Headers and Imports
|
||||
|
||||
For information about managing headers and imports, see [HeadersImports](HeadersImports.md) Markdown file.
|
||||
|
||||
### Code Formatting
|
||||
|
||||
To ensure that the code is formatted consistently, run the script
|
||||
[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh)
|
||||
before creating a pull request (PR).
|
||||
|
||||
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 mint
|
||||
```
|
||||
|
||||
### Running Unit Tests
|
||||
|
||||
Select a scheme and press Command-u to build a component and run its unit tests.
|
||||
|
||||
### Running Sample Apps
|
||||
To run the sample apps and integration tests, you'll need a valid
|
||||
`GoogleService-Info.plist
|
||||
` file. The Firebase Xcode project contains dummy plist
|
||||
files without real values, but they can be replaced with real plist files. To get your own
|
||||
`GoogleService-Info.plist` files:
|
||||
|
||||
1. Go to the [Firebase Console](https://console.firebase.google.com/)
|
||||
2. Create a new Firebase project, if you don't already have one
|
||||
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
|
||||
identifier (e.g., `com.google.Database-Example`)
|
||||
4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project.
|
||||
|
||||
### Coverage Report Generation
|
||||
|
||||
For coverage report generation instructions, see [scripts/code_coverage_report/README](scripts/code_coverage_report/README.md) Markdown file.
|
||||
|
||||
## Specific Component Instructions
|
||||
See the sections below for any special instructions for those components.
|
||||
|
||||
### Firebase Auth
|
||||
|
||||
For specific Firebase Auth development, refer to the [Auth Sample README](FirebaseAuth/Tests/Sample/README.md) for instructions about
|
||||
building and running the FirebaseAuth pod along with various samples and tests.
|
||||
|
||||
### Firebase Database
|
||||
|
||||
The Firebase Database Integration tests can be run against a locally running Database Emulator
|
||||
or against a production instance.
|
||||
|
||||
To run against a local emulator instance, invoke `./scripts/run_database_emulator.sh start` before
|
||||
running the integration test.
|
||||
|
||||
To run against a production instance, provide a valid `GoogleServices-Info.plist` and copy it to
|
||||
`FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to
|
||||
[public](https://firebase.google.com/docs/database/security/quickstart) while your tests are
|
||||
running.
|
||||
|
||||
### Firebase Dynamic Links
|
||||
|
||||
Firebase Dynamic Links is **deprecated** and should not be used in new projects. The service will shut down on August 25, 2025.
|
||||
|
||||
Please see our [Dynamic Links Deprecation FAQ documentation](https://firebase.google.com/support/dynamic-links-faq) for more guidance.
|
||||
|
||||
### Firebase Performance Monitoring
|
||||
|
||||
For specific Firebase Performance Monitoring development, see
|
||||
[the Performance README](FirebasePerformance/README.md) for instructions about building the SDK
|
||||
and [the Performance TestApp README](FirebasePerformance/Tests/TestApp/README.md) for instructions about
|
||||
integrating Performance with the dev test App.
|
||||
|
||||
### Firebase Storage
|
||||
|
||||
To run the Storage Integration tests, follow the instructions in
|
||||
[StorageIntegration.swift](FirebaseStorage/Tests/Integration/StorageIntegration.swift).
|
||||
|
||||
#### Push Notifications
|
||||
|
||||
Push notifications can only be delivered to specially provisioned App IDs in the developer portal.
|
||||
In order to test receiving push notifications, you will need to:
|
||||
|
||||
1. Change the bundle identifier of the sample app to something you own in your Apple Developer
|
||||
account and enable that App ID for push notifications.
|
||||
2. You'll also need to
|
||||
[upload your APNs Provider Authentication Key or certificate to the
|
||||
Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs)
|
||||
at **Project Settings > Cloud Messaging > [Your Firebase App]**.
|
||||
3. Ensure your iOS device is added to your Apple Developer portal as a test device.
|
||||
|
||||
#### iOS Simulator
|
||||
|
||||
The iOS Simulator cannot register for remote notifications and will not receive push notifications.
|
||||
To receive push notifications, follow the steps above and run the app on a physical device.
|
||||
|
||||
### Vertex AI for Firebase
|
||||
|
||||
See the [Vertex AI for Firebase README](FirebaseVertexAI#development) for
|
||||
instructions about building and testing the SDK.
|
||||
|
||||
## Building with Firebase on Apple platforms
|
||||
|
||||
Firebase provides official beta support for macOS, Catalyst, and tvOS. visionOS and watchOS
|
||||
are community supported. Thanks to community contributions for many of the multi-platform PRs.
|
||||
|
||||
At this time, most of Firebase's products are available across Apple platforms. There are still
|
||||
a few gaps, especially on visionOS and watchOS. For details about the current support matrix, see
|
||||
[this chart](https://firebase.google.com/docs/ios/learn-more#firebase_library_support_by_platform)
|
||||
in Firebase's documentation.
|
||||
|
||||
### visionOS
|
||||
|
||||
Where supported, visionOS works as expected with the exception of Firestore via Swift Package
|
||||
Manager where it is required to use the source distribution.
|
||||
|
||||
To enable the Firestore source distribution, quit Xcode and open the desired
|
||||
project from the command line with the `FIREBASE_SOURCE_FIRESTORE` environment
|
||||
variable: `open --env FIREBASE_SOURCE_FIRESTORE /path/to/project.xcodeproj`.
|
||||
To go back to using the binary distribution of Firestore, quit Xcode and open
|
||||
Xcode like normal, without the environment variable.
|
||||
|
||||
### watchOS
|
||||
Thanks to contributions from the community, many of Firebase SDKs now compile, run unit tests, and
|
||||
work on watchOS. See the [Independent Watch App Sample](Example/watchOSSample).
|
||||
|
||||
Keep in mind that watchOS is not officially supported by Firebase. While we can catch basic unit
|
||||
test issues with GitHub Actions, there may be some changes where the SDK no longer works as expected
|
||||
on watchOS. If you encounter this, please
|
||||
[file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
|
||||
|
||||
During app setup in the console, you may get to a step that mentions something like "Checking if the
|
||||
app has communicated with our servers". This relies on Analytics and will not work on watchOS.
|
||||
**It's safe to ignore the message and continue**, the rest of the SDKs will work as expected.
|
||||
|
||||
#### Additional Crashlytics Notes
|
||||
* watchOS has limited support. Due to watchOS restrictions, mach exceptions and signal crashes are
|
||||
not recorded. (Crashes in SwiftUI are generated as mach exceptions, so will not be recorded)
|
||||
|
||||
## Combine
|
||||
Thanks to contributions from the community, _FirebaseCombineSwift_ contains support for Apple's Combine
|
||||
framework. This module is currently under development and not yet supported for use in production
|
||||
environments. For more details, please refer to the [docs](FirebaseCombineSwift/README.md).
|
||||
|
||||
## Roadmap
|
||||
|
||||
See [Roadmap](ROADMAP.md) for more about the Firebase Apple SDK Open Source
|
||||
plans and directions.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
|
||||
Apple SDK.
|
||||
|
||||
## License
|
||||
|
||||
The contents of this repository are licensed under the
|
||||
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
Your use of Firebase is governed by the
|
||||
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).
|
||||
Reference in New Issue
Block a user