Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
26 changes: 23 additions & 3 deletions FlagShip/FlagShipTests/FSOnExposureTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ final class FSOnExposureTest: XCTestCase {
let variation = FSVariation(idVariation: "varId", variationName: "varName", nil, isReference: false)
let modif = FSModification(aCampaign: camp, aVariation: variation, valueForFlag: "flagVlaue")
let mettdata = FSFlagMetadata(modif)
let flagTest = FSExposedFlag(key: "keyFlag", defaultValue: "dfl", metadata: mettdata, value: "flagVlaue")
let flagTest = FSExposedFlag(key: "keyFlag", defaultValue: "dfl", metadata: mettdata, value: "flagVlaue", alreadyActivatedCampaign: true)

XCTAssertTrue(flagTest.toDictionary()["value"] as? String == "flagVlaue")
XCTAssertTrue(flagTest.toDictionary()["key"] as? String == "keyFlag")
XCTAssertTrue(flagTest.metadata.campaignId == "campId")
XCTAssertTrue(flagTest.toJson()?.count ?? 0 > 0)
XCTAssertTrue(flagTest.toDictionary()["alreadyActivatedCampaign"] as? Bool == true)

XCTAssertTrue(flagTest.toJson()?.length ?? 0 > 0)
}

// Test Visitor Object
Expand All @@ -92,6 +94,24 @@ final class FSOnExposureTest: XCTestCase {
XCTAssertTrue(visitorObject.toDictionary()["id"] as? String == "testId")
XCTAssertTrue(visitorObject.toDictionary()["anonymousId"] as? String == "ano1")
XCTAssertTrue((visitorObject.toDictionary()["context"] as? [String: Any])?["key1"] as? String == "val1")
XCTAssertTrue(visitorObject.toJson()?.count ?? 0 > 0)
XCTAssertTrue(visitorObject.toJson()?.length ?? 0 > 0)
}

func testDeduplication() {
let expectationSync = XCTestExpectation(description: "Service-deduplication")
testVisitor?.fetchFlags(onFetchCompleted: {
if let flag = self.testVisitor?.getFlag(key: "btnTitle") {
XCTAssertTrue(flag.value(defaultValue: "dfl") == "Alpha_demoApp")
// Activate again
XCTAssertTrue(flag.value(defaultValue: "dfl") == "Alpha_demoApp")
// Check deduplication
XCTAssertTrue(self.testVisitor?.isDeduplicatedFlag(campId: "bvcdqksmicqghldq9agg", varGrpId: "bvcdqksmicqghldq9ahg") ?? false)
// Send hit to reset timer
self.testVisitor?.sendHit(FSScreen("testLocatoin"))
XCTAssertTrue(self.testVisitor?.isDeduplicatedFlag(campId: "bvcdqksmicqghldq9agg", varGrpId: "bvcdqksmicqghldq9ahg") ?? false)
}
expectationSync.fulfill()
})
wait(for: [expectationSync], timeout: 5.0)
}
}
23 changes: 20 additions & 3 deletions FlagShip/Source/Core/FSExposedFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ protocol IFlag {

// Get metadata
var metadata: FSFlagMetadata { get set }

// Already activated campaign

var alreadyActivatedCampaign: Bool? { get }
}

@objc public class FSExposedFlag: NSObject, IFlag {
Expand All @@ -32,18 +36,23 @@ protocol IFlag {
// Value for flag
public private(set) var value: Any?

init(key: String, defaultValue: Any? = nil, metadata: FSFlagMetadata, value: Any?) {
// Already activated campaign
public var alreadyActivatedCampaign: Bool? = true

init(key: String, defaultValue: Any? = nil, metadata: FSFlagMetadata, value: Any?, alreadyActivatedCampaign: Bool = false) {
self.key = key
self.defaultValue = defaultValue
self.metadata = metadata
self.value = value
self.alreadyActivatedCampaign = alreadyActivatedCampaign
}

init(expoedInfo: [String: Any]) {
self.key = expoedInfo["key"] as? String ?? ""
self.value = expoedInfo["value"]
self.defaultValue = expoedInfo["defaultValue"]
self.metadata = FSFlagMetadata(metadataDico: expoedInfo["metadata"] as? [String: Any] ?? [:])
self.alreadyActivatedCampaign = expoedInfo["alreadyActivatedCampaign"] as? Bool ?? false // TODO: Review & test later
}

/// Dictionary that represent the Exposed Flag
Expand All @@ -62,12 +71,16 @@ protocol IFlag {
result.updateValue(aValue, forKey: "value")
}

if let aAlreadyActivatedCampaign = alreadyActivatedCampaign {
result.updateValue(aAlreadyActivatedCampaign, forKey: "alreadyActivatedCampaign")
}

return result
}

/// String that represent a json for the Exposed Flag
/// - Return: String?
@objc public func toJson() -> String? {
@objc public func toJson() -> NSString? {
var result: [String: Any] = [
"key": key,
"metadata": metadata.toJson()
Expand All @@ -81,9 +94,13 @@ protocol IFlag {
result.updateValue(aValue, forKey: "value")
}

if let aAlreadyActivated = alreadyActivatedCampaign {
result.updateValue(aAlreadyActivated, forKey: "alreadyActivatedCampaign")
}

guard let jsonData = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted) else {
return nil
}
return jsonData.jsonString
return jsonData.prettyPrintedJSONString
}
}
29 changes: 29 additions & 0 deletions FlagShip/Source/Core/FSVisitor+Flags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@

import Foundation

let FS_SESSION_VISITOR: TimeInterval = 30.0 // 30 * 60 /// 30 mn

public extension FSVisitor {
/// Get FSFlag object
/// - Parameter key: key represent key modification
/// - Returns: FSFLag object
func getFlag(key: String) -> FSFlag {
/// Init the session
// self.sessionDuration = Date()
// We dispaly a warning if the flag's status is not fetched
if self.fetchStatus != .FETCHED {
FlagshipLogManager.Log(level: .ALL, tag: .FLAG, messageToDisplay: FSLogMessage.MESSAGE(self.requiredFetchReason.warningMessage(key, self.visitorId)))
Expand All @@ -23,13 +27,38 @@ public extension FSVisitor {
/// Getting FSFlagCollection
/// - Returns: an instance of FSFlagCollection with flags
func getFlags() -> FSFlagCollection {
/// Init the session
// self.sessionDuration = Date()
var ret: [String: FSFlag] = [:]
self.currentFlags.forEach { (key: String, _: FSModification) in

ret.updateValue(FSFlag(key, self.strategy), forKey: key)
}
return FSFlagCollection(flags: ret)
}

internal func isDeduplicatedFlag(campId: String, varGrpId: String) -> Bool {
let elapsed = Date().timeIntervalSince(self.sessionDuration)

// print("⏱️ Time session since last activity is : \(Int(elapsed)) s")

// Reset the timestamp at the end of function
defer { sessionDuration = Date() }

// Session expired ==> reset the dico and return false
guard elapsed <= FS_SESSION_VISITOR else {
activatedVariations = [campId: varGrpId]
return false
}
// Session still valide
if activatedVariations[campId] == varGrpId {
// The ids already exist ==> is deduplicated flag
return true
}
// We have a new flag ==> save it and return false
activatedVariations[campId] = varGrpId
return false
}
}

// We keep FlagMap instead using FSFlagV4 wich is also possible
Expand Down
4 changes: 4 additions & 0 deletions FlagShip/Source/Core/FSVisitor+Reconcilliation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ 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) {
/// Init the session
self.sessionDuration = Date()
if configManager.flagshipConfig.mode != .DECISION_API {
FlagshipLogManager.Log(level: .ALL, tag: .AUTHENTICATE, messageToDisplay: FSLogMessage.IGNORE_AUTHENTICATE)
return
Expand All @@ -27,6 +29,8 @@ public extension FSVisitor {

/// Use authenticate methode to go from Logged in session to logged out session
@objc func unauthenticate() {
/// Init the session
self.sessionDuration = Date()
if configManager.flagshipConfig.mode != .DECISION_API {
FlagshipLogManager.Log(level: .ALL, tag: .UNAUTHENTICATE, messageToDisplay: FSLogMessage.IGNORE_AUTHENTICATE)
return
Expand Down
31 changes: 30 additions & 1 deletion FlagShip/Source/Core/FSVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ import UIKit

// Score value
public internal(set) var emotionScoreAI: String? = nil

// Session duration in MS
var sessionDuration: Date

// List of activated variations
var activatedVariations: [String: String] = [:] /// campId:varGrpId

// Refonte status
public internal(set) var fetchStatus: FSFlagStatus = .FETCH_REQUIRED {
Expand Down Expand Up @@ -129,7 +135,7 @@ import UIKit
self.visitorId = FSTools.manageVisitorId(aVisitorId)
self.anonymousId = nil
}

// Set the user context
self.context = FSContext(aContext, visitorId: aVisitorId)

Expand All @@ -149,9 +155,14 @@ import UIKit
self._onFlagStatusChanged = pOnFlagStatusChanged
self._onFlagStatusFetchRequired = pOnFlagStatusFetchRequired
self._onFlagStatusFetched = pOnFlagStatusFetched

// init sessionStartTimestamp
self.sessionDuration = Date()
}

@objc public func fetchFlags(onFetchCompleted: @escaping () -> Void) {
/// Init the session
self.sessionDuration = Date()
self.prepareEmotionAI(onCompleted: { score, _ in
// Set the score
self.emotionScoreAI = score
Expand Down Expand Up @@ -213,6 +224,8 @@ import UIKit
}

public func collectEmotionsAIEvents(window: UIWindow?, screenName: String? = nil, usingSwizzling: Bool = false) {
/// Init the session
self.sessionDuration = Date()
if Flagship.sharedInstance.eaiCollectEnabled == true {
self.strategy?.getStrategy().collectEmotionsAIEvents(window: window, screenName: screenName, usingSwizzling: usingSwizzling)
} else {
Expand All @@ -231,6 +244,8 @@ import UIKit
// Update Context
// - Parameter newContext: user's context
@objc public func updateContext(_ context: [String: Any]) {
/// Init the session
self.sessionDuration = Date()
self._updateContext(context)
}

Expand All @@ -239,6 +254,8 @@ import UIKit
// - key: key for the given value
// - newValue: value for teh given key
public func updateContext(_ key: String, _ newValue: Any) {
/// Init the session
self.sessionDuration = Date()
self._updateContext([key: newValue])
}

Expand All @@ -247,6 +264,8 @@ import UIKit
// - presetKey: name of the preset context, see PresetContext
// - newValue: the value for the given key
public func updateContext(_ flagshipContext: FlagshipContext, _ value: Any) {
/// Init the session
self.sessionDuration = Date()
/// Check the validity value
if !flagshipContext.chekcValidity(value) {
FlagshipLogManager.Log(level: .ALL, tag: .UPDATE_CONTEXT, messageToDisplay: FSLogMessage.UPDATE_PRE_CONTEXT_FAILED(flagshipContext.rawValue))
Expand All @@ -266,17 +285,24 @@ import UIKit
// Get the current context
// - Returns: Dictionary that represent a user context
@objc public func getContext() -> [String: Any] {
/// Init the session
self.sessionDuration = Date()
return self.context.getCurrentContext()
}

// Clear the current context
@objc public func clearContext() {
/// Init the session
self.sessionDuration = Date()
self.context.clearContext()
}

// Send Hits
// - Parameter T: Hit object
public func sendHit<T: FSTrackingProtocol>(_ event: T) {
/// Init the session
self.sessionDuration = Date()

self.strategy?.getStrategy().sendHit(event)
}

Expand All @@ -285,6 +311,9 @@ import UIKit
// Set the conssent
// - Parameter newValue: if true, then flush all stored visitor data
@objc public func setConsent(hasConsented: Bool) {
/// Init the session
self.sessionDuration = Date()

self.hasConsented = hasConsented
self.strategy?.getStrategy().setConsent(newValue: hasConsented)

Expand Down
4 changes: 2 additions & 2 deletions FlagShip/Source/Core/FSVisitorExposed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import Foundation

/// String that represent a json for the Visitor Exposed
/// - Return: NSString ?
@objc public func toJson() -> String? {
@objc public func toJson() -> NSString? {
var result: [String: Any] = [
"id": id,
"context": context
Expand All @@ -60,7 +60,7 @@ import Foundation
guard let jsonData = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted) else {
return nil
}
return jsonData.jsonString
return jsonData.prettyPrintedJSONString
}
}

Expand Down
Loading