Skip to content
Draft
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
4 changes: 4 additions & 0 deletions DevCycle.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
0B721685290B21CD004D0AB7 /* SSEMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B721684290B21CD004D0AB7 /* SSEMessage.swift */; };
0F2DD75A279EFA6B00540C9D /* CustomData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F2DD759279EFA6B00540C9D /* CustomData.swift */; };
0FD643D52A8680E4004BB036 /* EventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FD643D42A8680E4004BB036 /* EventEmitter.swift */; };
52133B2028DDFB260007691D /* RequestConsolidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52133B1F28DDFB260007691D /* RequestConsolidatorTests.swift */; };
52133B2228DE00BC0007691D /* ProcessConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52133B2128DE00BC0007691D /* ProcessConfig.swift */; };
52133B2428DE0FEB0007691D /* GetTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52133B2328DE0FEB0007691D /* GetTestConfig.swift */; };
Expand Down Expand Up @@ -82,6 +83,7 @@
/* Begin PBXFileReference section */
0B721684290B21CD004D0AB7 /* SSEMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSEMessage.swift; sourceTree = "<group>"; };
0F2DD759279EFA6B00540C9D /* CustomData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomData.swift; sourceTree = "<group>"; };
0FD643D42A8680E4004BB036 /* EventEmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventEmitter.swift; sourceTree = "<group>"; };
52133B1F28DDFB260007691D /* RequestConsolidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConsolidatorTests.swift; sourceTree = "<group>"; };
52133B2128DE00BC0007691D /* ProcessConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessConfig.swift; sourceTree = "<group>"; };
52133B2328DE0FEB0007691D /* GetTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetTestConfig.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -166,6 +168,7 @@
529CE32228DCBEC2009AB137 /* RequestConsolidator.swift */,
52133B2128DE00BC0007691D /* ProcessConfig.swift */,
5226DF05290C588900630745 /* NotificationNames.swift */,
0FD643D42A8680E4004BB036 /* EventEmitter.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -444,6 +447,7 @@
5276C9F0275E682B00B9A324 /* DevCycleOptions.swift in Sources */,
524D58242770F78B00D7CC56 /* ObjCDVCVariable.swift in Sources */,
52133B2228DE00BC0007691D /* ProcessConfig.swift in Sources */,
0FD643D52A8680E4004BB036 /* EventEmitter.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
10 changes: 7 additions & 3 deletions DevCycle/DVCVariable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ public class DVCVariable<T> {
let oldValue = self.value
self.value = value
self.isDefaulted = false
if let handler = self.handler,
!isEqual(oldValue, variable.value) {

if let handler = self.handler, !isEqual(oldValue, variable.value) {
handler(value)
}
} else {
Expand Down Expand Up @@ -136,7 +136,11 @@ public class DVCVariable<T> {
}

private func addNotificationObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(propertyChange(notification:)), name: Notification.Name(NotificationNames.NewUserConfig), object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(propertyChange(notification:)),
name: Notification.Name(NotificationNames.NewUserConfig),
object: nil)
}

public func onUpdate(handler: @escaping VariableValueHandler<T>) -> DVCVariable {
Expand Down
136 changes: 104 additions & 32 deletions DevCycle/DevCycleClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public typealias IdentifyCompletedHandler = (Error?, [String: Variable]?) -> Voi
public typealias FlushCompletedHandler = (Error?) -> Void
public typealias CloseCompletedHandler = () -> Void

internal typealias VariableInstanceDic = [String: NSMapTable<AnyObject, AnyObject>]


public class DevCycleClient {
var sdkKey: String?
var user: DevCycleUser?
Expand All @@ -42,14 +45,15 @@ public class DevCycleClient {
private var enableEdgeDB: Bool = false
var inactivityDelayMS: Double = 120000

let eventEmitter: EventEmitter = EventEmitter()
private var service: DevCycleServiceProtocol?
private var cacheService: CacheServiceProtocol = CacheService()
private var cache: Cache?
var sseConnection: SSEConnectionProtocol?
private var flushTimer: Timer?
private var closed: Bool = false
private var inactivityWorkItem: DispatchWorkItem?
private var variableInstanceDictonary = [String: NSMapTable<AnyObject, AnyObject>]()
private var variableInstanceDictonary = VariableInstanceDic()
private var isConfigCached: Bool = false
private var disableAutomaticEventLogging: Bool = false
private var disableCustomEventLogging: Bool = false
Expand Down Expand Up @@ -140,39 +144,40 @@ public class DevCycleClient {

self.service?.getConfig(user: user, enableEdgeDB: self.enableEdgeDB, extraParams: nil, completion: { [weak self] config, error in
guard let self = self else { return }
var requestErrored = false

if let error = error {
Log.error("Error getting config: \(error)", tags: ["setup"])
self.cache = self.cacheService.load()

self.eventEmitter.emit(EventEmitValues<Any>.error(error))
requestErrored = true
} else {
if let config = config {
Log.debug("Config: \(config)", tags: ["setup"])
}
self.config?.userConfig = config
self.isConfigCached = false

self.cacheUser(user: user)
self.setUserConfig(config)
self.cacheUser(user)

if (self.checkIfEdgeDBEnabled(config: config!, enableEdgeDB: self.enableEdgeDB)) {
if (!(user.isAnonymous ?? false)) {
self.service?.saveEntity(user: user, completion: { data, response, error in
if error != nil {
Log.error("Error saving user entity for \(user). Error: \(String(describing: error))")
} else {
Log.info("Saved user entity")
}
})
}
if (self.checkIfEdgeDBEnabled(config: config!, enableEdgeDB: self.enableEdgeDB) && !(user.isAnonymous ?? false)) {
self.service?.saveEntity(user: user, completion: { data, response, error in
if error != nil {
Log.error("Error saving user entity for \(user). Error: \(String(describing: error))")
} else {
Log.info("Saved user entity")
}
})
}
}

self.setupSSEConnection()
self.configCompletionHandlers.forEach { handler in handler(error) }
self.configCompletionHandlers = []

for handler in self.configCompletionHandlers {
handler(error)
}
callback?(error)
self.initialized = true
self.configCompletionHandlers = []
callback?(error)
self.eventEmitter.emit(EventEmitValues.initialized(!requestErrored))

self.setupSSEConnection()
})

self.flushTimer = Timer.scheduledTimer(
Expand Down Expand Up @@ -209,17 +214,40 @@ public class DevCycleClient {
let extraParams = RequestParams(sse: sse, lastModified: lastModified, etag: etag)
self.service?.getConfig(user: lastIdentifiedUser, enableEdgeDB: self.enableEdgeDB, extraParams: extraParams, completion: { [weak self] config, error in
guard let self = self else { return }

if let error = error {
Log.error("Error getting config: \(error)", tags: ["refetchConfig"])
self.eventEmitter.emit(EventEmitValues<Any>.error(error))
} else {
self.config?.userConfig = config
self.isConfigCached = false
self.setUserConfig(config)
}
})
}
}

private func setUserConfig(_ config: UserConfig?) {
let oldConfig = self.config
self.config?.userConfig = config
self.isConfigCached = false

if let config = config {
self.eventEmitter.emitFeatureUpdates(
oldFeatures: oldConfig?.userConfig?.features,
newFeatures: config.features
)
self.eventEmitter.emitVariableUpdates(
oldVariables: oldConfig?.userConfig?.variables,
newVariables: config.variables,
variableInstanceDic: self.variableInstanceDictonary
)

if oldConfig == nil || config.etag != oldConfig?.userConfig?.etag {
self.eventEmitter.emit(EventEmitValues<Any>.configUpdated(config.variables))
}
}
}

private func cacheUser(user: DevCycleUser) {
private func cacheUser(_ user: DevCycleUser) {
self.cacheService.save(user: user)
if user.isAnonymous == true, let userId = user.userId {
self.cacheService.setAnonUserId(anonUserId: userId)
Expand Down Expand Up @@ -307,8 +335,9 @@ public class DevCycleClient {
return getVariable(key: key, defaultValue: defaultValue)
}

private let regex = try? NSRegularExpression(pattern: ".*[^a-z0-9(\\-)(_)].*")

func getVariable<T>(key: String, defaultValue: T) -> DVCVariable<T> {
let regex = try? NSRegularExpression(pattern: ".*[^a-z0-9(\\-)(_)].*")
if (regex?.firstMatch(in: key, range: NSMakeRange(0, key.count)) != nil) {
Log.error("The variable key \(key) is invalid. It must contain only lowercase letters, numbers, hyphens and underscores. The default value will always be returned for this call.")
return DVCVariable(
Expand Down Expand Up @@ -347,6 +376,8 @@ public class DevCycleClient {
self.eventQueue.updateAggregateEvents(variableKey: variable.key, variableIsDefaulted: variable.isDefaulted)
}


self.eventEmitter.emit(EventEmitValues<T>.variableEvaluated(variable.key, variable))
return variable
}
}
Expand All @@ -367,18 +398,19 @@ public class DevCycleClient {

self.service?.getConfig(user: updateUser, enableEdgeDB: self.enableEdgeDB, extraParams: nil, completion: { [weak self] config, error in
guard let self = self else { return }

if let error = error {
Log.error("Error getting config: \(error)", tags: ["identify"])
self.cache = self.cacheService.load()
self.eventEmitter.emit(EventEmitValues<Any>.error(error))
} else {
if let config = config {
Log.debug("Config: \(config)", tags: ["identify"])
}
self.config?.userConfig = config
self.isConfigCached = false
self.setUserConfig(config)
}
self.user = user
self.cacheUser(user: user)
self.cacheUser(user)
callback?(error, config?.variables)
})
}
Expand All @@ -395,21 +427,23 @@ public class DevCycleClient {

self.service?.getConfig(user: anonUser, enableEdgeDB: self.enableEdgeDB, extraParams: nil, completion: { [weak self] config, error in
guard let self = self else { return }
guard error == nil else {

if let error = error {
if let previousAnonUserId = cachedAnonUserId {
self.cacheService.setAnonUserId(anonUserId: previousAnonUserId)
}
self.eventEmitter.emit(EventEmitValues<Any>.error(error))
callback?(error, nil)
return
}

if let config = config {
Log.debug("Config: \(config)", tags: ["reset"])
self.eventEmitter.emit(EventEmitValues.configUpdated(config.variables))
}
self.config?.userConfig = config
self.isConfigCached = false
self.setUserConfig(config)
self.user = anonUser
self.cacheUser(user: anonUser)
self.cacheUser(anonUser)
callback?(error, config?.variables)
})
}
Expand All @@ -421,6 +455,44 @@ public class DevCycleClient {
public func allVariables() -> [String: Variable] {
return self.config?.userConfig?.variables ?? [:]
}

public func subscribe(_ handler: InitializedEventHandler) {
self.eventEmitter.subscribe(EventHandlers.initialized(handler))
}
public func subscribe(_ handler: ErrorEventHandler) {
self.eventEmitter.subscribe(EventHandlers.error(handler))
}
public func subscribe(_ handler: ConfigUpdatedEventHandler) {
self.eventEmitter.subscribe(EventHandlers.configUpdated(handler))
}
public func subscribe(_ handler: VariableUpdatedHandler) {
self.eventEmitter.subscribe(EventHandlers.variableUpdated(handler))
}
public func subscribe(_ handler: VariableEvaluatedHandler) {
self.eventEmitter.subscribe(EventHandlers.variableEvaluated(handler))
}
public func subscribe(_ handler: FeatureUpdatedHandler) {
self.eventEmitter.subscribe(EventHandlers.featureUpdated(handler))
}

public func unsubscribe(_ handler: InitializedEventHandler) {
self.eventEmitter.unsubscribe(EventHandlers.initialized(handler))
}
public func unsubscribe(_ handler: ErrorEventHandler) {
self.eventEmitter.unsubscribe(EventHandlers.error(handler))
}
public func unsubscribe(_ handler: ConfigUpdatedEventHandler) {
self.eventEmitter.unsubscribe(EventHandlers.configUpdated(handler))
}
public func unsubscribe(_ handler: VariableUpdatedHandler) {
self.eventEmitter.unsubscribe(EventHandlers.variableUpdated(handler))
}
public func unsubscribe(_ handler: VariableEvaluatedHandler) {
self.eventEmitter.unsubscribe(EventHandlers.variableEvaluated(handler))
}
public func unsubscribe(_ handler: FeatureUpdatedHandler) {
self.eventEmitter.unsubscribe(EventHandlers.featureUpdated(handler))
}

public func track(_ event: DevCycleEvent) {
if (self.closed) {
Expand Down
Loading