Skip to content

Commit

Permalink
Switch Sync environment to production (#503)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/0/1205489036222324/f

Description:
This patch allows to control Sync server environment from client apps.
* ServerEnvironment enum is added
* DDGSync dependencies is now mutable
* Endpoints is now a class (it's shared between AccountManager and SyncQueue so it's handy
  to have it passed around as a reference), can be initialized with ServerEnvironment and can have
  baseURL updated after creation
* DDGSyncingDebuggingSupport and SyncDependenciesDebuggingSupport protocols
  were added to allow overriding server environment
  • Loading branch information
ayoy authored Sep 14, 2023
1 parent 7ae1806 commit 2a3dc29
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 67 deletions.
47 changes: 21 additions & 26 deletions Sources/DDGSync/DDGSync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@ public class DDGSync: DDGSyncing {
public static let bundle = Bundle.module

enum Constants {
// #if DEBUG
public static let baseUrl = URL(string: "https://dev-sync-use.duckduckgo.com")!
// #else
// public static let baseUrl = URL(string: "https://sync.duckduckgo.com")!
// #endif

public static let syncEnabledKey = "com.duckduckgo.sync.enabled"
}

Expand Down Expand Up @@ -59,8 +53,9 @@ public class DDGSync: DDGSyncing {
/// This is the constructor intended for use by app clients.
public convenience init(dataProvidersSource: DataProvidersSource,
errorEvents: EventMapping<SyncError>,
log: @escaping @autoclosure () -> OSLog = .disabled) {
let dependencies = ProductionDependencies(baseUrl: Constants.baseUrl, errorEvents: errorEvents, log: log())
log: @escaping @autoclosure () -> OSLog = .disabled,

This comment has been minimized.

Copy link
@Krapship

Krapship Sep 15, 2023

Absolutely: private

environment: ServerEnvironment = .production) {
let dependencies = ProductionDependencies(serverEnvironment: environment, errorEvents: errorEvents, log: log())
self.init(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
}

Expand Down Expand Up @@ -171,36 +166,36 @@ public class DDGSync: DDGSyncing {
}
}

public var serverEnvironment: ServerEnvironment {
if dependencies.endpoints.baseURL == ServerEnvironment.production.baseURL {
return .production
}
return .development
}

public func updateServerEnvironment(_ serverEnvironment: ServerEnvironment) {
try? updateAccount(nil)
dependencies.updateServerEnvironment(serverEnvironment)
authState = .initializing
initializeIfNeeded()
}

// MARK: -

let dependencies: SyncDependencies
var dependencies: SyncDependencies

init(dataProvidersSource: DataProvidersSource, dependencies: SyncDependencies) {
self.dataProvidersSource = dataProvidersSource
self.dependencies = dependencies
}

public func initializeIfNeeded(isInternalUser: Bool) {
public func initializeIfNeeded() {
guard authState == .initializing else { return }

let syncEnabled = dependencies.keyValueStore.object(forKey: Constants.syncEnabledKey) != nil
guard syncEnabled else {
// This is for initial tests only
if isInternalUser {
// Migrate and start using user defaults flag
do {
let account = try dependencies.secureStore.account()
authState = account?.state ?? .inactive
try updateAccount(account)

} catch {
dependencies.errorEvents.fire(.failedToMigrate, error: error)
}
} else {
try? dependencies.secureStore.removeAccount()
authState = .inactive
}

try? dependencies.secureStore.removeAccount()
authState = .inactive
return
}

Expand Down
48 changes: 43 additions & 5 deletions Sources/DDGSync/DDGSyncing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public protocol DataProvidersSource: AnyObject {
func makeDataProviders() -> [DataProviding]
}

public protocol DDGSyncing {
public protocol DDGSyncing: DDGSyncingDebuggingSupport {

var dataProvidersSource: DataProvidersSource? { get set }

Expand Down Expand Up @@ -87,7 +87,7 @@ public protocol DDGSyncing {
/**
Initializes Sync object, loads account info and prepares internal state.
*/
func initializeIfNeeded(isInternalUser: Bool)
func initializeIfNeeded()

/**
Creates an account.
Expand All @@ -111,7 +111,7 @@ public protocol DDGSyncing {
func login(_ recoveryKey: SyncCode.RecoveryKey, deviceName: String, deviceType: String) async throws -> [RegisteredDevice]

/**
Returns a device id and temporary secret key ready for display and allows callers attempt to fetch the transmitted recovery key.
Returns a device id and temporary secret key ready for display and allows callers attempt to fetch the transmitted recovery key.
*/
func remoteConnect() throws -> RemoteConnecting

Expand All @@ -129,7 +129,7 @@ public protocol DDGSyncing {
Disconnect the specified device from the sync service.

- Parameter deviceId: ID of the device to be disconnected.
*/
*/
func disconnect(deviceId: String) async throws

/**
Expand All @@ -138,15 +138,53 @@ public protocol DDGSyncing {
func fetchDevices() async throws -> [RegisteredDevice]

/**
Updated the device name.
Updated the device name.
*/
func updateDeviceName(_ name: String) async throws -> [RegisteredDevice]

/**
Deletes this account, but does not affect locally stored data.
*/
func deleteAccount() async throws
}

public protocol DDGSyncingDebuggingSupport {
var serverEnvironment: ServerEnvironment { get }
func updateServerEnvironment(_ serverEnvironment: ServerEnvironment)
}

public enum ServerEnvironment: LosslessStringConvertible {
case development
case production

var baseURL: URL {
switch self {
case .development:
return URL(string: "https://dev-sync-use.duckduckgo.com")!
case .production:
return URL(string: "https://sync.duckduckgo.com")!
}
}

public var description: String {
switch self {
case .development:
return "Development"
case .production:
return "Production"
}
}

public init?(_ description: String) {
switch description {
case "Development":
self = .development
case "Production":
self = .production
default:
return nil
}
}
}

public protocol Crypting {
Expand Down
59 changes: 41 additions & 18 deletions Sources/DDGSync/internal/Endpoints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@

import Foundation

struct Endpoints {
class Endpoints {

let signup: URL
let login: URL
let logoutDevice: URL
let connect: URL
private(set) var baseURL: URL

private(set) var signup: URL
private(set) var login: URL
private(set) var logoutDevice: URL
private(set) var connect: URL

private(set) var syncGet: URL
private(set) var syncPatch: URL

/// Constructs sync GET URL for specific data type(s), e.g. `sync/type1,type2,type3`
func syncGet(features: [String]) throws -> URL {
Expand All @@ -32,18 +37,36 @@ struct Endpoints {
}
return syncGet.appendingPathComponent(features.joined(separator: ","))
}

let syncGet: URL
let syncPatch: URL

init(baseUrl: URL) {
signup = baseUrl.appendingPathComponent("sync/signup")
login = baseUrl.appendingPathComponent("sync/login")
logoutDevice = baseUrl.appendingPathComponent("sync/logout-device")
connect = baseUrl.appendingPathComponent("sync/connect")

syncGet = baseUrl.appendingPathComponent("sync")
syncPatch = baseUrl.appendingPathComponent("sync/data")

convenience init(serverEnvironment: ServerEnvironment) {
self.init(baseURL: serverEnvironment.baseURL)
}


init(baseURL: URL) {
self.baseURL = baseURL
signup = baseURL.appendingPathComponent("sync/signup")
login = baseURL.appendingPathComponent("sync/login")
logoutDevice = baseURL.appendingPathComponent("sync/logout-device")
connect = baseURL.appendingPathComponent("sync/connect")

syncGet = baseURL.appendingPathComponent("sync")
syncPatch = baseURL.appendingPathComponent("sync/data")
}
}

// MARK: - Debugging Support

extension Endpoints {

func updateBaseURL(for serverEnvironment: ServerEnvironment) {
baseURL = serverEnvironment.baseURL
signup = baseURL.appendingPathComponent("sync/signup")
login = baseURL.appendingPathComponent("sync/login")
logoutDevice = baseURL.appendingPathComponent("sync/logout-device")
connect = baseURL.appendingPathComponent("sync/connect")

syncGet = baseURL.appendingPathComponent("sync")
syncPatch = baseURL.appendingPathComponent("sync/data")
}

}
11 changes: 7 additions & 4 deletions Sources/DDGSync/internal/ProductionDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ struct ProductionDependencies: SyncDependencies {
}
private let getLog: () -> OSLog

init(baseUrl: URL, errorEvents: EventMapping<SyncError>, log: @escaping @autoclosure () -> OSLog = .disabled) {
init(serverEnvironment: ServerEnvironment, errorEvents: EventMapping<SyncError>, log: @escaping @autoclosure () -> OSLog = .disabled) {

self.init(fileStorageUrl: FileManager.default.applicationSupportDirectoryForComponent(named: "Sync"),
baseUrl: baseUrl,
serverEnvironment: serverEnvironment,
keyValueStore: KeyValueStore(),
secureStore: SecureStorage(),
errorEvents: errorEvents,
Expand All @@ -48,14 +48,14 @@ struct ProductionDependencies: SyncDependencies {

init(
fileStorageUrl: URL,
baseUrl: URL,
serverEnvironment: ServerEnvironment,
keyValueStore: KeyValueStoring,
secureStore: SecureStoring,
errorEvents: EventMapping<SyncError>,
log: @escaping @autoclosure () -> OSLog = .disabled
) {
self.fileStorageUrl = fileStorageUrl
self.endpoints = Endpoints(baseUrl: baseUrl)
self.endpoints = Endpoints(serverEnvironment: serverEnvironment)
self.keyValueStore = keyValueStore
self.secureStore = secureStore
self.errorEvents = errorEvents
Expand All @@ -76,4 +76,7 @@ struct ProductionDependencies: SyncDependencies {
return RecoveryKeyTransmitter(endpoints: endpoints, api: api, storage: secureStore, crypter: crypter)
}

func updateServerEnvironment(_ serverEnvironment: ServerEnvironment) {
endpoints.updateBaseURL(for: serverEnvironment)
}
}
6 changes: 5 additions & 1 deletion Sources/DDGSync/internal/SyncDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import Foundation
import Combine
import Common

protocol SyncDependencies {
protocol SyncDependenciesDebuggingSupport {
func updateServerEnvironment(_ serverEnvironment: ServerEnvironment)
}

protocol SyncDependencies: SyncDependenciesDebuggingSupport {

var endpoints: Endpoints { get }
var account: AccountManaging { get }
Expand Down
18 changes: 9 additions & 9 deletions Tests/DDGSyncTests/DDGSyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ final class DDGSyncTests: XCTestCase {
dataProvidersSource.dataProviders = [dataProvider]

let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.requestSyncImmediately()
Expand Down Expand Up @@ -155,7 +155,7 @@ final class DDGSyncTests: XCTestCase {
dataProvidersSource.dataProviders = [dataProvider]

let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.requestSyncImmediately()
Expand Down Expand Up @@ -200,7 +200,7 @@ final class DDGSyncTests: XCTestCase {

dataProvidersSource.dataProviders = [bookmarksDataProvider, credentialsDataProvider]
let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.requestSyncImmediately()
Expand Down Expand Up @@ -235,7 +235,7 @@ final class DDGSyncTests: XCTestCase {

dataProvidersSource.dataProviders = [bookmarksDataProvider, credentialsDataProvider]
let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.requestSyncImmediately()
Expand Down Expand Up @@ -282,7 +282,7 @@ final class DDGSyncTests: XCTestCase {

dataProvidersSource.dataProviders = [bookmarksDataProvider, credentialsDataProvider]
let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.requestSyncImmediately()
Expand Down Expand Up @@ -316,7 +316,7 @@ final class DDGSyncTests: XCTestCase {
dataProvidersSource.dataProviders = [dataProvider]

let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()

isInProgressCancellable = syncService.isSyncInProgressPublisher.sink { [weak syncService] isInProgress in
if isInProgress {
Expand Down Expand Up @@ -364,7 +364,7 @@ final class DDGSyncTests: XCTestCase {
dataProvidersSource.dataProviders = [dataProvider]

let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.cancelSyncAndSuspendSyncQueue()
Expand All @@ -390,7 +390,7 @@ final class DDGSyncTests: XCTestCase {
dataProvidersSource.dataProviders = [dataProvider]

let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.cancelSyncAndSuspendSyncQueue()
Expand Down Expand Up @@ -420,7 +420,7 @@ final class DDGSyncTests: XCTestCase {
dataProvidersSource.dataProviders = [dataProvider]

let syncService = DDGSync(dataProvidersSource: dataProvidersSource, dependencies: dependencies)
syncService.initializeIfNeeded(isInternalUser: false)
syncService.initializeIfNeeded()
bindInProgressPublisher(for: syncService)

syncService.scheduler.requestSyncImmediately()
Expand Down
6 changes: 4 additions & 2 deletions Tests/DDGSyncTests/Mocks/Mocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ class MockKeyValueStore: KeyValueStoring {
}
}

struct MockSyncDepenencies: SyncDependencies {
var endpoints: Endpoints = Endpoints(baseUrl: URL(string: "https://dev.null")!)
struct MockSyncDepenencies: SyncDependencies, SyncDependenciesDebuggingSupport {
var endpoints: Endpoints = Endpoints(baseURL: URL(string: "https://dev.null")!)
var account: AccountManaging = AccountManagingMock()
var api: RemoteAPIRequestCreating = RemoteAPIRequestCreatingMock()
var secureStore: SecureStoring = SecureStorageStub()
Expand All @@ -163,6 +163,8 @@ struct MockSyncDepenencies: SyncDependencies {
func createRecoveryKeyTransmitter() throws -> RecoveryKeyTransmitting {
RecoveryKeyTransmitter(endpoints: endpoints, api: api, storage: secureStore, crypter: crypter)
}

func updateServerEnvironment(_ serverEnvironment: ServerEnvironment) {}
}

final class MockDataProvidersSource: DataProvidersSource {
Expand Down
Loading

0 comments on commit 2a3dc29

Please sign in to comment.