Skip to content

refactor: move NetworkStack to WireNetwork for re-use - WPB-17719 #3257

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

Draft
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

johnxnguyen
Copy link
Collaborator

Issue

In WireAuthentication we defined a NetworkStack that is initialized with a backend environment and is handles the validation of the environment (e.g checking if proxy credentials are required, resolving backend metadata). For multibackend support, we want to do much of the same thing when setting up the user session (also for the share extension and notification extension). This PR moves NetworkStack from WireAuthentication to WireNetwork so that it can be reused. It also updates the implementation so that it can be reused more generally and updates the main app and extensions to use the NetworkStack.

Some changes include:

  • Split out a WireNetworkInterface target that contains public types so other targets can depend only on types and not all the implementations of WireNetwork-
  • Delete some dead code (such as the WireNetwork.Assembly)

Testing

TODO


Checklist

  • Title contains a reference JIRA issue number like [WPB-XXX].
  • Description is filled and free of optional paragraphs.
  • Adds/updates automated tests.

UI accessibility checklist

If your PR includes UI changes, please utilize this checklist:

  • Make sure you use the API for UI elements that support large fonts.
  • All colors are taken from WireDesign.ColorTheme or constructed using WireDesign.BaseColorPalette.
  • New UI elements have Accessibility strings for VoiceOver.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved to WireNetwork

@@ -55,12 +56,13 @@ class DetermineAuthMethodComponent: Component<DetermineAuthMethodComponentDepend
email: String?,
canCreateAccount: Bool,
didDetectDomainConflict: Bool,
backendInfo: BackendInfo
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Here and elsewhere you'll see that BackendInfo has been replaced by BackendEnvironment2 (which will be renamed to BackendEnvironment), which will be the sole environment model going forward.

Comment on lines -32 to -38
public typealias WireAuthenticationBackendEnvironment = WireAuthenticationAPI.WireAuthenticationBackendEnvironment
public typealias BackendEnvironmentType = WireAuthenticationAPI.BackendEnvironmentType
public typealias BackendConfig = WireAuthenticationAPI.BackendConfig
public typealias Endpoints = WireAuthenticationAPI.Endpoints
public typealias ProxySettings = WireAuthenticationAPI.UnresolvedProxySettings
public typealias TrustData = WireAuthenticationAPI.TrustData
public typealias BackendMetadata = WireAuthenticationAPI.BackendMetadata
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These were all old models that are no longer needed, replaced by BackendEnvironment2

}

var proxyServer: String {
backendInfo.backendConfig.endpoints.backendURL.absoluteString
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was wrong (it's the backend rest api endpoint), I believe it should be the proxy host (right @KaterinaWire ?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These and the following deleted snapshots don't have a corresponding test, so the snapshots have been deleted.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These snapshot tests changed because I fixed a typo (there were some other tests that needed re-recording for non visible changes)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The client session component current holds a lot of the "new stack" for the user session. The changes here are:

  • we don't inject small pieces like network service, api version, etc...
  • instead we inject in what is needed which is AuthenticatedRESTAPI (see later in the pull request) to provide the api clients.

Comment on lines +107 to +108
public lazy var accountsAPI: some AccountsAPI = authenticatedRESTAPI.accountsAPI()
public lazy var conversationsAPI: some ConversationsAPI = authenticatedRESTAPI.conversationsAPI()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These two are public because they're still needed outside this component. There was some code in UI project that creates api clients to make use cases. For now I made these public, but in the future the UI project shouldn't be concerned with api clients.

@@ -316,7 +260,7 @@ public final class ClientSessionComponent {
store: userLocalStore
)

private lazy var pullSelfUserClientsSync = PullSelfUserClientsSync(
public lazy var pullSelfUserClientsSync = PullSelfUserClientsSync(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Similar to the last comment, it's public because there's some other target that is using it.

Comment on lines -28 to -30
private let backendEnvironment: WireNetwork.BackendEnvironment
private let minTLSVersion: WireNetwork.TLSVersion
private let apiVersion: WireNetwork.APIVersion
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We not just inject a NetworkStack which encapsulate all the set up.

Comment on lines +84 to +89
) async throws -> ClientSessionComponent {
let authenticatedRESTAPI = try await networkStack.authenticatedRESTAPI(
userID: selfUserID,
clientID: clientID,
cookieEncryptionKey: UserDefaults.cookiesKey()
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This needs to be async now, but I think it's only temporary. Deterministic session loading will ensure that the network stack is fully set up (which is async) before we have a user session.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the entry point for the new NSE. I've moved some of the dependencies around (e.g up to higher components and passing through to children via Needle).


public extension BackendEnvironment2 {

init(_ legacyEnvironment: WireTransport.BackendEnvironment) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All these mappings are temporary until we implement multibackend support and remove the legacy environments).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was dead

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved to WireNetworkInterface

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved to WireNetworkInterface

clientID: clientID,
cookieStorage: cookieStorage,
networkService: networkServices.rest,
onAuthenticationFailure: {} // TODO: network stack should bubble this up
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

issue: still TODO

import WireLogging
import WireNetworkInterface

// TODO: doc
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

issue: Still TODO


}

// TODO: move to network service
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

issue: still TODO

// along with this program. If not, see http://www.gnu.org/licenses/.
//

public struct AuthenticatedRESTAPI {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a factory for all api clients that need an APIService (which requires an AuthenticationManager). It's an async throws operation to create this factory because the api version needs to be resolved.

But once we have it we can create api clients when needed.

@@ -16,18 +16,14 @@
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation
public struct UnauthenticatedRESTAPI: Sendable {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Similar to AuthenticatedRESTAPI but doesn't need an api service, just a network service...


extension ProxySettings {

func proxyDictionary() -> [AnyHashable: Any] {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved from the model definition, it's only needed here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved from WireAuthentication.


private extension BackendEnvironment2 {

init(_ legacyEnvironment: WireTransport.BackendEnvironment) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is some duplication, but we'll remove when the legacy environment is retired when multibackend support is added.

Copy link
Contributor

Choose a reason for hiding this comment

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

minor suggestion: Add TODO with ticket in order not forget remove it here

asyncStreamEnabled: selfUserClient.asyncStreamCapable
)
} catch {
fatalError("failed to set up sync agent: \(error)")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

issue: intending to address this with deterministic session loading, but maybe need to an intermediate solution. For instance, what happens if there's no network? Will this fatalError?

…rk-wpb-17719

# Conflicts:
#	WireAuthentication/Tests/WireAuthenticationUITests/Resources/ReferenceImages/PersonalAccountCreationViewTests/testDynamicTypeVariants.xxxLarge.png
#	WireNetwork/Sources/WireNetwork/Models/Backend/BackendEnvironment2.swift
#	WireNetwork/Sources/WireNetworkInterface/Models/PinnedKey.swift
#	wire-ios-sync-engine/Source/SessionManager/SessionFactories.swift
#	wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift
Copy link
Contributor

Test Results

    4 files    449 suites   5m 6s ⏱️
3 171 tests 3 136 ✅ 27 💤 8 ❌
3 171 runs  3 138 ✅ 27 💤 6 ❌

For more details on these failures, see this check.

Results for commit ba33885.

@datadog-wireapp
Copy link

Datadog Report

Branch report: refactor/move-network-stack-to-wire-network-wpb-17719
Commit report: 763e16f
Test service: wire-ios-mono

❌ 6 Failed (0 Known Flaky), 3138 Passed, 27 Skipped, 2m 6.64s Total Time

❌ Failed Tests (6)

This report shows up to 5 failed tests.

  • test_didRegisterSelfUserClient_withConsumableNotificationsCapabableEnablesSyncV3() - UnitTests.ZMUserSessionTests - Details

    Expand for error
     Crash: WireSyncEngine Test Host (17682) <external symbol>
    
  • test_itCreatesMLSClientIfNeeded_AfterQuickSync() - UnitTests.ZMUserSessionTests - Details

    Expand for error
     Crash: WireSyncEngine Test Host (17739) <external symbol>
    
  • test_itPerformsPeriodicMLSUpdates_AfterQuickSync() - UnitTests.ZMUserSessionTests - Details

    Expand for error
     Crash: WireSyncEngine Test Host (17740) <external symbol>
    
  • testThatItNotfiesTheTransportSessionWhenSelfUserClientIsRegistered() - UnitTests.ZMUserSessionTests - Details

    Expand for error
     Crash: WireSyncEngine Test Host (17743) <external symbol>
    
  • testThatIsLoggedInIsFalseAtStartup() - UnitTests.ZMUserSessionTests_Authentication

Copy link
Contributor

@dmitrysimkin dmitrysimkin left a comment

Choose a reason for hiding this comment

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

Great work! Left couple of comments

Comment on lines +124 to +126
let authenticationAPI = try await networkStack
.unauthenticatedRESTAPI()
.authenticationAPI()
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: this looks odd, is it really needed unauthenticatedRESTAPI to make authenticationAPI? usually it's either authenticated client or unauthenticated, and you don't need one to create another.

And from client side if authenticated client needed, I'd hide unauthenticated detail to implementation. Not need for client to know that

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ah yes the naming is confusing... on one hand we have unauthenticatedRESTAPI as a container of api clients that don't need an authentication cookie or access token... i.e to make unauthenticated requests. One such api client is AuthenticationAPI that is used to login or register.

Probably though this is the only 'unauthenticated' api client so maybe there is no need to have an UnauthenticatedRESTAPI container. On the other hand, the AuthenticatedRESTAPI container has a lot more api clients and needs a user id.

Or perhaps we can find some better names to avoid the confusion. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm ok if authenticated and unauthenticated share some code and depend on each other (or one on another) in implementation part.
But not for clients, i think clients should not know about that, only thing they should care if it needs authenticated and unauthenticated client. You can hide this detail under some abstraction right?

Comment on lines -161 to +166
let authenticationAPI = try await networkStack.makeAuthenticationAPI()
let authenticationAPI = try await networkStack
.unauthenticatedRESTAPI()
.authenticationAPI()
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: I like make prefix, it adds clarity what it does which is being factory method. Just unauthenticatedRESTAPI() or authenticationAPI() is unclear to me is it wrapper, adapter or doing some side affect


private extension BackendEnvironment2 {

init(_ legacyEnvironment: WireTransport.BackendEnvironment) {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor suggestion: Add TODO with ticket in order not forget remove it here

@johnxnguyen johnxnguyen marked this pull request as draft June 26, 2025 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants