-
Notifications
You must be signed in to change notification settings - Fork 95
/
Copy pathSettings.swift
190 lines (168 loc) · 7.3 KB
/
Settings.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//
// Settings.swift
// Segment
//
// Created by Cody Garvin on 12/15/20.
//
import Foundation
public struct Settings: Codable {
public var integrations: JSON? = nil
public var plan: JSON? = nil
public var edgeFunction: JSON? = nil
public var middlewareSettings: JSON? = nil
public var metrics: JSON? = nil
public var consentSettings: JSON? = nil
public init(writeKey: String, apiHost: String) {
integrations = try! JSON([
SegmentDestination.Constants.integrationName.rawValue: [
SegmentDestination.Constants.apiKey.rawValue: writeKey,
SegmentDestination.Constants.apiHost.rawValue: apiHost
]
])
}
public init(writeKey: String) {
integrations = try! JSON([
SegmentDestination.Constants.integrationName.rawValue: [
SegmentDestination.Constants.apiKey.rawValue: writeKey,
SegmentDestination.Constants.apiHost.rawValue: HTTPClient.getDefaultAPIHost()
]
])
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.integrations = try? values.decode(JSON.self, forKey: CodingKeys.integrations)
self.plan = try? values.decode(JSON.self, forKey: CodingKeys.plan)
self.edgeFunction = try? values.decode(JSON.self, forKey: CodingKeys.edgeFunction)
self.middlewareSettings = try? values.decode(JSON.self, forKey: CodingKeys.middlewareSettings)
self.metrics = try? values.decode(JSON.self, forKey: CodingKeys.metrics)
self.consentSettings = try? values.decode(JSON.self, forKey: CodingKeys.consentSettings)
}
static public func load(from url: URL?) -> Settings? {
guard let url = url else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
let settings = try? JSONDecoder.default.decode(Settings.self, from: data)
return settings
}
static public func load(resource: String, bundle: Bundle = Bundle.main) -> Settings? {
let url = bundle.url(forResource: resource, withExtension: nil)
return load(from: url)
}
enum CodingKeys: String, CodingKey {
case integrations
case plan
case edgeFunction
case middlewareSettings
case metrics
case consentSettings
}
/**
* Easily retrieve settings for a specific integration name.
*
* - Parameter for: The string name of the integration
* - Returns: The dictionary representing the settings for this integration as supplied by Segment.com
*/
public func integrationSettings(forKey key: String) -> [String: Any]? {
guard let settings = integrations?.dictionaryValue else { return nil }
let result = settings[key] as? [String: Any]
return result
}
public func integrationSettings<T: Decodable>(forKey key: String) -> T? {
var result: T? = nil
guard let settings = integrations?.dictionaryValue else { return nil }
if let dict = settings[key], let jsonData = try? JSONSerialization.data(withJSONObject: dict) {
result = try? JSONDecoder.default.decode(T.self, from: jsonData)
}
return result
}
public func integrationSettings<T: Decodable>(forPlugin plugin: DestinationPlugin) -> T? {
return integrationSettings(forKey: plugin.key)
}
public func hasIntegrationSettings(forPlugin plugin: DestinationPlugin) -> Bool {
return hasIntegrationSettings(key: plugin.key)
}
public func hasIntegrationSettings(key: String) -> Bool {
guard let settings = integrations?.dictionaryValue else { return false }
return (settings[key] != nil)
}
}
extension Settings: Equatable {
public static func == (lhs: Settings, rhs: Settings) -> Bool {
let l = lhs.prettyPrint()
let r = rhs.prettyPrint()
return l == r
}
}
extension Analytics {
internal func update(settings: Settings) {
guard let system: System = store.currentState() else { return }
apply { plugin in
plugin.update(settings: settings, type: updateType(for: plugin, in: system))
if let destPlugin = plugin as? DestinationPlugin {
destPlugin.apply { subPlugin in
subPlugin.update(settings: settings, type: updateType(for: subPlugin, in: system))
}
}
}
}
internal func updateIfNecessary(plugin: Plugin) {
guard let system: System = store.currentState() else { return }
// if we're already running, update has already been called for existing plugins,
// so we just wanna call it on this one if it hasn't been done already.
if system.running, let settings = system.settings {
let alreadyInitialized = system.initializedPlugins.contains { p in
return plugin === p
}
if !alreadyInitialized {
store.dispatch(action: System.AddPluginToInitialized(plugin: plugin))
plugin.update(settings: settings, type: .initial)
} else {
plugin.update(settings: settings, type: .refresh)
}
}
}
internal func updateType(for plugin: Plugin, in system: System) -> UpdateType {
let alreadyInitialized = system.initializedPlugins.contains { p in
return plugin === p
}
if alreadyInitialized {
return .refresh
} else {
store.dispatch(action: System.AddPluginToInitialized(plugin: plugin))
return .initial
}
}
internal func checkSettings() {
#if DEBUG
if isUnitTesting {
// we don't really wanna wait for this network call during tests...
// but we should make it work similarly.
store.dispatch(action: System.ToggleRunningAction(running: false))
operatingMode.run(queue: DispatchQueue.main) {
if let state: System = self.store.currentState(), let settings = state.settings {
self.store.dispatch(action: System.UpdateSettingsAction(settings: settings))
self.update(settings: settings)
}
self.store.dispatch(action: System.ToggleRunningAction(running: true))
}
return
}
#endif
let writeKey = self.configuration.values.writeKey
let httpClient = HTTPClient(analytics: self)
// stop things; queue in case our settings have changed.
store.dispatch(action: System.ToggleRunningAction(running: false))
httpClient.settingsFor(writeKey: writeKey) { (success, settings) in
if success {
if let s = settings {
// put the new settings in the state store.
// this will cause them to be cached.
self.store.dispatch(action: System.UpdateSettingsAction(settings: s))
// let plugins know we just received some settings..
self.update(settings: s)
}
}
// we're good to go back to a running state.
self.store.dispatch(action: System.ToggleRunningAction(running: true))
}
}
}