Skip to content

Commit ac73182

Browse files
authored
chore: allow to set wrapper SDK info (sourceId & version) (#115)
1 parent fe22e00 commit ac73182

20 files changed

+606
-103
lines changed

Bucketeer/Sources/Internal/DI/Component.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class ComponentImpl: Component {
2727
featureTag: dataModule.config.featureTag
2828
)
2929
self.eventInteractor = EventInteractorImpl(
30-
sdkVersion: dataModule.config.sdkVersion,
30+
sdkInfo: dataModule.config.toSDKInfo(),
3131
appVersion: dataModule.config.appVersion,
3232
device: dataModule.device,
3333
eventsMaxBatchQueueCount: dataModule.config.eventsMaxQueueSize,

Bucketeer/Sources/Internal/DI/DataModule.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ final class DataModuleImpl: DataModule {
3333
apiEndpoint: config.apiEndpoint,
3434
apiKey: self.config.apiKey,
3535
featureTag: self.config.featureTag,
36+
sdkInfo: self.config.toSDKInfo(),
3637
session: URLSession(configuration: .default),
3738
logger: self.config.logger
3839
)

Bucketeer/Sources/Internal/Event/EventInteractor.swift

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extension EventInteractor {
1818
}
1919

2020
final class EventInteractorImpl: EventInteractor {
21-
let sdkVersion: String
21+
let sdkInfo: SDKInfo
2222
let eventsMaxBatchQueueCount: Int
2323
let apiClient: ApiClient
2424
let eventSQLDao: EventSQLDao
@@ -31,7 +31,7 @@ final class EventInteractorImpl: EventInteractor {
3131
private var eventUpdateListener: EventUpdateListener?
3232

3333
init(
34-
sdkVersion: String,
34+
sdkInfo: SDKInfo,
3535
appVersion: String,
3636
device: Device,
3737
eventsMaxBatchQueueCount: Int,
@@ -42,7 +42,7 @@ final class EventInteractorImpl: EventInteractor {
4242
logger: Logger?,
4343
featureTag: String
4444
) {
45-
self.sdkVersion = sdkVersion
45+
self.sdkInfo = sdkInfo
4646
self.eventsMaxBatchQueueCount = eventsMaxBatchQueueCount
4747
self.apiClient = apiClient
4848
self.eventSQLDao = eventSQLDao
@@ -75,8 +75,8 @@ final class EventInteractorImpl: EventInteractor {
7575
user: user,
7676
reason: evaluation.reason,
7777
tag: featureTag,
78-
sourceId: .ios,
79-
sdkVersion: sdkVersion,
78+
sourceId: sdkInfo.sourceId,
79+
sdkVersion: sdkInfo.sdkVersion,
8080
metadata: metadata
8181
)),
8282
type: .evaluation
@@ -96,8 +96,8 @@ final class EventInteractorImpl: EventInteractor {
9696
user: user,
9797
reason: .init(type: .client),
9898
tag: featureTag,
99-
sourceId: .ios,
100-
sdkVersion: sdkVersion,
99+
sourceId: sdkInfo.sourceId,
100+
sdkVersion: sdkInfo.sdkVersion,
101101
metadata: metadata
102102
)),
103103
type: .evaluation
@@ -117,8 +117,8 @@ final class EventInteractorImpl: EventInteractor {
117117
value: value,
118118
user: user,
119119
tag: featureTag,
120-
sourceId: .ios,
121-
sdkVersion: sdkVersion,
120+
sourceId: sdkInfo.sourceId,
121+
sdkVersion: sdkInfo.sdkVersion,
122122
metadata: metadata
123123
)),
124124
type: .goal
@@ -140,8 +140,8 @@ final class EventInteractorImpl: EventInteractor {
140140
latencySecond: seconds
141141
)),
142142
type: .responseLatency,
143-
sourceId: .ios,
144-
sdk_version: sdkVersion,
143+
sourceId: sdkInfo.sourceId,
144+
sdk_version: sdkInfo.sdkVersion,
145145
metadata: metadata
146146
)),
147147
type: .metrics
@@ -156,8 +156,8 @@ final class EventInteractorImpl: EventInteractor {
156156
sizeByte: sizeByte
157157
)),
158158
type: .responseSize,
159-
sourceId: .ios,
160-
sdk_version: sdkVersion,
159+
sourceId: sdkInfo.sourceId,
160+
sdk_version: sdkInfo.sdkVersion,
161161
metadata: metadata
162162
)),
163163
type: .metrics
@@ -192,9 +192,10 @@ final class EventInteractorImpl: EventInteractor {
192192
apiId: apiId,
193193
labels: ["tag": featureTag],
194194
currentTimeSeconds: clock.currentTimeSeconds,
195-
sdkVersion: sdkVersion,
195+
sdkInfo: sdkInfo,
196196
metadata: metadata
197197
)
198+
198199
try trackMetricsEvent(events: [
199200
.init(
200201
id: idGenerator.id(),
@@ -332,8 +333,12 @@ extension Event {
332333
}
333334

334335
extension BKTError {
335-
func toMetricsEventData(apiId: ApiId, labels: [String: String], currentTimeSeconds: Int64, sdkVersion: String, metadata: [String: String]?) ->
336-
EventData {
336+
func toMetricsEventData(
337+
apiId: ApiId,
338+
labels: [String: String],
339+
currentTimeSeconds: Int64,
340+
sdkInfo: SDKInfo,
341+
metadata: [String: String]?) -> EventData {
337342
let error = self
338343
let metricsEventData: MetricsEventData
339344
let metricsEventType: MetricsEventType
@@ -427,8 +432,8 @@ extension BKTError {
427432
timestamp: currentTimeSeconds,
428433
event: metricsEventData,
429434
type: metricsEventType,
430-
sourceId: .ios,
431-
sdk_version: sdkVersion,
435+
sourceId: sdkInfo.sourceId,
436+
sdk_version: sdkInfo.sdkVersion,
432437
metadata: metadata
433438
))
434439
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
struct SDKInfo {
2+
let sourceId: SourceID
3+
let sdkVersion: String
4+
}

Bucketeer/Sources/Internal/Model/SourceID.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,12 @@ enum SourceID: Int, Codable, Hashable {
88
case goalBatch = 4
99
case goServer = 5
1010
case nodeServer = 6
11+
case flutter = 8
12+
case react = 9
13+
case reactNative = 10
14+
case openFeatureKotlin = 100
15+
case openFeatureSwift = 101
16+
case openFeatureJavaScript = 102
17+
case openFeatureGo = 103
18+
case openFeatureNode = 104
1119
}

Bucketeer/Sources/Internal/Remote/ApiClientImpl.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ final class ApiClientImpl: ApiClient {
77
private let apiEndpoint: URL
88
private let apiKey: String
99
private let featureTag: String
10+
private let sdkInfo: SDKInfo
1011
private let session: Session
1112
private let defaultRequestTimeoutMills: Int64
1213
private let logger: Logger?
@@ -23,14 +24,15 @@ final class ApiClientImpl: ApiClient {
2324
apiEndpoint: URL,
2425
apiKey: String,
2526
featureTag: String,
27+
sdkInfo: SDKInfo,
2628
defaultRequestTimeoutMills: Int64 = ApiClientImpl.DEFAULT_REQUEST_TIMEOUT_MILLIS,
2729
session: Session,
2830
logger: Logger?
2931
) {
30-
3132
self.apiEndpoint = apiEndpoint
3233
self.apiKey = apiKey
3334
self.featureTag = featureTag
35+
self.sdkInfo = sdkInfo
3436
self.defaultRequestTimeoutMills = defaultRequestTimeoutMills
3537
self.session = session
3638
self.logger = logger
@@ -48,12 +50,12 @@ final class ApiClientImpl: ApiClient {
4850
tag: self.featureTag,
4951
user: user,
5052
userEvaluationsId: userEvaluationsId,
51-
sourceId: .ios,
53+
sourceId: sdkInfo.sourceId,
5254
userEvaluationCondition: UserEvaluationCondition(
5355
evaluatedAt: condition.evaluatedAt,
5456
userAttributesUpdated: condition.userAttributesUpdated
5557
),
56-
sdkVersion: Version.current
58+
sdkVersion: sdkInfo.sdkVersion
5759
)
5860
let featureTag = self.featureTag
5961
let timeoutMillisValue = timeoutMillis ?? defaultRequestTimeoutMills
@@ -82,7 +84,8 @@ final class ApiClientImpl: ApiClient {
8284
func registerEvents(events: [Event], completion: ((Result<RegisterEventsResponse, BKTError>) -> Void)?) {
8385
let requestBody = RegisterEventsRequestBody(
8486
events: events,
85-
sdkVersion: Version.current
87+
sdkVersion: sdkInfo.sdkVersion,
88+
sourceId: sdkInfo.sourceId
8689
)
8790
logger?.debug(message: "[API] Register events: \(requestBody)")
8891
let encoder = JSONEncoder()

Bucketeer/Sources/Internal/Remote/RequestBody/RegisterEventsRequestBody.swift

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

33
struct RegisterEventsRequestBody: Codable {
44
internal init(events: [Event] = [],
5-
sdkVersion: String = Version.current,
6-
sourceId: SourceID = SourceID.ios
5+
sdkVersion: String,
6+
sourceId: SourceID
77
) {
88
self.events = events
99
self.sdkVersion = sdkVersion

Bucketeer/Sources/Public/BKTConfig.swift

Lines changed: 92 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public struct BKTConfig {
88
let eventsMaxQueueSize: Int
99
let pollingInterval: Int64
1010
let backgroundPollingInterval: Int64
11+
let sourceId: SourceID
1112
let sdkVersion: String
1213
let appVersion: String
1314
let logger: BKTLogger?
@@ -22,6 +23,8 @@ public struct BKTConfig {
2223
private(set) var backgroundPollingInterval: Int64?
2324
private(set) var appVersion: String?
2425
private(set) var logger: BKTLogger?
26+
private(set) var wrapperSdkVersion: String?
27+
private(set) var wrapperSdkSourceId: Int?
2528

2629
public init() {}
2730

@@ -65,6 +68,28 @@ public struct BKTConfig {
6568
return self
6669
}
6770

71+
// Sets the SDK version explicitly.
72+
// IMPORTANT: This option is intended for internal use only.
73+
// It should NOT be set by developers directly integrating this SDK.
74+
// Use this option ONLY when another SDK acts as a proxy and wraps this native SDK.
75+
// In such cases, set this value to the version of the proxy SDK.
76+
public func with(wrapperSdkVersion: String) -> Builder {
77+
self.wrapperSdkVersion = wrapperSdkVersion
78+
return self
79+
}
80+
81+
// Sets the SDK sourceId explicitly.
82+
// IMPORTANT: This option is intended for internal use only.
83+
// It should NOT be set by developers directly integrating this SDK.
84+
// Use this option ONLY when another SDK acts as a proxy and wraps this native SDK.
85+
// In such cases, set this value to the sourceId of the proxy SDK.
86+
// The wrapperSdkSourceId is used to identify the origin of the request.
87+
// We don't expose the SourceID enum publicly because only Flutter and OpenFeature Swift are currently supported.
88+
public func with(wrapperSdkSourceId: Int) -> Builder {
89+
self.wrapperSdkSourceId = wrapperSdkSourceId
90+
return self
91+
}
92+
6893
public func with(logger: BKTLogger) -> Builder {
6994
self.logger = logger
7095
return self
@@ -91,27 +116,55 @@ extension BKTConfig {
91116
appVersion: String,
92117
logger: BKTLogger? = nil
93118
) throws {
94-
guard !apiKey.isEmpty else {
119+
// Delegate to Builder to keep a single validation/normalization path.
120+
let builder = BKTConfig.Builder()
121+
.with(apiKey: apiKey)
122+
.with(apiEndpoint: apiEndpoint)
123+
.with(featureTag: featureTag)
124+
.with(eventsFlushInterval: eventsFlushInterval)
125+
.with(eventsMaxQueueSize: eventsMaxQueueSize)
126+
.with(pollingInterval: pollingInterval)
127+
.with(backgroundPollingInterval: backgroundPollingInterval)
128+
.with(appVersion: appVersion)
129+
if let logger = logger {
130+
_ = builder.with(logger: logger)
131+
}
132+
// Build and assign to self
133+
self = try builder.build()
134+
}
135+
136+
private init(with builder: Builder) throws {
137+
guard let apiKey = builder.apiKey, apiKey.isNotEmpty() else {
95138
throw BKTError.illegalArgument(message: "apiKey is required")
96139
}
97-
guard let apiEndpointURL = URL(string: apiEndpoint) else {
140+
guard let apiEndpoint = builder.apiEndpoint, apiEndpoint.isNotEmpty(), let apiEndpointURL = URL(string: apiEndpoint) else {
98141
throw BKTError.illegalArgument(message: "apiEndpoint is required")
99142
}
100-
guard !appVersion.isEmpty else {
143+
guard let appVersion = builder.appVersion, appVersion.isNotEmpty() else {
101144
throw BKTError.illegalArgument(message: "appVersion is required")
102145
}
103146

104-
var pollingInterval = pollingInterval
147+
// refs: JS SDK PR https://github.com/bucketeer-io/javascript-client-sdk/pull/91
148+
// Allow Builder.featureTag to be nil
149+
// So the default value of the BKTConfig will be ""
150+
let featureTag = builder.featureTag ?? ""
151+
let logger = builder.logger
152+
// Set default intervals if needed
153+
var pollingInterval: Int64 = builder.pollingInterval ?? Constant.MINIMUM_POLLING_INTERVAL_MILLIS
154+
var backgroundPollingInterval: Int64 = builder.backgroundPollingInterval ?? Constant.MINIMUM_BACKGROUND_POLLING_INTERVAL_MILLIS
155+
var eventsFlushInterval: Int64 = builder.eventsFlushInterval ?? Constant.DEFAULT_FLUSH_INTERVAL_MILLIS
156+
let eventsMaxQueueSize = builder.eventsMaxQueueSize ?? Constant.DEFAULT_MAX_QUEUE_SIZE
157+
105158
if pollingInterval < Constant.MINIMUM_POLLING_INTERVAL_MILLIS {
106159
logger?.warn(message: "pollingInterval: \(pollingInterval) is set but must be above \(Constant.MINIMUM_POLLING_INTERVAL_MILLIS)")
107160
pollingInterval = Constant.MINIMUM_POLLING_INTERVAL_MILLIS
108161
}
109-
var backgroundPollingInterval = backgroundPollingInterval
162+
110163
if backgroundPollingInterval < Constant.MINIMUM_BACKGROUND_POLLING_INTERVAL_MILLIS {
111164
logger?.warn(message: "backgroundPollingInterval: \(backgroundPollingInterval) is set but must be above \(Constant.MINIMUM_BACKGROUND_POLLING_INTERVAL_MILLIS)")
112165
backgroundPollingInterval = Constant.MINIMUM_BACKGROUND_POLLING_INTERVAL_MILLIS
113166
}
114-
var eventsFlushInterval = eventsFlushInterval
167+
115168
if eventsFlushInterval < Constant.MINIMUM_FLUSH_INTERVAL_MILLIS {
116169
logger?.warn(message: "eventsFlushInterval: \(eventsFlushInterval) is set but must be above \(Constant.MINIMUM_FLUSH_INTERVAL_MILLIS)")
117170
eventsFlushInterval = Constant.DEFAULT_FLUSH_INTERVAL_MILLIS
@@ -123,42 +176,46 @@ extension BKTConfig {
123176
self.eventsMaxQueueSize = eventsMaxQueueSize
124177
self.pollingInterval = pollingInterval
125178
self.backgroundPollingInterval = backgroundPollingInterval
126-
self.sdkVersion = Version.current
179+
180+
let resolvedSdkSourceId = try resolveSdkSourceId(wrapperSdkSourceId: builder.wrapperSdkSourceId)
181+
let resolvedSdkVersion = try resolveSdkVersion(
182+
resolvedSdkSourceId: resolvedSdkSourceId,
183+
wrapperSdkVersion: builder.wrapperSdkVersion
184+
)
185+
186+
self.sourceId = resolvedSdkSourceId
187+
self.sdkVersion = resolvedSdkVersion
127188
self.appVersion = appVersion
128189
self.logger = logger
129190
}
130191

131-
private init(with builder: Builder) throws {
132-
guard let apiKey = builder.apiKey, apiKey.isNotEmpty() else {
133-
throw BKTError.illegalArgument(message: "apiKey is required")
134-
}
135-
guard let apiEndpoint = builder.apiEndpoint, apiEndpoint.isNotEmpty() else {
136-
throw BKTError.illegalArgument(message: "apiEndpoint is required")
137-
}
138-
guard let appVersion = builder.appVersion, appVersion.isNotEmpty() else {
139-
throw BKTError.illegalArgument(message: "appVersion is required")
140-
}
192+
func toSDKInfo() -> SDKInfo {
193+
return .init(sourceId: self.sourceId, sdkVersion: self.sdkVersion)
194+
}
195+
}
141196

142-
// Set default intervals if needed
143-
let pollingInterval: Int64 = builder.pollingInterval ?? Constant.MINIMUM_POLLING_INTERVAL_MILLIS
144-
let backgroundPollingInterval: Int64 = builder.backgroundPollingInterval ?? Constant.MINIMUM_BACKGROUND_POLLING_INTERVAL_MILLIS
145-
let eventsFlushInterval: Int64 = builder.eventsFlushInterval ?? Constant.DEFAULT_FLUSH_INTERVAL_MILLIS
146-
let eventsMaxQueueSize = builder.eventsMaxQueueSize ?? Constant.DEFAULT_MAX_QUEUE_SIZE
197+
// Only Flutter (8) and OpenFeature Swift (101) are supported currently.
198+
// Other source IDs will throw an error as its not from iOS
199+
private let supportedWrapperSdkSourceIds: [SourceID] = [.flutter, .openFeatureSwift]
147200

148-
// Use the current init method
149-
try self.init(apiKey: apiKey,
150-
apiEndpoint: apiEndpoint,
151-
// refs: JS SDK PR https://github.com/bucketeer-io/javascript-client-sdk/pull/91
152-
// Allow Builder.featureTag could be nill
153-
// So the default value of the BKTConfig will be ""
154-
featureTag: builder.featureTag ?? "",
155-
eventsFlushInterval: eventsFlushInterval,
156-
eventsMaxQueueSize: eventsMaxQueueSize,
157-
pollingInterval: pollingInterval,
158-
backgroundPollingInterval: backgroundPollingInterval,
159-
appVersion: appVersion,
160-
logger: builder.logger)
201+
private func resolveSdkSourceId(wrapperSdkSourceId: Int?) throws -> SourceID {
202+
guard let wrapperSdkSourceId = wrapperSdkSourceId else {
203+
return .ios // default ios
204+
}
205+
if let sourceId = SourceID(rawValue: wrapperSdkSourceId), supportedWrapperSdkSourceIds.contains(sourceId) {
206+
return sourceId
207+
}
208+
throw BKTError.illegalArgument(message: "Unsupported wrapperSdkSourceId: \(wrapperSdkSourceId)")
209+
}
210+
211+
private func resolveSdkVersion(resolvedSdkSourceId: SourceID, wrapperSdkVersion: String?) throws -> String {
212+
if resolvedSdkSourceId != .ios {
213+
if let wrapperSdkVersion = wrapperSdkVersion, wrapperSdkVersion.isNotEmpty() {
214+
return wrapperSdkVersion
215+
}
216+
throw BKTError.illegalArgument(message: "wrapperSdkVersion is required when sourceId is not iOS")
161217
}
218+
return Version.current
162219
}
163220

164221
fileprivate extension String {

0 commit comments

Comments
 (0)