Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify Configuration Management #241

Merged
merged 35 commits into from
Mar 8, 2023
Merged

Unify Configuration Management #241

merged 35 commits into from
Mar 8, 2023

Conversation

jaceklyp
Copy link
Contributor

@jaceklyp jaceklyp commented Feb 22, 2023

Task/Issue URL: https://app.asana.com/0/72649045549333/1203797041821336/f
iOS PR: duckduckgo/iOS#1512
macOS PR: duckduckgo/macos-browser#990
What kind of version bump will this require?: Major

Description:
Use common API and Configuration modules located in BSK for fetching privacy related assets.
Add payload validation.

Steps to test this PR:

  1. Run tests
  2. Check if app downloads assets.
  3. Check if app sends etag in requests.
  4. Check if app handles 304s.
  5. Check if app stores the resources.

Internal references:

Software Engineering Expectations
Technical Design Template
When ready for review, remember to post the PR in MM

@ayoy
Copy link
Contributor

ayoy commented Feb 23, 2023

Hi @jaceklyp! I'm getting around to checking your code. Have just compiled and run it for the first time and I figured it would be great to have more debug logging for network requests. Perhaps controlled by a boolean flag. WDYT?

@ayoy ayoy self-assigned this Feb 23, 2023

public var defaultHeaders: HTTPHeaders {
guard let userAgent = APIHeaders.userAgent else {
fatalError("Please set the userAgent before accessing defaultHeaders.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is time-sensitive, based on when setUserAgent is called, right? Shouldn't it rather be an assertion, to not crash the app on production?

import Foundation
import Common

public enum URLRequestAttribution {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that used? Was that used? :)

case trace = "TRACE"
case patch = "PATCH"

}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these be put in a tighter scope instead of on Module level? E.g. under APIRequest


import Foundation

public protocol ConfigurationURLProvider {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConfigurationURLProviding ?

enum Constants {

static let weakEtagPrefix = "W/"
static let successfulStatusCodes = Array(200..<300)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use Range instead?

}
}

func etag(shouldDropWeakPrefix: Bool) -> String? {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

droppingWeakPrefix ?

Copy link
Contributor

@ayoy ayoy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed I’ve had 2 pending comments. Both rather minor, but perhaps worth considering. Dropping them now before I jump into reviewing the rest.


public static let nonEmptyData = APIResponseRequirements(rawValue: 1 << 0)
public static let etag = APIResponseRequirements(rawValue: 1 << 1)
public static let allow304 = APIResponseRequirements(rawValue: 1 << 2) // setting this overrides nonEmptyData since urlSession will actually return empty data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to either have docs for all the options, or user more descriptive naming. Ideally both :) I'd like the options to tell what the Requirement actually enforces, i.e:

  • requireNonEmptyData
  • requireETagHeader
  • allowHTTPNotModified

return (data, httpResponse)
}

private func getHTTPResponse(from response: URLResponse?) throws -> HTTPURLResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could remove this method and just define URLResponse.asHTTPURLResponse as throwable function.

Copy link
Contributor

@ayoy ayoy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 👍 Leaving a bunch of final comments, I’ve also tested macOS and it works great. Will check the code now.

Comment on lines 74 to 88
/**
Downloads and stores the configurations provided in parallel.

- Parameters:
- configurations: An array of `Configuration` enums that need to be downloaded and stored.

- Throws:
If any configuration fails to fetch or validate, an `Error` of type `.aggregated` is thrown.
The `.aggregated` case of the `Error` enum contains a dictionary of type `[Configuration: Error]`
that associates each failed configuration with its corresponding error.

- Important:
If any task fails, the error is recorded but the group continues processing the remaining tasks.
The task group is not cancelled automatically when a task throws an error.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now this documentation applies to aggregatedError (when you option+click it). Please move aggregatedError elsewhere so that it applies to fetch(any:).


protocol ConfigurationFetching {

func fetch(any configurations: [Configuration]) async throws
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d suggest fetch(anyOf:) and fetch(allOf:) but I’m not strong on that.

self.urlSession = urlSession
self.log = log

assertUserAgentIsPresent()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

}
}

/// This method is deprecated. Please use the 'fetch()' async method instead.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it’s only used on iOS, perhaps worth guarding this function with #if os(iOS)?
Or maybe add the deprecation notice to the os_log too?

Comment on lines 33 to 34
/// Allows HTTP Not Modified responses.
/// Setting this overrides requireNonEmptyData since urlSession will actually return empty data.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the following docs:

Allows HTTP 304 (Not Modified) response status code.
When this is set, requireNonEmptyData is not honored, since URLSession returns empty data on HTTP 304.


let privacyConfigurationData = Data("Privacy Config".utf8)

// Tests for fetch(any: [Configuration])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cosmetic: I'd use // MARK: - here

private static let mainThreadCallback = URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue.main)
private static let mainThreadCallbackEphemeral = URLSession(configuration: .ephemeral, delegate: nil, delegateQueue: OperationQueue.main)

public static func makeSession(useMainThreadCallbackQueue: Bool = false, ephemeral: Bool = true) -> URLSession {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing, sorry – I’d avoid calling this function makeSession, because it’s not making anything. It only picks one of the existing sessions.

I’ve noticed it while reading through macOS app code where it looks like we were creating sessions with every call. How about just session(useMainThreadCallbackQueue: Bool = false, ephemeral: Bool = true)?

@ayoy ayoy assigned jaceklyp and unassigned ayoy Mar 8, 2023
@jaceklyp jaceklyp merged commit c10bf52 into main Mar 8, 2023
@jaceklyp jaceklyp deleted the jacek/unify-configuration branch March 8, 2023 11:59
samsymons added a commit that referenced this pull request Apr 4, 2023
# By Alexey Martemyanov (8) and others
# Via GitHub
* main:
  Make FeatureName a struct so it can be extended from client code (#276)
  add count of all bookmarks in domain to view model (#272)
  Configuration updates optimizations (#269)
  Roll-back InteractiveUserScript (#268)
  Update C-S-S to 4.4.4 (#260)
  Add invalid payload pixel handler (#264)
  Fix Error pixel misfiring on valid scenario (#261)
  add didCancelNavigationAction (#262)
  Opt-outable private API usage (#254)
  HTTPS upgrade threading (#256)
  DDGSync module (#253)
  fix delayed new window with empty url creation handling (#255)
  remove major tracker network (#251)
  Enable username&password passed in URL (#245)
  fix assertion for popup about:blank navigations without WKNavigation (#247)
  fix about: scheme fragments dropping (#246)
  Unify Configuration Management (#241)

# Conflicts:
#	Package.resolved
#	Package.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants