Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion FlagShip.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = "FlagShip"
s.version = "5.0.0-beta.6"
s.version = "5.1.0-beta.1"
s.summary = "Flagship SDK"

# This description is used to generate tags and improve search results.
Expand Down
3 changes: 1 addition & 2 deletions FlagShip/FlagShipTests/FSEmotion/FSettingsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ final class FSettingsTest: XCTestCase {

XCTAssertEqual(score, "Immediacy")
expectationSync.fulfill()

}
wait(for: [expectationSync], timeout: 5.0)
wait(for: [expectationSync], timeout: 6.0)
}
}
4 changes: 2 additions & 2 deletions FlagShip/Flagship.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1880,7 +1880,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = "5.0.0-beta.6";
MARKETING_VERSION = "5.1.0-beta.1";
OTHER_LDFLAGS = "";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = ABTasty.FlagShip;
Expand Down Expand Up @@ -1914,7 +1914,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = "5.0.0-beta.6";
MARKETING_VERSION = "5.1.0-beta.1";
OTHER_LDFLAGS = "";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = ABTasty.FlagShip;
Expand Down
4 changes: 4 additions & 0 deletions FlagShip/Source/Cache/FSCacheDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import Foundation
/// Called when a visitor set consent to false. Must erase visitor data related to the given visitor
/// Id from the database.
func flushVisitor(visitorId: String)

/// Called to check if the visitor data is already in the database
@objc optional
func isVisitorCacheExist(visitorId: String) -> Bool
}

@objc public protocol FSHitCacheDelegate {
Expand Down
28 changes: 24 additions & 4 deletions FlagShip/Source/Cache/FSCacheManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ import Foundation
}

/// Cache the visitor
///
///

/// Check if visitor cache exists for the given visitor ID
/// - Parameter visitorId: The visitor identifier to check
/// - Returns: `true` if cache exists, `false` otherwise (including if delegate is not set)
func isVisitorCacheExist(_ visitorId: String) -> Bool {
guard let visitorDelegate = cacheVisitorDelegate else {
FlagshipLogManager.Log(
level: .WARNING,
tag: .STORAGE,
messageToDisplay: FSLogMessage.MESSAGE("Cache delegate is not set")
)
return false
}

// Call optional protocol method with safe unwrapping
return visitorDelegate.isVisitorCacheExist?(visitorId: visitorId) ?? false
}

/// - Parameter visitor: visitor instance
func cacheVisitor(_ visitor: FSVisitor) {
/// Create visitor cache object
Expand All @@ -58,7 +78,7 @@ import Foundation
/// - Parameters:
/// - visitoId: id of the visitor
/// - onCompletion: callback ob finishing the job
public func lookupVisitorCache(visitoId: String, onCompletion: @escaping (Error?, FSCacheVisitor?)->Void) {
public func lookupVisitorCache(visitoId: String, onCompletion: @escaping (FlagshipError?, FSCacheVisitor?) -> Void) {
/// Create a thread
let fsCacheQueue = DispatchQueue(label: "com.flagshipCache.queue", attributes: .concurrent)
/// Init the semaphore
Expand All @@ -72,10 +92,10 @@ import Foundation
let result = try JSONDecoder().decode(FSCacheVisitor.self, from: dataJson)
onCompletion(nil, result)
} catch {
onCompletion(error, nil)
onCompletion(FlagshipError(message: "Error on decode visitor data from cache", type: .internalError, code: 400), nil)
}
} else {
onCompletion(FlagshipError(type: .internalError, code: 400), nil)
onCompletion(FlagshipError(message: "The visitorId \(visitoId) not found in cache", type: .internalError, code: 404), nil)
}
semaphore.signal()
}
Expand All @@ -102,7 +122,7 @@ import Foundation
hitCacheDelegate?.cacheHits(hits: hits)
}

func lookupHits(onCompletion: @escaping (Error?, [FSTrackingProtocol]?)->Void) {
func lookupHits(onCompletion: @escaping (Error?, [FSTrackingProtocol]?) -> Void) {
/// Create a Thread
let fsHitCacheQueue = DispatchQueue(label: "com.flagshipLookupHitCache.queue", attributes: .concurrent)
/// Init the semaphore
Expand Down
4 changes: 4 additions & 0 deletions FlagShip/Source/Cache/FSDefaultCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public class FSDefaultCacheVisitor: FSVisitorCacheDelegate {
public func flushVisitor(visitorId: String) {
dbMgt_visitor.delete(idItemToDelete: visitorId)
}

public func isVisitorCacheExist(visitorId: String) -> Bool {
return dbMgt_visitor.isVisitorExist(visitorId)
}
}

////////////////////////////||
Expand Down
37 changes: 29 additions & 8 deletions FlagShip/Source/Core/FSVisitor+Reconcilliation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ public extension FSVisitor {
/// - Important: After using this method, you should use Flagship.fetchFlags method to update the visitor informations
/// - Requires: Make sure that the experience continuity option is enabled on the flagship platform before using this method
@objc func authenticate(visitorId: String) {
if configManager.flagshipConfig.mode != .DECISION_API {
FlagshipLogManager.Log(level: .ALL, tag: .AUTHENTICATE, messageToDisplay: FSLogMessage.IGNORE_AUTHENTICATE)
return
}
self.strategy?.getStrategy().authenticateVisitor(visitorId: visitorId)
self.updateStateAndTriggerCallback(true)
// Troubleshooting xpc
Expand All @@ -27,10 +23,6 @@ public extension FSVisitor {

/// Use authenticate methode to go from Logged in session to logged out session
@objc func unauthenticate() {
if configManager.flagshipConfig.mode != .DECISION_API {
FlagshipLogManager.Log(level: .ALL, tag: .UNAUTHENTICATE, messageToDisplay: FSLogMessage.IGNORE_AUTHENTICATE)
return
}
self.strategy?.getStrategy().unAuthenticateVisitor()
self.updateStateAndTriggerCallback(false)
// Troubleshooting xpc
Expand All @@ -43,4 +35,33 @@ public extension FSVisitor {
// Set the fetch state to required state
self.fetchStatus = .FETCH_REQUIRED
}

// Copy method actually used only in bucketing mode - that explain why we put here in this extension
func copy() -> FSVisitor {
let copiedVisitor = FSVisitor(
aVisitorId: self.visitorId,
aContext: self.context.getCurrentContext(),
aConfigManager: self.configManager,
aHasConsented: self.hasConsented,
aIsAuthenticated: self.isAuthenticated,
pOnFlagStatusChanged: self._onFlagStatusChanged,
pOnFlagStatusFetchRequired: self._onFlagStatusFetchRequired,
pOnFlagStatusFetched: self._onFlagStatusFetched
)

// Copy additional properties
copiedVisitor.anonymousId = self.anonymousId
copiedVisitor.currentFlags = self.currentFlags
copiedVisitor.assignedVariationHistory = self.assignedVariationHistory
copiedVisitor.requiredFetchReason = self.requiredFetchReason
copiedVisitor.eaiVisitorScored = self.eaiVisitorScored
copiedVisitor.emotionScoreAI = self.emotionScoreAI
copiedVisitor.fetchStatus = self.fetchStatus

// Copy strategy if needed
if let strategy = self.strategy {
copiedVisitor.strategy = FSStrategy(copiedVisitor)
}
return copiedVisitor
}
}
15 changes: 15 additions & 0 deletions FlagShip/Source/Core/FSVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ import Foundation

// Go to ING state while the fetch is ongoing
self.fetchStatus = .FETCHING

/// Look for the visitor in local storage
self.strategy?.getStrategy().lookupVisitor()

// Synchronize the visitor
self.strategy?.getStrategy().synchronize(onSyncCompleted: { state, reason in

// After the synchronize completion we cache the visitor
Expand All @@ -154,6 +159,16 @@ import Foundation
self.sendHit(FSSegment(self.getContext()))
self.context.needToUpload = false
}

// Another task for bucketing in xpc mode is to save the anonymous when has no cache

if let ano = self.anonymousId {
if !self.configManager.flagshipConfig.cacheManager.isVisitorCacheExist(ano) {
let anoVisitor: FSVisitor = self.copy()
anoVisitor.visitorId = ano
self.configManager.flagshipConfig.cacheManager.cacheVisitor(anoVisitor)
}
}
}
// Update the reason status
self.requiredFetchReason = reason
Expand Down
6 changes: 1 addition & 5 deletions FlagShip/Source/Core/Flagship.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,7 @@ public class Flagship: NSObject {
newVisitor.strategy = FSStrategy(newVisitor)

if hasConsented {
// Read the cached visitor
newVisitor.strategy?.getStrategy().lookupVisitor()
// Read the cacheed hits from data base
newVisitor.strategy?.getStrategy().lookupHits()

newVisitor.strategy?.getStrategy().lookupHits()
} else {
// user not consent then flush the cache related
newVisitor.strategy?.getStrategy().flushVisitor()
Expand Down
2 changes: 1 addition & 1 deletion FlagShip/Source/Logger/FSError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ enum ErrorType {
}

// Flagship Error
class FlagshipError: Error {
public class FlagshipError: Error {
var message = ""
var error: ErrorType
let codeError: Int
Expand Down
34 changes: 34 additions & 0 deletions FlagShip/Source/Storage/FSVisitorDbMgt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import Foundation
import SQLite3

class FSVisitorDbMgt: FSQLiteWrapper {
// Add this constant at the class level
private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)

public init() {
super.init(.DatabaseVisitor)
}
Expand Down Expand Up @@ -54,4 +57,35 @@ class FSVisitorDbMgt: FSQLiteWrapper {
}
return nil
}

// Add this function to FSVisitorDbMgt class
public func isVisitorExist(_ visitorId: String) -> Bool {
var queryPointer: OpaquePointer?
let queryStatementString = "SELECT COUNT(*) FROM table_visitors WHERE id = ?;"

// Prepare the query with parameter binding for safety
guard sqlite3_prepare_v2(db_opaquePointer, queryStatementString, -1, &queryPointer, nil) == SQLITE_OK else {
FlagshipLogManager.Log(level: .ERROR, tag: .STORAGE, messageToDisplay: FSLogMessage.MESSAGE("sqlite3 prepare error"))
return false
}

defer {
// Always cleanup the prepared statement
sqlite3_finalize(queryPointer)
}

// Bind the visitor ID parameter (safer than string interpolation)
guard sqlite3_bind_text(queryPointer, 1, (visitorId as NSString).utf8String, -1, SQLITE_TRANSIENT) == SQLITE_OK else {
FlagshipLogManager.Log(level: .ERROR, tag: .STORAGE, messageToDisplay: FSLogMessage.MESSAGE("sqlite3 binding error"))
return false
}

// Execute the query and get the count
if sqlite3_step(queryPointer) == SQLITE_ROW {
let count = sqlite3_column_int(queryPointer, 0)
return count > 0
}

return false
}
}
Loading