Skip to content

Commit 78b3836

Browse files
authored
Merge branch 'trunk' into task/WOOMOB-1025-two-panel-settings-view
2 parents cf5d8c7 + e60ecaa commit 78b3836

39 files changed

+608
-312
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<!--
22
Contains editorialized release notes. Raw release notes should go into `RELEASE-NOTES.txt`.
33
-->
4+
## 23.1
5+
Managing your store just got smoother with a more reliable shipping label workflow! Jetpack setup has also been improved for a faster, easier connection. Update now and enjoy the improvements!
6+
47
## 23.0
58
Faster performance and smoother experience! This update brings improved product loading, better tax calculations, and enhanced address lookup with map support. We've added guided barcode scanning for POS users and strengthened shipping requirements for EU destinations. Plus, we've fixed stability issues for a more reliable experience.
69

Modules/Sources/Experiments/DefaultFeatureFlagService.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,25 +88,18 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
8888
return true
8989
case .productImageOptimizedHandling:
9090
return true
91-
case .pointOfSaleBarcodeScanningi1:
92-
return true
93-
case .showPointOfSaleBarcodeSimulator:
94-
// Enables a simulated barcode scanner in dev builds for testing. Do not ship this one!
95-
return false
96-
case .pointOfSaleAsATabi1:
97-
return true
9891
case .pointOfSaleAsATabi2:
9992
return true
10093
case .pointOfSaleOrdersi1:
10194
return true
10295
case .pointOfSaleOrdersi2:
10396
return true
104-
case .pointOfSaleBarcodeScanningi2:
105-
return true
10697
case .pointOfSaleSettingsi1:
10798
return buildConfig == .localDeveloper || buildConfig == .alpha
10899
case .orderAddressMapSearch:
109100
return true
101+
case .pointOfSaleHistoricalOrdersi1:
102+
return buildConfig == .localDeveloper || buildConfig == .alpha
110103
default:
111104
return true
112105
}

Modules/Sources/Experiments/FeatureFlag.swift

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -183,18 +183,6 @@ public enum FeatureFlag: Int {
183183
///
184184
case pointOfSaleReceipts
185185

186-
/// Enables barcode scanning with an external scanner in POS
187-
///
188-
case pointOfSaleBarcodeScanningi1
189-
190-
/// Enables a simulated barcode scanner for testing in POS. Do not ship this one!
191-
///
192-
case showPointOfSaleBarcodeSimulator
193-
194-
/// Enables displaying POS as a tab in the tab bar with the same eligibility as the previous entry point
195-
///
196-
case pointOfSaleAsATabi1
197-
198186
/// Enables displaying POS as a tab in the tab bar for stores in eligible countries
199187
///
200188
case pointOfSaleAsATabi2
@@ -207,15 +195,15 @@ public enum FeatureFlag: Int {
207195
///
208196
case pointOfSaleOrdersi2
209197

210-
/// Enables the Point of Sale Barcode Scanner set up flows, as part of i2
211-
///
212-
case pointOfSaleBarcodeScanningi2
213-
214198
/// Enables the entry point for Point of Sale Settings
215199
///
216200
case pointOfSaleSettingsi1
217201

218202
/// Enables the CTA to search for an address in the map in order details > shipping address.
219203
///
220204
case orderAddressMapSearch
205+
206+
/// Enables the entry point for Point of Sale Orders
207+
///
208+
case pointOfSaleHistoricalOrdersi1
221209
}

Modules/Sources/Fakes/Networking.generated.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1786,7 +1786,8 @@ extension Networking.SiteAPI {
17861786
public static func fake() -> Networking.SiteAPI {
17871787
.init(
17881788
siteID: .fake(),
1789-
namespaces: .fake()
1789+
namespaces: .fake(),
1790+
applicationPasswordAvailable: .fake()
17901791
)
17911792
}
17921793
}

Modules/Sources/Networking/Model/SiteAPI.swift

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public struct SiteAPI: Decodable, Equatable, GeneratedFakeable {
1313
///
1414
public let namespaces: [String]
1515

16+
/// Whether application password authentication is available
17+
///
18+
public let applicationPasswordAvailable: Bool
19+
1620
/// Highest Woo API version installed on the site
1721
///
1822
public var highestWooVersion: WooAPIVersion {
@@ -42,15 +46,18 @@ public struct SiteAPI: Decodable, Equatable, GeneratedFakeable {
4246

4347
let siteAPIContainer = try decoder.container(keyedBy: SiteAPIKeys.self)
4448
let namespaces = siteAPIContainer.failsafeDecodeIfPresent([String].self, forKey: .namespaces) ?? []
49+
let authentication = try? siteAPIContainer.decode(Authentication.self, forKey: .authentication)
50+
let applicationPasswordAvailable = authentication?.applicationPasswords?.endpoints?.authorization != nil
4551

46-
self.init(siteID: siteID, namespaces: namespaces)
52+
self.init(siteID: siteID, namespaces: namespaces, applicationPasswordAvailable: applicationPasswordAvailable)
4753
}
4854

4955
/// Designated Initializer.
5056
///
51-
public init(siteID: Int64, namespaces: [String]) {
57+
public init(siteID: Int64, namespaces: [String], applicationPasswordAvailable: Bool) {
5258
self.siteID = siteID
5359
self.namespaces = namespaces
60+
self.applicationPasswordAvailable = applicationPasswordAvailable
5461
}
5562
}
5663

@@ -60,7 +67,23 @@ public struct SiteAPI: Decodable, Equatable, GeneratedFakeable {
6067
private extension SiteAPI {
6168

6269
enum SiteAPIKeys: String, CodingKey {
63-
case namespaces = "namespaces"
70+
case namespaces
71+
case authentication
72+
}
73+
74+
struct Authentication: Decodable {
75+
let applicationPasswords: ApplicationPasswords?
76+
enum CodingKeys: String, CodingKey {
77+
case applicationPasswords = "application-passwords"
78+
}
79+
}
80+
81+
struct ApplicationPasswords: Decodable {
82+
let endpoints: Endpoints?
83+
}
84+
85+
struct Endpoints: Decodable {
86+
let authorization: String?
6487
}
6588
}
6689

Modules/Sources/Networking/Model/SiteSettingGroup.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public enum SiteSettingGroup: Decodable, Hashable, GeneratedFakeable {
77
case general
88
case product
99
case advanced
10+
case pointOfSale
1011
case custom(String) // catch-all
1112
}
1213

@@ -25,6 +26,8 @@ extension SiteSettingGroup: RawRepresentable {
2526
self = .product
2627
case Keys.advanged:
2728
self = .advanced
29+
case Keys.pointOfSale:
30+
self = .pointOfSale
2831
default:
2932
self = .custom(rawValue)
3033
}
@@ -37,6 +40,7 @@ extension SiteSettingGroup: RawRepresentable {
3740
case .general: return Keys.general
3841
case .product: return Keys.product
3942
case .advanced: return Keys.advanged
43+
case .pointOfSale: return Keys.pointOfSale
4044
case .custom(let payload): return payload
4145
}
4246
}
@@ -49,4 +53,5 @@ private enum Keys {
4953
static let general = "general"
5054
static let product = "product"
5155
static let advanged = "advanced"
56+
static let pointOfSale = "point-of-sale"
5257
}

Modules/Sources/Networking/Remote/SiteAPIRemote.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class SiteAPIRemote: Remote {
2020
siteID: siteID,
2121
path: path,
2222
parameters: parameters,
23-
availableAsRESTRequest: true)
23+
availableAsRESTRequest: false)
2424
let mapper = SiteAPIMapper(siteID: siteID)
2525

2626
enqueue(request, mapper: mapper, completion: completion)

Modules/Sources/Networking/Remote/SiteSettingsRemote.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,26 @@ public class SiteSettingsRemote: Remote {
5252
enqueue(request, mapper: mapper, completion: completion)
5353
}
5454

55+
/// Retrieves all of the point of sale `SiteSetting`s for a given site.
56+
///
57+
/// - Parameters:
58+
/// - siteID: Site for which we'll fetch the point of sale settings.
59+
/// - Returns: An array of SiteSettings, specific to point of sale.
60+
/// - Throws: Error if the request fails.
61+
///
62+
public func loadPointOfSaleSettings(for siteID: Int64) async throws -> [SiteSetting] {
63+
let path = Constants.siteSettingsPath + Constants.pointOfSaleSettingsGroup
64+
let request = JetpackRequest(wooApiVersion: .mark3,
65+
method: .get,
66+
siteID: siteID,
67+
path: path,
68+
parameters: nil,
69+
availableAsRESTRequest: true)
70+
let mapper = SiteSettingsMapper(siteID: siteID, settingsGroup: SiteSettingGroup.pointOfSale)
71+
72+
return try await enqueue(request, mapper: mapper)
73+
}
74+
5575
/// Retrieve detail for a single setting for a given site
5676
///
5777
/// - Parameters:
@@ -179,6 +199,7 @@ private extension SiteSettingsRemote {
179199
static let generalSettingsGroup: String = "general"
180200
static let productSettingsGroup: String = "products"
181201
static let advancedSettingsGroup: String = "advanced"
202+
static let pointOfSaleSettingsGroup: String = "point-of-sale"
182203
static let valueParameter: String = "value"
183204
static let featureEnabledValue: String = "yes"
184205
static let featureDisabledValue: String = "no"

Modules/Sources/NetworkingCore/ApplicationPassword/ApplicationPasswordUseCase.swift

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import enum Alamofire.AFError
3+
import struct Alamofire.HTTPMethod
34
import KeychainAccess
45

56
#if canImport(UIKit)
@@ -34,13 +35,20 @@ public protocol ApplicationPasswordUseCase {
3435
}
3536

3637
final public class DefaultApplicationPasswordUseCase: ApplicationPasswordUseCase {
37-
/// Site Address
38+
/// Authentication type
3839
///
39-
private let siteAddress: String
40+
private let authenticationType: AuthenticationType
4041

4142
/// WPOrg username
4243
///
43-
private let username: String
44+
private var username: String {
45+
switch authenticationType {
46+
case .wporg(let username, _, _):
47+
return username
48+
case .wpcom(let wporgUsername, _):
49+
return wporgUsername
50+
}
51+
}
4452

4553
/// To generate and delete application password
4654
///
@@ -63,13 +71,23 @@ final public class DefaultApplicationPasswordUseCase: ApplicationPasswordUseCase
6371
#endif
6472
}
6573

74+
/// Internal initializer
75+
/// periphery: ignore - used in future PR for WOOMOB-1123
76+
init(type: AuthenticationType,
77+
network: Network,
78+
keychain: Keychain = Keychain(service: WooConstants.keychainServiceName)) {
79+
self.authenticationType = type
80+
self.storage = ApplicationPasswordStorage(keychain: keychain)
81+
self.network = network
82+
}
83+
84+
/// Public initializer for wporg authentication
6685
public init(username: String,
6786
password: String,
6887
siteAddress: String,
6988
network: Network? = nil,
7089
keychain: Keychain = Keychain(service: WooConstants.keychainServiceName)) throws {
71-
self.siteAddress = siteAddress
72-
self.username = username
90+
self.authenticationType = .wporg(username: username, password: password, siteAddress: siteAddress)
7391
self.storage = ApplicationPasswordStorage(keychain: keychain)
7492

7593
if let network {
@@ -143,6 +161,24 @@ final public class DefaultApplicationPasswordUseCase: ApplicationPasswordUseCase
143161
}
144162

145163
private extension DefaultApplicationPasswordUseCase {
164+
/// Helper method to construct network requests either directly with the remote site
165+
/// or through Jetpack proxy.
166+
func constructRequest(method: HTTPMethod, path: String, parameters: [String: Any]? = nil) -> Request {
167+
switch authenticationType {
168+
case .wpcom(_, let siteID):
169+
JetpackRequest(wooApiVersion: .none,
170+
method: method,
171+
siteID: siteID,
172+
path: path,
173+
parameters: parameters)
174+
case .wporg(_, _, let siteAddress):
175+
RESTRequest(siteURL: siteAddress,
176+
method: method,
177+
path: path,
178+
parameters: parameters)
179+
}
180+
}
181+
146182
/// Creates application password using WordPress.com authentication token
147183
///
148184
/// - Returns: Generated `ApplicationPassword`
@@ -151,7 +187,9 @@ private extension DefaultApplicationPasswordUseCase {
151187
let passwordName = applicationPasswordName
152188

153189
let parameters = [ParameterKey.name: passwordName]
154-
let request = RESTRequest(siteURL: siteAddress, method: .post, path: Path.applicationPasswords, parameters: parameters)
190+
let request = constructRequest(method: .post,
191+
path: Path.applicationPasswords,
192+
parameters: parameters)
155193
return try await withCheckedThrowingContinuation { continuation in
156194
network.responseData(for: request) { [weak self] result in
157195
guard let self else { return }
@@ -190,7 +228,7 @@ private extension DefaultApplicationPasswordUseCase {
190228
/// Get the UUID of the application password
191229
///
192230
func fetchUUIDForApplicationPassword(_ passwordName: String) async throws -> String {
193-
let request = RESTRequest(siteURL: siteAddress, method: .get, path: Path.applicationPasswords)
231+
let request = constructRequest(method: .get, path: Path.applicationPasswords)
194232

195233
return try await withCheckedThrowingContinuation { continuation in
196234
network.responseData(for: request) { result in
@@ -217,7 +255,7 @@ private extension DefaultApplicationPasswordUseCase {
217255
/// Deletes application password using UUID
218256
///
219257
func deleteApplicationPassword(_ uuid: String) async throws {
220-
let request = RESTRequest(siteURL: siteAddress, method: .delete, path: Path.applicationPasswords + "/" + uuid)
258+
let request = constructRequest(method: .delete, path: Path.applicationPasswords + "/" + uuid)
221259

222260
try await withCheckedThrowingContinuation { continuation in
223261
network.responseData(for: request) { result in
@@ -232,6 +270,13 @@ private extension DefaultApplicationPasswordUseCase {
232270
}
233271
}
234272

273+
extension DefaultApplicationPasswordUseCase {
274+
enum AuthenticationType {
275+
case wporg(username: String, password: String, siteAddress: String)
276+
case wpcom(wporgUsername: String, siteID: Int64)
277+
}
278+
}
279+
235280
// MARK: - Constants
236281
//
237282
private extension DefaultApplicationPasswordUseCase {

Modules/Sources/NetworkingCore/ApplicationPassword/RequestProcessor.swift

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ extension RequestProcessor: RequestRetrier {
4343

4444
requestsToRetry.append(completion)
4545
if !isAuthenticating {
46+
isAuthenticating = true
4647
generateApplicationPassword()
4748
}
4849
}
@@ -53,8 +54,6 @@ extension RequestProcessor: RequestRetrier {
5354
private extension RequestProcessor {
5455
func generateApplicationPassword() {
5556
Task(priority: .medium) {
56-
isAuthenticating = true
57-
5857
do {
5958
let _ = try await requestAuthenticator.generateApplicationPassword()
6059
isAuthenticating = false
@@ -75,17 +74,14 @@ private extension RequestProcessor {
7574
}
7675

7776
func shouldRetry(_ error: Error) -> Bool {
78-
// Need to generate application password
79-
if .applicationPasswordNotAvailable == error as? RequestAuthenticatorError {
77+
switch error {
78+
case RequestAuthenticatorError.applicationPasswordNotAvailable,
79+
AFError.requestAdaptationFailed(RequestAuthenticatorError.applicationPasswordNotAvailable),
80+
AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: 401)): // Failed authorization
8081
return true
82+
default:
83+
return false
8184
}
82-
83-
// Failed authorization
84-
if case .responseValidationFailed(reason: .unacceptableStatusCode(code: 401)) = error as? AFError {
85-
return true
86-
}
87-
88-
return false
8985
}
9086

9187
func completeRequests(_ shouldRetry: Bool) {

0 commit comments

Comments
 (0)