Skip to content

Commit

Permalink
[MT-1563] Make firebase configuration threadsafe (#18)
Browse files Browse the repository at this point in the history
* Make firebase configuration threadsafe

Both Firebase configuration and isConfigured check happen on the main thread to avoid race conditions

* Version 3.3.0

* Make scripts executable
  • Loading branch information
Enricoza authored Aug 26, 2024
1 parent 79369a0 commit ed24946
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 44 deletions.
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAnalyticsBinary.json" "10.7.0"
github "tealium/tealium-swift" "2.11.1"
github "tealium/tealium-swift" "2.13.0"
2 changes: 1 addition & 1 deletion Sources/FirebaseConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum FirebaseConstants {
static let commandId = "firebaseAnalytics"
static let description = "Firebase Remote Command"
static let errorPrefix = "Tealium Firebase: "
static let version = "3.2.0"
static let version = "3.3.0"

enum Keys {
static let sessionTimeout = "firebase_session_timeout_seconds"
Expand Down
41 changes: 27 additions & 14 deletions Sources/FirebaseInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,34 @@ public protocol FirebaseCommand {
func setConsent(_ consentSettings: [String: String])
}

/// A simple wrapper around the Firebase API. All public methods are expected to be called on the `TealiumQueues.backgroundSerialQueue`
public class FirebaseInstance: FirebaseCommand {

public init() { }

private var _onReady = TealiumReplaySubject<Void>(cacheSize: 1)
private var onReadySubject = TealiumReplaySubject<Void>(cacheSize: 1)

/// Must be called on the main queue
var isConfigured: Bool {
// Analytics is only logged on default instance
FirebaseApp.app() != nil
}
/// Waits for the default FirebaseApp to be configured for the first time and then calls the completion block. If the default app gets deleted later this won't wait anymore.
public func onReady(_ onReady: @escaping () -> Void) {
guard !isConfigured else {
if _onReady.last() == nil {
_onReady.publish()
}
onReady()
onReadySubject.subscribeOnce(onReady)
guard self.onReadySubject.last() == nil else {
return
}
_onReady.subscribeOnce(onReady)
DispatchQueue.main.async {
guard self.isConfigured else {
return
}
TealiumQueues.backgroundSerialQueue.async {
if self.onReadySubject.last() == nil {
self.onReadySubject.publish()
}
}
}
}

public func createAnalyticsConfig(_ sessionTimeoutSeconds: TimeInterval?,
Expand All @@ -63,15 +71,20 @@ public class FirebaseInstance: FirebaseCommand {
Analytics.setAnalyticsCollectionEnabled(analyticsEnabled)
}
FirebaseConfiguration.shared.setLoggerLevel(logLevel)
if !isConfigured {
DispatchQueue.main.async {
FirebaseApp.configure()
TealiumQueues.backgroundSerialQueue.async {
self._onReady.publish()
DispatchQueue.main.async {
self.configure()
TealiumQueues.backgroundSerialQueue.async {
if self.onReadySubject.last() == nil {
self.onReadySubject.publish()
}
}
} else {
_onReady.publish()
}
}

/// Must be called on the main queue
func configure() {
if !self.isConfigured {
FirebaseApp.configure()
}
}

Expand Down
6 changes: 3 additions & 3 deletions TealiumFirebase.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Pod::Spec.new do |s|
# ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
s.name = "TealiumFirebase"
s.module_name = "TealiumFirebase"
s.version = "3.2.0"
s.version = "3.3.0"
s.summary = "Tealium Swift and Firebase integration"
s.description = <<-DESC
Tealium's integration with Firebase for iOS.
Expand All @@ -30,8 +30,8 @@ Pod::Spec.new do |s|

# ――― Dependencies ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
s.static_framework = true
s.ios.dependency 'tealium-swift/Core', '~> 2.12'
s.ios.dependency 'tealium-swift/RemoteCommands', '~> 2.12'
s.ios.dependency 'tealium-swift/Core', '~> 2.13'
s.ios.dependency 'tealium-swift/RemoteCommands', '~> 2.13'
s.dependency 'Firebase', '~> 10.7'
s.dependency 'FirebaseAnalytics', '~> 10.7'
end
6 changes: 3 additions & 3 deletions TealiumFirebase.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 60;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -84,8 +84,8 @@
15913F292779CCFF0051BCB2 /* FirebaseCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = FirebaseCore.xcframework; path = Carthage/Build/FirebaseCore.xcframework; sourceTree = "<group>"; };
15913F2B2779CCFF0051BCB2 /* FirebaseInstallations.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = FirebaseInstallations.xcframework; path = Carthage/Build/FirebaseInstallations.xcframework; sourceTree = "<group>"; };
15913F2C2779CCFF0051BCB2 /* GoogleUtilities.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GoogleUtilities.xcframework; path = Carthage/Build/GoogleUtilities.xcframework; sourceTree = "<group>"; };
15913F372779CD070051BCB2 /* TealiumRemoteCommands.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = TealiumRemoteCommands.xcframework; path = Carthage/Build/TealiumRemoteCommands.xcframework; sourceTree = "<group>"; };
15913F3D2779CD070051BCB2 /* TealiumCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = TealiumCore.xcframework; path = Carthage/Build/TealiumCore.xcframework; sourceTree = "<group>"; };
15913F372779CD070051BCB2 /* TealiumRemoteCommands.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:XC939GDC9P:Tealium"; lastKnownFileType = wrapper.xcframework; name = TealiumRemoteCommands.xcframework; path = Carthage/Build/TealiumRemoteCommands.xcframework; sourceTree = "<group>"; };
15913F3D2779CD070051BCB2 /* TealiumCore.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:XC939GDC9P:Tealium"; lastKnownFileType = wrapper.xcframework; name = TealiumCore.xcframework; path = Carthage/Build/TealiumCore.xcframework; sourceTree = "<group>"; };
15913F402779CD080051BCB2 /* nanopb.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = nanopb.xcframework; path = Carthage/Build/nanopb.xcframework; sourceTree = "<group>"; };
15914AE4277A2BD80051BCB2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
15914AE7277A2BE10051BCB2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?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/>
</plist>
18 changes: 9 additions & 9 deletions TealiumFirebaseExample/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,18 @@ PODS:
- nanopb/decode (2.30909.1)
- nanopb/encode (2.30909.1)
- PromisesObjC (2.3.1)
- tealium-swift/Core (2.12.2)
- tealium-swift/Lifecycle (2.12.2):
- tealium-swift/Core (2.13.0)
- tealium-swift/Lifecycle (2.13.0):
- tealium-swift/Core
- tealium-swift/RemoteCommands (2.12.2):
- tealium-swift/RemoteCommands (2.13.0):
- tealium-swift/Core
- tealium-swift/TagManagement (2.12.2):
- tealium-swift/TagManagement (2.13.0):
- tealium-swift/Core
- TealiumFirebase (3.2.0):
- TealiumFirebase (3.3.0):
- Firebase (~> 10.7)
- FirebaseAnalytics (~> 10.7)
- tealium-swift/Core (~> 2.12)
- tealium-swift/RemoteCommands (~> 2.12)
- tealium-swift/Core (~> 2.13)
- tealium-swift/RemoteCommands (~> 2.13)

DEPENDENCIES:
- tealium-swift/Lifecycle
Expand Down Expand Up @@ -125,8 +125,8 @@ SPEC CHECKSUMS:
GoogleUtilities: 202e7a9f5128accd11160fb9c19612de1911aa19
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
tealium-swift: 4b7e3dda42d7c1de6acb769abad9920346d17d43
TealiumFirebase: b489c74d3a1d7d81ad806e4da4638be6ea9870b9
tealium-swift: f2a1d022bae58151c7ff31d2a063008cfe6463d8
TealiumFirebase: 2afc11e00415f232672e3095e8dbd32c5af52fdc

PODFILE CHECKSUM: a4e910fa3529d9d8e69f4558a9a644ea2b7341b5

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class TravelViewController: UIViewController {
}

@objc func share() {
TealiumHelper.trackEvent(title: "share", data: [TravelViewController.contentType: "travel screen", TravelViewController.shareId: "traqwe123", "event_title": EventNames.lookup["share"]])
TealiumHelper.trackEvent(title: "share", data: [TravelViewController.contentType: "travel screen", TravelViewController.shareId: "traqwe123", "event_title": EventNames.lookup["share"] as Any])
let vc = UIActivityViewController(activityItems: ["Travel"], applicationActivities: [])
vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
present(vc, animated: true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"product_brand": "items.param_item_brand",
"product_category": "items.param_item_category",
"product_id": "items.param_item_id",
"tealium_event": "items.param_item_custom_id",
"product_unit_price": "items.param_price",
"product_quantity": "items.param_quantity",
"product_list": "items.param_item_list",
Expand Down
24 changes: 24 additions & 0 deletions Tests/FirebaseInstanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import XCTest
import TealiumCore
@testable import TealiumFirebase
import TealiumRemoteCommands

Expand Down Expand Up @@ -43,6 +44,29 @@ class FirebaseInstanceTests: XCTestCase {
firebaseCommand.processRemoteCommand(with: payload)
XCTAssertEqual(0, firebaseInstance.createAnalyticsConfigCallCount)
}

func testCreateAnalyticsForcesOnReadyToBeCalled() {
let onReadyCalled = expectation(description: "On Ready is called")
firebaseInstance.onReady {
XCTAssertTrue(self.firebaseInstance._isConfigured)
dispatchPrecondition(condition: .onQueue(TealiumQueues.backgroundSerialQueue))
onReadyCalled.fulfill()
}
let payload: [String: Any] = ["command_name": "config"]
firebaseCommand.processRemoteCommand(with: payload)
XCTAssertEqual(1, firebaseInstance.createAnalyticsConfigCallCount)
waitForExpectations(timeout: 1.0)
}

func testOnReadyCalledImmmediatelyIfAlreadyConfigured() {
let onReadyCalled = expectation(description: "On Ready is called")
firebaseInstance.configure()
firebaseInstance.onReady {
dispatchPrecondition(condition: .onQueue(TealiumQueues.backgroundSerialQueue))
onReadyCalled.fulfill()
}
waitForExpectations(timeout: 1.0)
}

func testLogEventWithParams() {
let payload: [String: Any] = ["command_name": "logevent", "firebase_event_name": "event_add_to_cart", "firebase_event_params":
Expand Down
34 changes: 23 additions & 11 deletions Tests/MockFirebaseInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@

import Foundation
import FirebaseCore
import TealiumCore
@testable import TealiumFirebase
import TealiumRemoteCommands


class MockFirebaseInstance: FirebaseCommand {
func onReady(_ onReady: @escaping () -> Void) {
onReady()
class MockFirebaseInstance: FirebaseInstance {
var _isConfigured = false
override var isConfigured: Bool {
get {
dispatchPrecondition(condition: .onQueue(.main))
return _isConfigured
}


}

var createAnalyticsConfigCallCount = 0
Expand All @@ -35,35 +42,40 @@ class MockFirebaseInstance: FirebaseCommand {

var consentSettings: [String: String]?

func createAnalyticsConfig(_ sessionTimeoutSeconds: TimeInterval?, _ minimumSessionSeconds: TimeInterval?, _ analyticsEnabled: Bool?, _ logLevel: FirebaseLoggerLevel) {
override func configure() {
dispatchPrecondition(condition: .onQueue(.main))
_isConfigured = true
}
override func createAnalyticsConfig(_ sessionTimeoutSeconds: TimeInterval?, _ minimumSessionSeconds: TimeInterval?, _ analyticsEnabled: Bool?, _ logLevel: FirebaseLoggerLevel) {
createAnalyticsConfigCallCount += 1
super.createAnalyticsConfig(sessionTimeoutSeconds, minimumSessionSeconds, analyticsEnabled, logLevel)
}

func logEvent(_ name: String, _ params: [String : Any]?) {
override func logEvent(_ name: String, _ params: [String : Any]?) {
logEventWithParamsCallCount += 1
}

func setScreenName(_ screenName: String, _ screenClass: String?) {
override func setScreenName(_ screenName: String, _ screenClass: String?) {
setScreenNameCallCount += 1
}

func setUserProperty(_ property: String, value: String) {
override func setUserProperty(_ property: String, value: String) {
setUserPropertyCallCount += 1
}

func setUserId(_ id: String) {
override func setUserId(_ id: String) {
setUserIdCallCount += 1
}

func initiateOnDeviceConversionMeasurement(emailAddress: String) {
override func initiateOnDeviceConversionMeasurement(emailAddress: String) {
initateConversionCount += 1
}

func setDefaultEventParameters(parameters: [String : Any]?) {
override func setDefaultEventParameters(parameters: [String : Any]?) {
defaultParameters = parameters
}

func setConsent(_ consentSettings: [String : String]) {
override func setConsent(_ consentSettings: [String : String]) {
self.consentSettings = consentSettings
}
}
1 change: 1 addition & 0 deletions publish.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/bin/bash
# A script to verify that the repo is up to date and the versions are correct and then runs the pod trunk push command

constants=$(<Sources/FirebaseConstants.swift)
Expand Down
Empty file modified xcframeworks.sh
100644 → 100755
Empty file.

0 comments on commit ed24946

Please sign in to comment.