| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- // Copyright 2022 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
- // Avoids exposing internal FirebaseCore APIs to Swift users.
- @_implementationOnly import FirebaseCoreExtension
- @_implementationOnly import FirebaseInstallations
- @_implementationOnly import GoogleDataTransport
- @_implementationOnly import Promises
- private enum GoogleDataTransportConfig {
- static let sessionsLogSource = "1974"
- static let sessionsTarget = GDTCORTarget.FLL
- }
- @objc(FIRSessions) final class Sessions: NSObject, Library, SessionsProvider {
- // MARK: - Private Variables
- /// The Firebase App ID associated with Sessions.
- private let appID: String
- /// Top-level Classes in the Sessions SDK
- private let coordinator: SessionCoordinator
- private let initiator: SessionInitiator
- private let sessionGenerator: SessionGenerator
- private let appInfo: ApplicationInfo
- private let settings: SessionsSettings
- /// Subscribers
- /// `subscribers` are used to determine the Data Collection state of the Sessions SDK.
- /// If any Subscribers has Data Collection enabled, the Sessions SDK will send events
- private var subscribers: [SessionsSubscriber] = []
- /// `subscriberPromises` are used to wait until all Subscribers have registered
- /// themselves. Subscribers must have Data Collection state available upon registering.
- private var subscriberPromises: [SessionsSubscriberName: Promise<Void>] = [:]
- /// Notifications
- static let SessionIDChangedNotificationName = Notification
- .Name("SessionIDChangedNotificationName")
- let notificationCenter = NotificationCenter()
- // MARK: - Initializers
- // Initializes the SDK and top-level classes
- required convenience init(appID: String, installations: InstallationsProtocol) {
- let googleDataTransport = GDTCORTransport(
- mappingID: GoogleDataTransportConfig.sessionsLogSource,
- transformers: nil,
- target: GoogleDataTransportConfig.sessionsTarget
- )
- let fireLogger = EventGDTLogger(googleDataTransport: googleDataTransport!)
- let appInfo = ApplicationInfo(appID: appID)
- let settings = SessionsSettings(
- appInfo: appInfo,
- installations: installations
- )
- let sessionGenerator = SessionGenerator(settings: settings)
- let coordinator = SessionCoordinator(
- installations: installations,
- fireLogger: fireLogger
- )
- let initiator = SessionInitiator(settings: settings)
- self.init(appID: appID,
- sessionGenerator: sessionGenerator,
- coordinator: coordinator,
- initiator: initiator,
- appInfo: appInfo,
- settings: settings)
- }
- // Initializes the SDK and begines the process of listening for lifecycle events and logging events
- init(appID: String, sessionGenerator: SessionGenerator, coordinator: SessionCoordinator,
- initiator: SessionInitiator, appInfo: ApplicationInfo, settings: SessionsSettings) {
- self.appID = appID
- self.sessionGenerator = sessionGenerator
- self.coordinator = coordinator
- self.initiator = initiator
- self.appInfo = appInfo
- self.settings = settings
- super.init()
- SessionsDependencies.dependencies.forEach { subscriberName in
- self.subscriberPromises[subscriberName] = Promise<Void>.pending()
- }
- Logger.logDebug("Expecting subscriptions from: \(SessionsDependencies.dependencies)")
- self.initiator.beginListening {
- // Generating a Session ID early is important as Subscriber
- // SDKs will need to read it immediately upon registration.
- let sessionInfo = self.sessionGenerator.generateNewSession()
- // Post a notification so subscriber SDKs can get an updated Session ID
- self.notificationCenter.post(name: Sessions.SessionIDChangedNotificationName,
- object: nil)
- let event = SessionStartEvent(sessionInfo: sessionInfo, appInfo: self.appInfo)
- // Wait until all subscriber promises have been fulfilled before
- // doing any data collection.
- all(self.subscriberPromises.values).then(on: .global(qos: .background)) { _ in
- guard self.isAnyDataCollectionEnabled else {
- Logger
- .logDebug(
- "Data Collection is disabled for all subscribers. Skipping this Session Event"
- )
- return
- }
- Logger.logDebug("Data Collection is enabled for at least one Subscriber")
- // Fetch settings if they have expired. This must happen after the check for
- // data collection because it uses the network, but it must happen before the
- // check for sessionsEnabled from Settings because otherwise we would permanently
- // turn off the Sessions SDK when we disabled it.
- self.settings.updateSettings()
- self.addEventDataCollectionState(event: event)
- if !(self.settings.sessionsEnabled && sessionInfo.shouldDispatchEvents) {
- Logger
- .logDebug(
- "Session Event logging is disabled sessionsEnabled: \(self.settings.sessionsEnabled), shouldDispatchEvents: \(sessionInfo.shouldDispatchEvents)"
- )
- return
- }
- self.coordinator.attemptLoggingSessionStart(event: event) { result in
- }
- }
- }
- }
- // MARK: - Data Collection
- var isAnyDataCollectionEnabled: Bool {
- for subscriber in subscribers {
- if subscriber.isDataCollectionEnabled {
- return true
- }
- }
- return false
- }
- func addEventDataCollectionState(event: SessionStartEvent) {
- subscribers.forEach { subscriber in
- event.set(subscriber: subscriber.sessionsSubscriberName,
- isDataCollectionEnabled: subscriber.isDataCollectionEnabled)
- }
- }
- // MARK: - SessionsProvider
- var currentSessionDetails: SessionDetails {
- return SessionDetails(sessionId: sessionGenerator.currentSession?.sessionId)
- }
- func register(subscriber: SessionsSubscriber) {
- Logger
- .logDebug(
- "Registering Sessions SDK subscriber with name: \(subscriber.sessionsSubscriberName), data collection enabled: \(subscriber.isDataCollectionEnabled)"
- )
- notificationCenter.addObserver(
- forName: Sessions.SessionIDChangedNotificationName,
- object: nil,
- queue: nil
- ) { notification in
- subscriber.onSessionChanged(self.currentSessionDetails)
- }
- // Immediately call the callback because the Sessions SDK starts
- // before subscribers, so subscribers will miss the first Notification
- subscriber.onSessionChanged(currentSessionDetails)
- // Fulfil this subscriber's promise
- subscribers.append(subscriber)
- subscriberPromises[subscriber.sessionsSubscriberName]?.fulfill(())
- }
- // MARK: - Library conformance
- static func componentsToRegister() -> [Component] {
- return [Component(SessionsProvider.self,
- instantiationTiming: .alwaysEager,
- dependencies: []) { container, isCacheable in
- // Sessions SDK only works for the default app
- guard let app = container.app, app.isDefaultApp else { return nil }
- isCacheable.pointee = true
- let installations = Installations.installations(app: app)
- return self.init(appID: app.options.googleAppID, installations: installations)
- }]
- }
- }
|