Skip to content

Commit d8c90aa

Browse files
committed
wip
1 parent 11bb8e6 commit d8c90aa

File tree

6 files changed

+592
-3
lines changed

6 files changed

+592
-3
lines changed

ParseSwift.xcodeproj/project.pbxproj

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@
131131
7028373A26BD8C89007688C9 /* ParseUser+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7028373826BD8C89007688C9 /* ParseUser+async.swift */; };
132132
7028373B26BD8C89007688C9 /* ParseUser+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7028373826BD8C89007688C9 /* ParseUser+async.swift */; };
133133
7028373C26BD8C89007688C9 /* ParseUser+async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7028373826BD8C89007688C9 /* ParseUser+async.swift */; };
134+
7030E07E29BB8CDC0021970D /* ParseConfigCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7030E07D29BB8CDC0021970D /* ParseConfigCodable.swift */; };
135+
7030E07F29BB8CDC0021970D /* ParseConfigCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7030E07D29BB8CDC0021970D /* ParseConfigCodable.swift */; };
136+
7030E08029BB8CDC0021970D /* ParseConfigCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7030E07D29BB8CDC0021970D /* ParseConfigCodable.swift */; };
137+
7030E08129BB8CDC0021970D /* ParseConfigCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7030E07D29BB8CDC0021970D /* ParseConfigCodable.swift */; };
138+
7030E08729BBAF650021970D /* ParseConfigCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7030E08229BBAF250021970D /* ParseConfigCodableTests.swift */; };
139+
7030E08829BBAF660021970D /* ParseConfigCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7030E08229BBAF250021970D /* ParseConfigCodableTests.swift */; };
140+
7030E08929BBAF670021970D /* ParseConfigCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7030E08229BBAF250021970D /* ParseConfigCodableTests.swift */; };
134141
7031F356296F553200E077CC /* APICommandMultipleAttemptsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7031F355296F553200E077CC /* APICommandMultipleAttemptsTests.swift */; };
135142
7031F357296F553200E077CC /* APICommandMultipleAttemptsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7031F355296F553200E077CC /* APICommandMultipleAttemptsTests.swift */; };
136143
7031F358296F553200E077CC /* APICommandMultipleAttemptsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7031F355296F553200E077CC /* APICommandMultipleAttemptsTests.swift */; };
@@ -1195,6 +1202,8 @@
11951202
7023800E2747FCCD00EFC443 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = "<group>"; };
11961203
7028373326BD8883007688C9 /* ParseObject+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseObject+async.swift"; sourceTree = "<group>"; };
11971204
7028373826BD8C89007688C9 /* ParseUser+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseUser+async.swift"; sourceTree = "<group>"; };
1205+
7030E07D29BB8CDC0021970D /* ParseConfigCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigCodable.swift; sourceTree = "<group>"; };
1206+
7030E08229BBAF250021970D /* ParseConfigCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigCodableTests.swift; sourceTree = "<group>"; };
11981207
7031F355296F553200E077CC /* APICommandMultipleAttemptsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICommandMultipleAttemptsTests.swift; sourceTree = "<group>"; };
11991208
7033ECB025584A83009770F3 /* TestHostTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestHostTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
12001209
7033ECB225584A83009770F3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -1623,6 +1632,7 @@
16231632
917BA4312703E36800F8D747 /* ParseConfigAsyncTests.swift */,
16241633
7044C21F25C5E0160011F6E7 /* ParseConfigCombineTests.swift */,
16251634
70D1BE0625BB2BF400A42E7C /* ParseConfigTests.swift */,
1635+
7030E08229BBAF250021970D /* ParseConfigCodableTests.swift */,
16261636
91B40650267A66ED00B129CD /* ParseErrorTests.swift */,
16271637
89899DB426045DC4002E2043 /* ParseFacebookCombineTests.swift */,
16281638
89899CF32603CE9D002E2043 /* ParseFacebookTests.swift */,
@@ -1805,6 +1815,9 @@
18051815
916786E1259B7DDA00BB5B4E /* ParseCloudable.swift */,
18061816
703B090626BD9764005A112F /* ParseCloudable+async.swift */,
18071817
7044C17425C4ECFF0011F6E7 /* ParseCloudable+combine.swift */,
1818+
70D1BDB925BB17A600A42E7C /* ParseConfig.swift */,
1819+
703B090B26BD984D005A112F /* ParseConfig+async.swift */,
1820+
7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */,
18081821
70647E9B259E3A9A004C1004 /* ParseEncodable.swift */,
18091822
704E781628CFD8A00075F952 /* ParseFileTransferable.swift */,
18101823
70CE0AB6285A83B100DAEA86 /* ParseHookable.swift */,
@@ -2133,9 +2146,7 @@
21332146
70170A482656E2FE0070C905 /* ParseAnalytics+combine.swift */,
21342147
91285B122698DBF20051B544 /* ParseBytes.swift */,
21352148
709A149F2839CABD00BF85E5 /* ParseCLP.swift */,
2136-
70D1BDB925BB17A600A42E7C /* ParseConfig.swift */,
2137-
703B090B26BD984D005A112F /* ParseConfig+async.swift */,
2138-
7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */,
2149+
7030E07D29BB8CDC0021970D /* ParseConfigCodable.swift */,
21392150
704C886B28BE69A8008E6B01 /* ParseConfiguration.swift */,
21402151
F97B45BF24D9C6F200F4A88B /* ParseError.swift */,
21412152
709A148628396B1C00BF85E5 /* ParseField.swift */,
@@ -2734,6 +2745,7 @@
27342745
70A98D822794AB3C009B58F2 /* ParseQueryScorable.swift in Sources */,
27352746
70385E852858F9750084D306 /* ParseHookParametable.swift in Sources */,
27362747
70F79A192639CE6F00731C46 /* ParseHealth.swift in Sources */,
2748+
7030E07E29BB8CDC0021970D /* ParseConfigCodable.swift in Sources */,
27372749
70212D132854C82B00386163 /* ParsePushFirebaseNotification.swift in Sources */,
27382750
7044C19F25C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */,
27392751
F97B461E24D9C6F200F4A88B /* ParseStorage.swift in Sources */,
@@ -2895,6 +2907,7 @@
28952907
70F03A622780EADD00E5AFB4 /* ParseLinkedInCombineTests.swift in Sources */,
28962908
89899D9F26045998002E2043 /* ParseTwitterCombineTests.swift in Sources */,
28972909
70212D2B2855266400386163 /* ParsePushPayloadAppleTests.swift in Sources */,
2910+
7030E08729BBAF650021970D /* ParseConfigCodableTests.swift in Sources */,
28982911
917BA43A2703E6D800F8D747 /* ParseHealthAsyncTests.swift in Sources */,
28992912
917BA4462703EEA700F8D747 /* ParseAnonymousAsyncTests.swift in Sources */,
29002913
70F79A672639DE9700731C46 /* ParseHealthCombineTests.swift in Sources */,
@@ -3046,6 +3059,7 @@
30463059
70A98D832794AB3C009B58F2 /* ParseQueryScorable.swift in Sources */,
30473060
70385E862858F9750084D306 /* ParseHookParametable.swift in Sources */,
30483061
F97B461F24D9C6F200F4A88B /* ParseStorage.swift in Sources */,
3062+
7030E07F29BB8CDC0021970D /* ParseConfigCodable.swift in Sources */,
30493063
70212D142854C82B00386163 /* ParsePushFirebaseNotification.swift in Sources */,
30503064
7044C1AE25C4FC080011F6E7 /* Query+combine.swift in Sources */,
30513065
F97B45D324D9C6F200F4A88B /* AnyDecodable.swift in Sources */,
@@ -3216,6 +3230,7 @@
32163230
70F03A642780EADD00E5AFB4 /* ParseLinkedInCombineTests.swift in Sources */,
32173231
89899DA126045998002E2043 /* ParseTwitterCombineTests.swift in Sources */,
32183232
70212D332855266600386163 /* ParsePushPayloadAppleTests.swift in Sources */,
3233+
7030E08929BBAF670021970D /* ParseConfigCodableTests.swift in Sources */,
32193234
917BA43C2703E6D800F8D747 /* ParseHealthAsyncTests.swift in Sources */,
32203235
917BA4482703EEA700F8D747 /* ParseAnonymousAsyncTests.swift in Sources */,
32213236
70F79A692639DE9700731C46 /* ParseHealthCombineTests.swift in Sources */,
@@ -3333,6 +3348,7 @@
33333348
70F03A632780EADD00E5AFB4 /* ParseLinkedInCombineTests.swift in Sources */,
33343349
89899DA026045998002E2043 /* ParseTwitterCombineTests.swift in Sources */,
33353350
70212D2F2855266500386163 /* ParsePushPayloadAppleTests.swift in Sources */,
3351+
7030E08829BBAF660021970D /* ParseConfigCodableTests.swift in Sources */,
33363352
917BA43B2703E6D800F8D747 /* ParseHealthAsyncTests.swift in Sources */,
33373353
917BA4472703EEA700F8D747 /* ParseAnonymousAsyncTests.swift in Sources */,
33383354
70F79A682639DE9700731C46 /* ParseHealthCombineTests.swift in Sources */,
@@ -3484,6 +3500,7 @@
34843500
70A98D852794AB3C009B58F2 /* ParseQueryScorable.swift in Sources */,
34853501
70385E882858F9750084D306 /* ParseHookParametable.swift in Sources */,
34863502
7044C1A225C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */,
3503+
7030E08129BB8CDC0021970D /* ParseConfigCodable.swift in Sources */,
34873504
70212D162854C82B00386163 /* ParsePushFirebaseNotification.swift in Sources */,
34883505
F97B465124D9C78C00F4A88B /* Add.swift in Sources */,
34893506
7044C1B025C4FC080011F6E7 /* Query+combine.swift in Sources */,
@@ -3679,6 +3696,7 @@
36793696
70A98D842794AB3C009B58F2 /* ParseQueryScorable.swift in Sources */,
36803697
70385E872858F9750084D306 /* ParseHookParametable.swift in Sources */,
36813698
7044C1A125C4FA870011F6E7 /* ParseOperation+combine.swift in Sources */,
3699+
7030E08029BB8CDC0021970D /* ParseConfigCodable.swift in Sources */,
36823700
70212D152854C82B00386163 /* ParsePushFirebaseNotification.swift in Sources */,
36833701
F97B465024D9C78B00F4A88B /* Add.swift in Sources */,
36843702
7044C1AF25C4FC080011F6E7 /* Query+combine.swift in Sources */,
File renamed without changes.
File renamed without changes.

Sources/ParseSwift/Types/ParseConfig.swift renamed to Sources/ParseSwift/Protocols/ParseConfig.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import Foundation
1212
Objects that conform to the `ParseConfig` protocol are able to access the Config on the Parse Server.
1313
When conforming to `ParseConfig`, any properties added can be retrieved by the client or updated on
1414
the server. The current `ParseConfig` is persisted to the Keychain and Parse Server.
15+
- note: `ParseConfigCodable` or created types that conform
16+
`ParseConfigCodable` both access the same Config.
1517
*/
1618
public protocol ParseConfig: ParseTypeable {}
1719

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//
2+
// ParseConfigCodable.swift
3+
// ParseSwift
4+
//
5+
// Created by Corey Baker on 3/10/23.
6+
// Copyright © 2023 Network Reconnaissance Lab. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
`ParseConfigCodable` allows access to the Config on the Parse Server as a
13+
dictionary where the keys are **strings** and the values are **Codable**.
14+
The current `ParseConfig` is persisted to the Keychain and Parse Server.
15+
- note: `ParseConfigCodable` or created types that conform
16+
`ParseConfigCodable` both access the same Config.
17+
*/
18+
public struct ParseConfigCodable<V: Codable> {}
19+
20+
// MARK: Update
21+
extension ParseConfigCodable {
22+
23+
/**
24+
Fetch the Config *asynchronously*.
25+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
26+
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
27+
- parameter completion: A block that will be called when retrieving the config completes or fails.
28+
It should have the following argument signature: `(Result<Self, ParseError>)`.
29+
- note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer
30+
desires a different policy, it should be inserted in `options`.
31+
*/
32+
public static func fetch(options: API.Options = [],
33+
callbackQueue: DispatchQueue = .main,
34+
completion: @escaping (Result<[String: V], ParseError>) -> Void) {
35+
Task {
36+
var options = options
37+
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
38+
await fetchCommand()
39+
.execute(options: options,
40+
callbackQueue: callbackQueue,
41+
completion: completion)
42+
}
43+
}
44+
45+
internal static func fetchCommand() async -> API.NonParseBodyCommand<[String: V], [String: V]> {
46+
47+
return API.NonParseBodyCommand(method: .GET,
48+
path: .config) { (data) -> [String: V] in
49+
let fetched = try ParseCoding
50+
.jsonDecoder()
51+
.decode(ParseConfigCodableFetchResponse<V>.self, from: data).params
52+
await Self.updateKeychainIfNeeded(fetched)
53+
return fetched
54+
}
55+
}
56+
}
57+
58+
// MARK: Update
59+
extension ParseConfigCodable {
60+
61+
/**
62+
Update the Config *asynchronously*.
63+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
64+
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
65+
- parameter completion: A block that will be called when retrieving the config completes or fails.
66+
It should have the following argument signature: `(Result<Bool, ParseError>)`.
67+
- note: The default cache policy for this method is `.reloadIgnoringLocalCacheData`. If a developer
68+
desires a different policy, it should be inserted in `options`.
69+
*/
70+
public static func save(_ config: [String: V],
71+
options: API.Options = [],
72+
callbackQueue: DispatchQueue = .main,
73+
completion: @escaping (Result<Bool, ParseError>) -> Void) {
74+
Task {
75+
var options = options
76+
options.insert(.usePrimaryKey)
77+
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
78+
await updateCommand(config)
79+
.execute(options: options,
80+
callbackQueue: callbackQueue,
81+
completion: completion)
82+
}
83+
}
84+
85+
// swiftlint:disable:next line_length
86+
internal static func updateCommand(_ config: [String: V]) async -> API.NonParseBodyCommand<ParseConfigCodableUpdateBody<[String: V]>, Bool> {
87+
let body = ParseConfigCodableUpdateBody(params: config)
88+
return API.NonParseBodyCommand(method: .PUT, // MARK: Should be switched to ".PATCH" when server supports PATCH.
89+
path: .config,
90+
body: body) { (data) -> Bool in
91+
let updated = try ParseCoding.jsonDecoder().decode(BooleanResponse.self, from: data).result
92+
93+
if updated {
94+
await Self.updateKeychainIfNeeded(config)
95+
}
96+
return updated
97+
}
98+
}
99+
}
100+
101+
// MARK: Current
102+
extension ParseConfigCodable {
103+
104+
/**
105+
Gets/Sets properties of the current config in the Keychain.
106+
107+
- returns: Returns the latest `ParseConfig` on this device. If there is none, throws an error.
108+
- throws: An error of `ParseError` type.
109+
*/
110+
public static func current() async throws -> [String: V] {
111+
guard let container = await Self.currentContainer(),
112+
let config = container.currentConfig else {
113+
throw ParseError(code: .otherCause,
114+
message: "There is no current Config")
115+
}
116+
return config
117+
}
118+
119+
static func currentContainer() async -> CurrentConfigDictionaryContainer<V>? {
120+
await yieldIfNotInitialized()
121+
guard let configInMemory: CurrentConfigDictionaryContainer<V> =
122+
try? await ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentConfig) else {
123+
#if !os(Linux) && !os(Android) && !os(Windows)
124+
return try? await KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentConfig)
125+
#else
126+
return nil
127+
#endif
128+
}
129+
return configInMemory
130+
}
131+
132+
static func setCurrentContainer(_ newValue: CurrentConfigDictionaryContainer<V>?) async {
133+
try? await ParseStorage.shared.set(newValue, for: ParseStorage.Keys.currentConfig)
134+
#if !os(Linux) && !os(Android) && !os(Windows)
135+
try? await KeychainStore.shared.set(newValue, for: ParseStorage.Keys.currentConfig)
136+
#endif
137+
}
138+
139+
static func updateKeychainIfNeeded(_ result: [String: V], deleting: Bool = false) async {
140+
if !deleting {
141+
await Self.setCurrent(result)
142+
} else {
143+
await Self.deleteCurrentContainerFromKeychain()
144+
}
145+
}
146+
147+
internal static func deleteCurrentContainerFromKeychain() async {
148+
try? await ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentConfig)
149+
#if !os(Linux) && !os(Android) && !os(Windows)
150+
try? await KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentConfig)
151+
#endif
152+
}
153+
154+
static func setCurrent(_ current: [String: V]?) async {
155+
if await Self.currentContainer() == nil {
156+
await Self.setCurrentContainer(CurrentConfigDictionaryContainer<V>())
157+
}
158+
var currentContainer = await Self.currentContainer()
159+
currentContainer?.currentConfig = current
160+
await Self.setCurrentContainer(currentContainer)
161+
}
162+
}
163+
164+
struct CurrentConfigDictionaryContainer<T: Codable>: Codable {
165+
var currentConfig: [String: T]?
166+
}
167+
168+
// MARK: ParseConfigCodableUpdateBody
169+
internal struct ParseConfigCodableUpdateBody<T>: Codable where T: Codable {
170+
let params: T
171+
}
172+
173+
// MARK: ParseConfigCodableFetchResponse
174+
internal struct ParseConfigCodableFetchResponse<T>: Codable where T: Codable {
175+
let params: [String: T]
176+
}

0 commit comments

Comments
 (0)