@bigcrunch/react-native-ads 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchAds.kt +434 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchBannerView.kt +484 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchInterstitial.kt +403 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchRewarded.kt +409 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/adapters/GoogleAdsAdapter.kt +592 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/AdOrchestrator.kt +623 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/AnalyticsClient.kt +719 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/BidRequestClient.kt +364 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/ConfigManager.kt +300 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/DeviceContext.kt +385 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/RewardedCallback.kt +42 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/SessionManager.kt +330 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/DeviceHelper.kt +60 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/HttpClient.kt +114 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/Logger.kt +71 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/PrivacyStore.kt +125 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/internal/Storage.kt +88 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/BannerAdListener.kt +55 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/InterstitialAdListener.kt +55 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/RewardedAdListener.kt +58 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/AdEvent.kt +880 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/AppConfig.kt +87 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/DeviceData.kt +18 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/PlacementConfig.kt +70 -0
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/SessionInfo.kt +21 -0
- package/android/build.gradle +22 -10
- package/android/settings.gradle +2 -6
- package/android/src/main/java/com/bigcrunch/ads/react/BigCrunchAdsModule.kt +8 -2
- package/ios/BigCrunchAds/Sources/Adapters/GoogleAdsAdapter.swift +512 -0
- package/ios/BigCrunchAds/Sources/BigCrunchAds.swift +387 -0
- package/ios/BigCrunchAds/Sources/BigCrunchBannerView.swift +448 -0
- package/ios/BigCrunchAds/Sources/BigCrunchInterstitial.swift +412 -0
- package/ios/BigCrunchAds/Sources/BigCrunchRewarded.swift +523 -0
- package/ios/BigCrunchAds/Sources/Core/AdOrchestrator.swift +514 -0
- package/ios/BigCrunchAds/Sources/Core/AnalyticsClient.swift +874 -0
- package/ios/BigCrunchAds/Sources/Core/BidRequestClient.swift +344 -0
- package/ios/BigCrunchAds/Sources/Core/ConfigManager.swift +305 -0
- package/ios/BigCrunchAds/Sources/Core/DeviceContext.swift +284 -0
- package/ios/BigCrunchAds/Sources/Core/SessionManager.swift +392 -0
- package/ios/BigCrunchAds/Sources/Internal/HTTPClient.swift +146 -0
- package/ios/BigCrunchAds/Sources/Internal/Logger.swift +62 -0
- package/ios/BigCrunchAds/Sources/Internal/PrivacyStore.swift +129 -0
- package/ios/BigCrunchAds/Sources/Internal/Storage.swift +73 -0
- package/ios/BigCrunchAds/Sources/Models/AdEvent.swift +784 -0
- package/ios/BigCrunchAds/Sources/Models/AppConfig.swift +97 -0
- package/ios/BigCrunchAds/Sources/Models/DeviceData.swift +68 -0
- package/ios/BigCrunchAds/Sources/Models/PlacementConfig.swift +137 -0
- package/ios/BigCrunchAds/Sources/Models/SessionInfo.swift +48 -0
- package/ios/BigCrunchAdsModule.swift +37 -9
- package/ios/BigCrunchBannerViewManager.swift +0 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -2
- package/package.json +7 -1
- package/react-native-bigcrunch-ads.podspec +0 -1
- package/scripts/inject-version.js +55 -0
- package/src/index.ts +3 -2
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP client for making network requests
|
|
5
|
+
*
|
|
6
|
+
* Uses URLSession for reliable networking with automatic retries and caching.
|
|
7
|
+
* All methods are async functions using Swift's async/await.
|
|
8
|
+
*/
|
|
9
|
+
internal class HTTPClient {
|
|
10
|
+
|
|
11
|
+
private let session: URLSession
|
|
12
|
+
|
|
13
|
+
init(configuration: URLSessionConfiguration? = nil) {
|
|
14
|
+
let config = configuration ?? {
|
|
15
|
+
let defaultConfig = URLSessionConfiguration.default
|
|
16
|
+
defaultConfig.timeoutIntervalForRequest = 10
|
|
17
|
+
defaultConfig.timeoutIntervalForResource = 10
|
|
18
|
+
return defaultConfig
|
|
19
|
+
}()
|
|
20
|
+
self.session = URLSession(configuration: config)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Perform a GET request
|
|
25
|
+
*
|
|
26
|
+
* - Parameters:
|
|
27
|
+
* - url: The URL to fetch
|
|
28
|
+
* - headers: Optional HTTP headers
|
|
29
|
+
* - Returns: Result containing response body or error
|
|
30
|
+
*/
|
|
31
|
+
func get(url: String, headers: [String: String] = [:]) async -> Result<String, Error> {
|
|
32
|
+
guard let url = URL(string: url) else {
|
|
33
|
+
let error = NSError(
|
|
34
|
+
domain: "HTTPClient",
|
|
35
|
+
code: -1,
|
|
36
|
+
userInfo: [NSLocalizedDescriptionKey: "Invalid URL: \(url)"]
|
|
37
|
+
)
|
|
38
|
+
BCLogger.error("GET request failed: Invalid URL")
|
|
39
|
+
return .failure(error)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var request = URLRequest(url: url)
|
|
43
|
+
request.httpMethod = "GET"
|
|
44
|
+
headers.forEach { request.addValue($0.value, forHTTPHeaderField: $0.key) }
|
|
45
|
+
|
|
46
|
+
do {
|
|
47
|
+
let (data, response) = try await session.data(for: request)
|
|
48
|
+
|
|
49
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
50
|
+
let error = NSError(
|
|
51
|
+
domain: "HTTPClient",
|
|
52
|
+
code: -1,
|
|
53
|
+
userInfo: [NSLocalizedDescriptionKey: "Invalid response type"]
|
|
54
|
+
)
|
|
55
|
+
return .failure(error)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (200...299).contains(httpResponse.statusCode) {
|
|
59
|
+
let string = String(data: data, encoding: .utf8) ?? ""
|
|
60
|
+
BCLogger.verbose("GET success: \(url.absoluteString) (\(data.count) bytes)")
|
|
61
|
+
return .success(string)
|
|
62
|
+
} else {
|
|
63
|
+
let error = NSError(
|
|
64
|
+
domain: "HTTPClient",
|
|
65
|
+
code: httpResponse.statusCode,
|
|
66
|
+
userInfo: [NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode)"]
|
|
67
|
+
)
|
|
68
|
+
BCLogger.warning("GET failed: \(url.absoluteString) - HTTP \(httpResponse.statusCode)")
|
|
69
|
+
return .failure(error)
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
BCLogger.error("GET request failed: \(url.absoluteString) - \(error)")
|
|
73
|
+
return .failure(error)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Perform a POST request
|
|
79
|
+
*
|
|
80
|
+
* - Parameters:
|
|
81
|
+
* - url: The URL to post to
|
|
82
|
+
* - body: Request body (JSON string)
|
|
83
|
+
* - headers: Optional HTTP headers
|
|
84
|
+
* - Returns: Result containing response body or error
|
|
85
|
+
*/
|
|
86
|
+
func post(
|
|
87
|
+
url: String,
|
|
88
|
+
body: String,
|
|
89
|
+
headers: [String: String] = [:]
|
|
90
|
+
) async -> Result<String, Error> {
|
|
91
|
+
guard let url = URL(string: url) else {
|
|
92
|
+
let error = NSError(
|
|
93
|
+
domain: "HTTPClient",
|
|
94
|
+
code: -1,
|
|
95
|
+
userInfo: [NSLocalizedDescriptionKey: "Invalid URL: \(url)"]
|
|
96
|
+
)
|
|
97
|
+
BCLogger.error("POST request failed: Invalid URL")
|
|
98
|
+
return .failure(error)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Log the request body for debugging
|
|
102
|
+
BCLogger.debug("POST to: \(url.absoluteString)")
|
|
103
|
+
BCLogger.debug("Request body: \(body)")
|
|
104
|
+
|
|
105
|
+
var request = URLRequest(url: url)
|
|
106
|
+
request.httpMethod = "POST"
|
|
107
|
+
request.httpBody = body.data(using: .utf8)
|
|
108
|
+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
109
|
+
headers.forEach { request.addValue($0.value, forHTTPHeaderField: $0.key) }
|
|
110
|
+
|
|
111
|
+
do {
|
|
112
|
+
let (data, response) = try await session.data(for: request)
|
|
113
|
+
|
|
114
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
115
|
+
let error = NSError(
|
|
116
|
+
domain: "HTTPClient",
|
|
117
|
+
code: -1,
|
|
118
|
+
userInfo: [NSLocalizedDescriptionKey: "Invalid response type"]
|
|
119
|
+
)
|
|
120
|
+
return .failure(error)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (200...299).contains(httpResponse.statusCode) {
|
|
124
|
+
let string = String(data: data, encoding: .utf8) ?? ""
|
|
125
|
+
BCLogger.verbose("POST success: \(url.absoluteString)")
|
|
126
|
+
return .success(string)
|
|
127
|
+
} else {
|
|
128
|
+
// Log response body for 4xx errors to see what the server says
|
|
129
|
+
let errorBody = String(data: data, encoding: .utf8) ?? ""
|
|
130
|
+
let error = NSError(
|
|
131
|
+
domain: "HTTPClient",
|
|
132
|
+
code: httpResponse.statusCode,
|
|
133
|
+
userInfo: [NSLocalizedDescriptionKey: "HTTP \(httpResponse.statusCode)"]
|
|
134
|
+
)
|
|
135
|
+
BCLogger.warning("POST failed: \(url.absoluteString) - HTTP \(httpResponse.statusCode)")
|
|
136
|
+
if !errorBody.isEmpty {
|
|
137
|
+
BCLogger.warning("Error response body: \(errorBody)")
|
|
138
|
+
}
|
|
139
|
+
return .failure(error)
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
BCLogger.error("POST request failed: \(url.absoluteString) - \(error)")
|
|
143
|
+
return .failure(error)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internal logger for BigCrunch Ads SDK
|
|
5
|
+
*
|
|
6
|
+
* All logs are prefixed with "[BCrunch:LEVEL]" for easy filtering.
|
|
7
|
+
* Logging can be disabled in production by setting isEnabled = false.
|
|
8
|
+
* Error logs are always shown regardless of isEnabled flag.
|
|
9
|
+
*/
|
|
10
|
+
internal class BCLogger {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Enable/disable debug logging
|
|
14
|
+
* Defaults to false for production builds
|
|
15
|
+
*/
|
|
16
|
+
static var isEnabled = false
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Verbose log - detailed debugging information
|
|
20
|
+
*/
|
|
21
|
+
static func verbose(_ message: String) {
|
|
22
|
+
if isEnabled {
|
|
23
|
+
print("[BCrunch:VERBOSE] \(message)")
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Debug log - general debugging information
|
|
29
|
+
*/
|
|
30
|
+
static func debug(_ message: String) {
|
|
31
|
+
if isEnabled {
|
|
32
|
+
print("[BCrunch:DEBUG] \(message)")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Info log - informational messages
|
|
38
|
+
*/
|
|
39
|
+
static func info(_ message: String) {
|
|
40
|
+
if isEnabled {
|
|
41
|
+
print("[BCrunch:INFO] \(message)")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Warning log - potential issues
|
|
47
|
+
*/
|
|
48
|
+
static func warning(_ message: String) {
|
|
49
|
+
if isEnabled {
|
|
50
|
+
print("[BCrunch:WARNING] \(message)")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Error log - critical errors
|
|
56
|
+
* Always logs regardless of isEnabled flag
|
|
57
|
+
*/
|
|
58
|
+
static func error(_ message: String) {
|
|
59
|
+
// Always log errors
|
|
60
|
+
print("[BCrunch:ERROR] \(message)")
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Centralized privacy signal store
|
|
5
|
+
*
|
|
6
|
+
* Manages GDPR, CCPA, and COPPA privacy signals for the SDK.
|
|
7
|
+
* Reads IAB TCF/USP standard keys from UserDefaults when available,
|
|
8
|
+
* and provides structured objects for bid request construction.
|
|
9
|
+
*/
|
|
10
|
+
internal class PrivacyStore {
|
|
11
|
+
|
|
12
|
+
private let lock = NSLock()
|
|
13
|
+
|
|
14
|
+
// SDK-set values (from public API)
|
|
15
|
+
private var _gdprConsentString: String?
|
|
16
|
+
private var _gdprApplies: Bool?
|
|
17
|
+
private var _ccpaString: String?
|
|
18
|
+
private var _coppaApplies: Bool = false
|
|
19
|
+
|
|
20
|
+
// IAB standard UserDefaults keys
|
|
21
|
+
private static let iabTcfConsentKey = "IABTCF_TCString"
|
|
22
|
+
private static let iabTcfGdprAppliesKey = "IABTCF_gdprApplies"
|
|
23
|
+
private static let iabUspStringKey = "IABUSPrivacy_String"
|
|
24
|
+
|
|
25
|
+
// MARK: - Setters (called from public API)
|
|
26
|
+
|
|
27
|
+
func setGdprConsent(_ consent: String) {
|
|
28
|
+
lock.lock()
|
|
29
|
+
defer { lock.unlock() }
|
|
30
|
+
_gdprApplies = true
|
|
31
|
+
_gdprConsentString = consent
|
|
32
|
+
BCLogger.debug("PrivacyStore: GDPR consent set")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func setCcpaString(_ ccpa: String) {
|
|
36
|
+
lock.lock()
|
|
37
|
+
defer { lock.unlock() }
|
|
38
|
+
_ccpaString = ccpa
|
|
39
|
+
// Also write to IAB standard key for CMP interop
|
|
40
|
+
UserDefaults.standard.set(ccpa, forKey: Self.iabUspStringKey)
|
|
41
|
+
BCLogger.debug("PrivacyStore: CCPA string set")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func setCoppaApplies(_ applies: Bool) {
|
|
45
|
+
lock.lock()
|
|
46
|
+
defer { lock.unlock() }
|
|
47
|
+
_coppaApplies = applies
|
|
48
|
+
BCLogger.debug("PrivacyStore: COPPA set to \(applies)")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// MARK: - Getters
|
|
52
|
+
|
|
53
|
+
/// Effective GDPR consent string (SDK-set takes priority, then IAB TCF)
|
|
54
|
+
var gdprConsentString: String? {
|
|
55
|
+
lock.lock()
|
|
56
|
+
defer { lock.unlock() }
|
|
57
|
+
return _gdprConsentString
|
|
58
|
+
?? UserDefaults.standard.string(forKey: Self.iabTcfConsentKey)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Whether GDPR applies (SDK-set takes priority, then IAB TCF)
|
|
62
|
+
var gdprApplies: Bool? {
|
|
63
|
+
lock.lock()
|
|
64
|
+
defer { lock.unlock() }
|
|
65
|
+
if let sdkValue = _gdprApplies {
|
|
66
|
+
return sdkValue
|
|
67
|
+
}
|
|
68
|
+
let iabValue = UserDefaults.standard.object(forKey: Self.iabTcfGdprAppliesKey) as? Int
|
|
69
|
+
return iabValue.map { $0 == 1 }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Effective CCPA/USP string (SDK-set takes priority, then IAB USP)
|
|
73
|
+
var ccpaString: String? {
|
|
74
|
+
lock.lock()
|
|
75
|
+
defer { lock.unlock() }
|
|
76
|
+
return _ccpaString
|
|
77
|
+
?? UserDefaults.standard.string(forKey: Self.iabUspStringKey)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var coppaApplies: Bool {
|
|
81
|
+
lock.lock()
|
|
82
|
+
defer { lock.unlock() }
|
|
83
|
+
return _coppaApplies
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// MARK: - Bid Request Helpers
|
|
87
|
+
|
|
88
|
+
/// Build the `regs` object for an OpenRTB bid request
|
|
89
|
+
func buildRegsDict() -> [String: Any] {
|
|
90
|
+
var regs: [String: Any] = [:]
|
|
91
|
+
var ext: [String: Any] = [:]
|
|
92
|
+
|
|
93
|
+
if coppaApplies {
|
|
94
|
+
regs["coppa"] = 1
|
|
95
|
+
} else {
|
|
96
|
+
regs["coppa"] = 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if let gdpr = gdprApplies {
|
|
100
|
+
ext["gdpr"] = gdpr ? 1 : 0
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if let usp = ccpaString {
|
|
104
|
+
ext["us_privacy"] = usp
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if !ext.isEmpty {
|
|
108
|
+
regs["ext"] = ext
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return regs
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Build the `user` object for an OpenRTB bid request
|
|
115
|
+
func buildUserDict() -> [String: Any] {
|
|
116
|
+
var user: [String: Any] = [:]
|
|
117
|
+
var ext: [String: Any] = [:]
|
|
118
|
+
|
|
119
|
+
if let consent = gdprConsentString {
|
|
120
|
+
ext["consent"] = consent
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if !ext.isEmpty {
|
|
124
|
+
user["ext"] = ext
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return user
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Key-value storage protocol for persisting SDK data
|
|
5
|
+
*/
|
|
6
|
+
internal protocol KeyValueStore {
|
|
7
|
+
/**
|
|
8
|
+
* Get a string value by key
|
|
9
|
+
*
|
|
10
|
+
* - Parameters:
|
|
11
|
+
* - key: Storage key
|
|
12
|
+
* - default: Default value if key doesn't exist
|
|
13
|
+
* - Returns: Stored value or default
|
|
14
|
+
*/
|
|
15
|
+
func getString(key: String, default: String?) -> String?
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Store a string value
|
|
19
|
+
*
|
|
20
|
+
* - Parameters:
|
|
21
|
+
* - key: Storage key
|
|
22
|
+
* - value: Value to store (pass nil to remove the key)
|
|
23
|
+
*/
|
|
24
|
+
func putString(key: String, value: String?)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Clear all stored data
|
|
28
|
+
*/
|
|
29
|
+
func clear()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* UserDefaults-based implementation of KeyValueStore
|
|
34
|
+
*
|
|
35
|
+
* Thread-safe implementation using iOS's UserDefaults.
|
|
36
|
+
* All keys are prefixed with "bigcrunch_ads_" for namespace isolation.
|
|
37
|
+
*/
|
|
38
|
+
internal class UserDefaultsStore: KeyValueStore {
|
|
39
|
+
|
|
40
|
+
private let userDefaults = UserDefaults.standard
|
|
41
|
+
private let keyPrefix = "bigcrunch_ads_"
|
|
42
|
+
|
|
43
|
+
func getString(key: String, default: String? = nil) -> String? {
|
|
44
|
+
let prefixedKey = keyPrefix + key
|
|
45
|
+
let value = userDefaults.string(forKey: prefixedKey) ?? `default`
|
|
46
|
+
if value != nil {
|
|
47
|
+
BCLogger.verbose("Retrieved value for key: \(key)")
|
|
48
|
+
}
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func putString(key: String, value: String?) {
|
|
53
|
+
let prefixedKey = keyPrefix + key
|
|
54
|
+
if let value = value {
|
|
55
|
+
userDefaults.set(value, forKey: prefixedKey)
|
|
56
|
+
BCLogger.verbose("Stored value for key: \(key)")
|
|
57
|
+
} else {
|
|
58
|
+
userDefaults.removeObject(forKey: prefixedKey)
|
|
59
|
+
BCLogger.verbose("Removed value for key: \(key)")
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func clear() {
|
|
64
|
+
let keys = userDefaults.dictionaryRepresentation().keys
|
|
65
|
+
let prefixedKeys = keys.filter { $0.hasPrefix(keyPrefix) }
|
|
66
|
+
|
|
67
|
+
for key in prefixedKeys {
|
|
68
|
+
userDefaults.removeObject(forKey: key)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
BCLogger.debug("Cleared all stored data")
|
|
72
|
+
}
|
|
73
|
+
}
|