Skip to content

Commit 0851da8

Browse files
authored
Merge pull request #102 from vazarkevych/streaming-host-update
streamingHost variable && add init feature property
2 parents fb0d283 + 0c92b2e commit 0851da8

File tree

10 files changed

+108
-100
lines changed

10 files changed

+108
-100
lines changed

GrowthBookTests/ExperimentRunTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ExperimentRunTests: XCTestCase {
1818
let experiment = Experiment(json: item[2].dictionaryValue)
1919

2020
let gbContext = Context(apiHost: nil,
21+
streamingHost: nil,
2122
clientKey: nil,
2223
encryptionKey: nil,
2324
isEnabled: testContext.isEnabled,

GrowthBookTests/FeatureValueTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class FeatureValueTests: XCTestCase {
1818
let testData = FeaturesTest(json: item[1].dictionaryValue)
1919

2020
let gbContext = Context(apiHost: nil,
21+
streamingHost: nil,
2122
clientKey: nil,
2223
encryptionKey: nil,
2324
isEnabled: true,

GrowthBookTests/GrowthBookSDKBuilderTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import XCTest
44

55
class GrowthBookSDKBuilderTests: XCTestCase {
66
let testApiHost = "https://host.com"
7+
let testStreamingHost = "https://streaming.host.com"
78
let testClientKey = "4r23r324f23"
89
let expectedURL = "https://host.com/api/features/4r23r324f23"
10+
let expectedStreamingHostURL = "https://streaming.host.com/sub/4r23r324f23"
11+
let expectedDefaultStreamingURL = "https://host.com/sub/4r23r324f23"
912
let testAttributes: JSON = JSON()
1013
let testKeyString = "Ns04T5n9+59rl2x3SlNHtQ=="
1114

@@ -33,6 +36,30 @@ class GrowthBookSDKBuilderTests: XCTestCase {
3336
}
3437
}
3538

39+
func testApiURL() throws {
40+
var gbContext = Context(apiHost: testApiHost,
41+
streamingHost: testStreamingHost,
42+
clientKey: testClientKey,
43+
encryptionKey: nil,
44+
isEnabled: true,
45+
attributes: JSON(),
46+
forcedVariations: JSON(),
47+
isQaMode: false,
48+
trackingClosure: { _, _ in },
49+
backgroundSync: false,
50+
savedGroups: JSON())
51+
52+
let streamingHostURL = gbContext.getSSEUrl()
53+
54+
gbContext.streamingHost = nil
55+
56+
let defaultURL = gbContext.getSSEUrl()
57+
58+
XCTAssertTrue(gbContext.getFeaturesURL() == expectedURL)
59+
XCTAssertTrue(streamingHostURL == expectedStreamingHostURL)
60+
XCTAssertTrue(defaultURL == expectedDefaultStreamingURL)
61+
}
62+
3663
func testSDKInitializationDefault() throws {
3764
let sdkInstance = GrowthBookBuilder(apiHost: testApiHost,
3865
clientKey: testClientKey,

GrowthBookTests/StickyBucketingTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class StickyBucketingFeatureTests: XCTestCase {
3131
}
3232

3333
let gbContext = Context(apiHost: nil,
34+
streamingHost: nil,
3435
clientKey: nil,
3536
encryptionKey: nil,
3637
isEnabled: true,

Sources/CommonMain/Evaluators/ConditionEvaluator.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class ConditionEvaluator {
249249
/// This function is just a case statement that handles all the possible operators
250250
///
251251
/// There are basic comparison operators in the form attributeValue {op} conditionValue
252-
func isEvalOperatorCondition(operatorKey: String, attributeValue: JSON?, conditionValue: JSON, savedGroups: JSON? = nil) -> Bool {
252+
func isEvalOperatorCondition(operatorKey: String, attributeValue: JSON, conditionValue: JSON, savedGroups: JSON? = nil) -> Bool {
253253
let conditionJson = JSON(conditionValue)
254254
// Evaluate TYPE operator - whether both are of same type
255255
if operatorKey == "$type" {
@@ -264,9 +264,9 @@ class ConditionEvaluator {
264264
// Evaluate EXISTS operator - whether condition contains attribute
265265
if operatorKey == "$exists" {
266266
let targetPrimitiveValue = conditionJson.stringValue
267-
if targetPrimitiveValue == "false" && attributeValue == nil {
267+
if targetPrimitiveValue == "false" && attributeValue == .null {
268268
return true
269-
} else if targetPrimitiveValue == "true" && attributeValue != nil {
269+
} else if targetPrimitiveValue == "true" && attributeValue != .null {
270270
return true
271271
}
272272
}
@@ -280,16 +280,16 @@ class ConditionEvaluator {
280280
}
281281
case "$exists":
282282
let targetPrimitiveValue = conditionJson.stringValue
283-
if targetPrimitiveValue == "false" && attributeValue == nil {
283+
if targetPrimitiveValue == "false" && attributeValue == .null {
284284
return true
285-
} else if targetPrimitiveValue == "true" && attributeValue != nil {
285+
} else if targetPrimitiveValue == "true" && attributeValue != .null {
286286
return true
287287
}
288288
default: break
289289
}
290290

291291
/// There are three operators where conditionValue is an array
292-
if let conditionValue = conditionJson.array, let attributeValue = attributeValue {
292+
if let conditionValue = conditionJson.array, attributeValue != .null {
293293
switch operatorKey {
294294
case "$in":
295295
return Common.isIn(actual: attributeValue, expected: conditionValue)
@@ -317,7 +317,7 @@ class ConditionEvaluator {
317317
}
318318
default: break
319319
}
320-
} else if let attribute = attributeValue?.array {
320+
} else if let attribute = attributeValue.array {
321321
switch operatorKey {
322322
// Evaluate ELEMMATCH operator - whether condition matches attribute
323323
case "$elemMatch":
@@ -332,36 +332,36 @@ class ConditionEvaluator {
332332
let sourcePrimitiveValue = attributeValue
333333
switch operatorKey {
334334
case "$veq":
335-
if let attributeString = attributeValue?.string, let conditionString = conditionValue.string {
335+
if let attributeString = attributeValue.string, let conditionString = conditionValue.string {
336336
return Utils.paddedVersionString(input: attributeString) == Utils.paddedVersionString(input: conditionString)
337337
}
338338
case "$vne":
339-
if let attributeString = attributeValue?.string, let conditionString = conditionValue.string {
339+
if let attributeString = attributeValue.string, let conditionString = conditionValue.string {
340340
return Utils.paddedVersionString(input: attributeString) != Utils.paddedVersionString(input: conditionString)
341341
}
342342
case "$vgt":
343-
if let attributeString = attributeValue?.string, let conditionString = conditionValue.string {
343+
if let attributeString = attributeValue.string, let conditionString = conditionValue.string {
344344
return Utils.paddedVersionString(input: attributeString) > Utils.paddedVersionString(input: conditionString)
345345
}
346346
case "$vgte":
347-
if let attributeString = attributeValue?.string, let conditionString = conditionValue.string {
347+
if let attributeString = attributeValue.string, let conditionString = conditionValue.string {
348348
return Utils.paddedVersionString(input: attributeString) >= Utils.paddedVersionString(input: conditionString)
349349
}
350350
case "$vlt":
351-
if let attributeString = attributeValue?.string, let conditionString = conditionValue.string {
351+
if let attributeString = attributeValue.string, let conditionString = conditionValue.string {
352352
return Utils.paddedVersionString(input: attributeString) < Utils.paddedVersionString(input: conditionString)
353353
}
354354
case "$vlte":
355-
if let attributeString = attributeValue?.string, let conditionString = conditionValue.string {
355+
if let attributeString = attributeValue.string, let conditionString = conditionValue.string {
356356
return Utils.paddedVersionString(input: attributeString) <= Utils.paddedVersionString(input: conditionString)
357357
}
358358
case "$inGroup":
359-
if let attributeString = attributeValue, let conditionString = conditionValue.string {
360-
return Common.isIn(actual: attributeString, expected: savedGroups?[conditionString].array ?? [] )
359+
if attributeValue != .null, let conditionString = conditionValue.string {
360+
return Common.isIn(actual: attributeValue, expected: savedGroups?[conditionString].array ?? [] )
361361
}
362362
case "$notInGroup":
363-
if let attributeString = attributeValue, let conditionString = conditionValue.string {
364-
return !Common.isIn(actual: attributeString, expected: savedGroups?[conditionString].array ?? [])
363+
if attributeValue != .null, let conditionString = conditionValue.string {
364+
return !Common.isIn(actual: attributeValue, expected: savedGroups?[conditionString].array ?? [])
365365
}
366366
// Evaluate EQ operator - whether condition equals to attribute
367367
case "$eq":
@@ -375,36 +375,36 @@ class ConditionEvaluator {
375375
let conditionDoubleOrNull = Utils.convertJsonToDouble(from: conditionValue) {
376376
return attributeDoubleOrNull < conditionDoubleOrNull
377377
}
378-
return sourcePrimitiveValue ?? 0.0 < targetPrimitiveValue
378+
return sourcePrimitiveValue < targetPrimitiveValue
379379
// Evaluate LTE operator - whether attribute less than or equal to condition
380380
case "$lte":
381381
if let attributeDoubleOrNull = Utils.convertJsonToDouble(from: attributeValue),
382382
let conditionDoubleOrNull = Utils.convertJsonToDouble(from: conditionValue) {
383383
return attributeDoubleOrNull <= conditionDoubleOrNull
384384
}
385-
return sourcePrimitiveValue ?? 0.0 <= targetPrimitiveValue
385+
return sourcePrimitiveValue <= targetPrimitiveValue
386386
// Evaluate GT operator - whether attribute greater than to condition
387387
case "$gt":
388388
if let attributeDoubleOrNull = Utils.convertJsonToDouble(from: attributeValue),
389389
let conditionDoubleOrNull = Utils.convertJsonToDouble(from: conditionValue) {
390390
return attributeDoubleOrNull > conditionDoubleOrNull
391391
}
392-
return sourcePrimitiveValue ?? 0.0 > targetPrimitiveValue
392+
return sourcePrimitiveValue > targetPrimitiveValue
393393
// Evaluate GTE operator - whether attribute greater than or equal to condition
394394
case "$gte":
395395
if let attributeDoubleOrNull = Utils.convertJsonToDouble(from: attributeValue),
396396
let conditionDoubleOrNull = Utils.convertJsonToDouble(from: conditionValue) {
397397
return attributeDoubleOrNull >= conditionDoubleOrNull
398398
}
399-
return sourcePrimitiveValue ?? 0.0 >= targetPrimitiveValue
399+
return sourcePrimitiveValue >= targetPrimitiveValue
400400
// Evaluate REGEX operator - whether attribute contains condition regex
401401
case "$regex":
402402
let targetPrimitiveValueString = conditionValue.stringValue
403-
let sourcePrimitiveValueString = attributeValue?.stringValue
404-
if isContains(source: sourcePrimitiveValueString ?? "", target: targetPrimitiveValueString) {
403+
let sourcePrimitiveValueString = attributeValue.stringValue
404+
if isContains(source: sourcePrimitiveValueString, target: targetPrimitiveValueString) {
405405
return true
406406
}
407-
return sourcePrimitiveValueString?.contains(targetPrimitiveValueString) ?? false
407+
return sourcePrimitiveValueString.contains(targetPrimitiveValueString)
408408

409409
default: break
410410
}

Sources/CommonMain/Evaluators/ExperimentEvaluator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ExperimentEvaluator {
1616

1717
// If context.forcedVariations[experiment.trackingKey] is defined, return immediately (not in experiment, forced variation)
1818
if let forcedVariation = context.userContext.forcedVariations?.dictionaryValue[experiment.key] {
19+
logger.trace("Forced variation found for experiment \(experiment.key). \nForced variation: \(forcedVariation)")
1920
return getExperimentResult(gbContext: context, experiment: experiment, variationIndex: forcedVariation.intValue, hashUsed: false, featureId: featureId)
2021
}
2122

Sources/CommonMain/GrowthBookSDK.swift

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ protocol GrowthBookProtocol: AnyObject {
1212

1313
public struct GrowthBookModel {
1414
var apiHost: String?
15+
var streamingHost: String?
1516
var clientKey: String?
1617
var encryptionKey: String?
1718
var features: Data?
@@ -42,8 +43,30 @@ public struct GrowthBookModel {
4243

4344
private var cachingManager: CachingLayer
4445

45-
@objc public init(apiHost: String? = nil, clientKey: String? = nil, encryptionKey: String? = nil, attributes: [String: Any], trackingCallback: @escaping TrackingCallback, refreshHandler: CacheRefreshHandler? = nil, backgroundSync: Bool = false, remoteEval: Bool = false, apiRequestHeaders: [String: String]? = nil, streamingHostRequestHeaders: [String: String]? = nil) {
46-
growthBookBuilderModel = GrowthBookModel(apiHost: apiHost, clientKey: clientKey, encryptionKey: encryptionKey, attributes: JSON(attributes), trackingClosure: trackingCallback, backgroundSync: backgroundSync, remoteEval: remoteEval, apiRequestHeaders: apiRequestHeaders, streamingHostRequestHeaders: streamingHostRequestHeaders)
46+
@objc public init(
47+
apiHost: String? = nil,
48+
clientKey: String? = nil,
49+
encryptionKey: String? = nil,
50+
attributes: [String: Any],
51+
features: Data? = nil,
52+
trackingCallback: @escaping TrackingCallback,
53+
refreshHandler: CacheRefreshHandler? = nil,
54+
backgroundSync: Bool = false,
55+
remoteEval: Bool = false,
56+
apiRequestHeaders: [String: String]? = nil,
57+
streamingHostRequestHeaders: [String: String]? = nil
58+
) {
59+
growthBookBuilderModel = GrowthBookModel(
60+
apiHost: apiHost,
61+
clientKey: clientKey,
62+
encryptionKey: encryptionKey,
63+
features: features,
64+
attributes: JSON(attributes),
65+
trackingClosure: trackingCallback,
66+
backgroundSync: backgroundSync,
67+
remoteEval: remoteEval,
68+
apiRequestHeaders: apiRequestHeaders,
69+
streamingHostRequestHeaders: streamingHostRequestHeaders)
4770
self.refreshHandler = refreshHandler
4871
self.networkDispatcher = CoreNetworkClient(
4972
apiRequestHeaders: apiRequestHeaders ?? [:],
@@ -127,10 +150,16 @@ public struct GrowthBookModel {
127150
cachingManager.setCustomCachePath(customDirectory)
128151
return self
129152
}
153+
154+
@objc public func setStreamingHost(streamingHost: String) -> GrowthBookBuilder {
155+
growthBookBuilderModel.streamingHost = streamingHost
156+
return self
157+
}
130158

131159
@objc public func initializer() -> GrowthBookSDK {
132160
let gbContext = Context(
133161
apiHost: growthBookBuilderModel.apiHost,
162+
streamingHost : growthBookBuilderModel.streamingHost,
134163
clientKey: growthBookBuilderModel.clientKey,
135164
encryptionKey: growthBookBuilderModel.encryptionKey,
136165
isEnabled: growthBookBuilderModel.isEnabled,

Sources/CommonMain/Model/Context.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import Foundation
22

33
/// Defines the GrowthBook context.
44
@objc public class Context: NSObject {
5-
/// your api host
5+
/// Your api host
66
public let apiHost: String?
7-
/// unique client key
7+
/// Your streaming host
8+
public var streamingHost: String?
9+
/// Unique client key
810
public let clientKey: String?
911
/// Encryption key for encrypted features.
1012
public let encryptionKey: String?
@@ -28,13 +30,14 @@ import Foundation
2830
public var stickyBucketIdentifierAttributes: [String]?
2931
/// Enable to use remote evaluation
3032
public let remoteEval: Bool
31-
// Keys are unique identifiers for the features and the values are Feature objects.
32-
// Feature definitions - To be pulled from API / Cache
33+
/// Keys are unique identifiers for the features and the values are Feature objects.
34+
/// Feature definitions - To be pulled from API / Cache
3335
var features: Features
34-
36+
/// Target the same group of users across multiple features and experiments with Saved Groups
3537
public var savedGroups: JSON?
3638

3739
init(apiHost: String?,
40+
streamingHost: String?,
3841
clientKey: String?,
3942
encryptionKey: String?,
4043
isEnabled: Bool,
@@ -50,6 +53,7 @@ import Foundation
5053
remoteEval: Bool = false,
5154
savedGroups: JSON? = nil) {
5255
self.apiHost = apiHost
56+
self.streamingHost = streamingHost
5357
self.clientKey = clientKey
5458
self.encryptionKey = encryptionKey
5559
self.isEnabled = isEnabled
@@ -83,8 +87,8 @@ import Foundation
8387
}
8488

8589
@objc public func getSSEUrl() -> String? {
86-
if let apiHost = apiHost, let clientKey = clientKey {
87-
return "\(apiHost)/sub/\(clientKey)"
90+
if let host = streamingHost ?? apiHost, let clientKey = clientKey {
91+
return "\(host)/sub/\(clientKey)"
8892
} else {
8993
return nil
9094
}

0 commit comments

Comments
 (0)