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

macapp: filter blocks user reqs if macapp awol #130

Merged
merged 3 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ codegen-typescript:

codegen: codegen-typescript codegen-swift

xcode:
macapp:
@open macapp/Xcode/Gertrude.xcodeproj

# ios

watch-ios:
@just watch-build iosapp/lib-ios

iosapp:
@open iosapp/Gertrude-iOS.xcodeproj

# api

watch-api:
Expand Down
4 changes: 2 additions & 2 deletions macapp/App/Sources/App/AdminWindow/AdminWindowFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,14 @@ extension AdminWindowFeature.RootReducer {
case .webview(.healthCheck(.enableFilterClicked)):
state.adminWindow.healthCheck.filterStatus = nil
return .merge(
.exec { try await startFilter($0) },
.exec { try await self.startFilter($0) },
self.withTimeoutAfter(seconds: 3)
)

case .webview(.healthCheck(.installFilterClicked)):
state.adminWindow.healthCheck.filterStatus = .installing
return .merge(
.exec { try await installFilter($0) },
.exec { try await self.installFilter($0) },
self.withTimeoutAfter(seconds: 20)
)

Expand Down
45 changes: 31 additions & 14 deletions macapp/App/Sources/App/AppReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ struct AppReducer: Reducer, Sendable {
enum CancelId {
case heartbeatInterval
case websocketMessages
case networkConnectionChanges
}

enum Action: Equatable, Sendable {
Expand Down Expand Up @@ -75,6 +76,7 @@ struct AppReducer: Reducer, Sendable {
case startProtecting(user: UserData)
case websocket(WebSocketFeature.Action)
case setTrustedTimestamp(TrustedTimestamp)
case networkConnectionChanged(connected: Bool)

indirect case adminAuthed(Action)
}
Expand All @@ -87,6 +89,7 @@ struct AppReducer: Reducer, Sendable {
@Dependency(\.network) var network
@Dependency(\.storage) var storage
@Dependency(\.websocket) var websocket
@Dependency(\.filterXpc) var xpc

var body: some ReducerOf<Self> {
Reduce<State, Action> { state, action in
Expand All @@ -98,7 +101,7 @@ struct AppReducer: Reducer, Sendable {
case .loadedPersistentState(.none):
state.onboarding.windowOpen = true
return .exec { [new = state.persistent] _ in
try await storage.savePersistentState(new)
try await self.storage.savePersistentState(new)
}

case .loadedPersistentState(.some(let persisted)):
Expand All @@ -122,43 +125,54 @@ struct AppReducer: Reducer, Sendable {
effects.append(.exec { [persist = state.persistent] _ in
var withoutResume = persist
withoutResume.resumeOnboarding = nil
try await storage.savePersistentState(withoutResume)
try await self.storage.savePersistentState(withoutResume)
})
}
return .merge(effects)

case .startProtecting(let user):
let onboardingWindowOpen = state.onboarding.windowOpen
return .merge(

.exec { [filterVersion = state.filter.version] send in
await api.setUserToken(user.token)
guard network.isConnected() else { return }
await self.api.setUserToken(user.token)
guard self.network.isConnected() else { return }
await send(.checkIn(
result: TaskResult { try await api.appCheckIn(filterVersion) },
result: TaskResult { try await self.api.appCheckIn(filterVersion) },
reason: .startProtecting
))
},

.exec { _ in
if onboardingWindowOpen == false, (await app.isLaunchAtLoginEnabled()) == false {
await app.enableLaunchAtLogin()
if onboardingWindowOpen == false, (await self.app.isLaunchAtLoginEnabled()) == false {
await self.app.enableLaunchAtLogin()
}
},

.publisher {
websocket.receive()
self.websocket.receive()
.map { .websocket(.receivedMessage($0)) }
.receive(on: mainQueue)
.receive(on: self.mainQueue)
}.cancellable(id: CancelId.websocketMessages),

.publisher {
self.network.connectionChanges()
.map { .networkConnectionChanged(connected: $0) }
.receive(on: self.mainQueue)
}.cancellable(id: CancelId.networkConnectionChanges),

.exec { send in
var numTicks = 0
for await _ in bgQueue.timer(interval: .seconds(60)) {
for await _ in self.bgQueue.timer(interval: .seconds(60)) {
numTicks += 1
for interval in heartbeatIntervals(for: numTicks) {
await send(.heartbeat(interval))
}
}
}.cancellable(id: CancelId.heartbeatInterval),

.exec { _ in
try await app.startRelaunchWatcher()
try await self.app.startRelaunchWatcher()
}
)

Expand All @@ -172,9 +186,9 @@ struct AppReducer: Reducer, Sendable {
return .exec { _ in
switch notification {
case .unexpectedError:
await device.notifyUnexpectedError()
await self.device.notifyUnexpectedError()
case .text(let title, let body):
await device.showNotification(title, body)
await self.device.showNotification(title, body)
}
}

Expand All @@ -184,7 +198,7 @@ struct AppReducer: Reducer, Sendable {
return .exec { [persist = state.persistent] _ in
var copy = persist
copy.resumeOnboarding = resume
try await storage.savePersistentState(copy)
try await self.storage.savePersistentState(copy)
}

case .onboarding(.delegate(.onboardingConfigComplete)):
Expand All @@ -199,6 +213,9 @@ struct AppReducer: Reducer, Sendable {
state.timestamp = timestamp
return .none

case .networkConnectionChanged(connected: true):
return .exec { _ in _ = await self.xpc.sendAlive() }

default:
return .none
}
Expand Down
6 changes: 5 additions & 1 deletion macapp/App/Sources/App/ApplicationFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ extension ApplicationFeature.RootReducer: RootReducing {
let setupState = await self.filterExtension.setup()
await send(.filter(.receivedState(setupState)))
if setupState.installed {
_ = await filterXpc.establishConnection()
_ = await self.filterXpc.establishConnection()
}
},

Expand Down Expand Up @@ -140,10 +140,14 @@ extension ApplicationFeature.RootReducer: RootReducing {
}
}

case .application(.didWake):
return .exec { _ in _ = await self.filterXpc.sendAlive() }

case .application(.willTerminate):
return .merge(
.cancel(id: AppReducer.CancelId.heartbeatInterval),
.cancel(id: AppReducer.CancelId.websocketMessages),
.cancel(id: AppReducer.CancelId.networkConnectionChanges),
.exec { _ in await self.app.stopRelaunchWatcher() }
)

Expand Down
52 changes: 26 additions & 26 deletions macapp/App/Sources/App/FilterControlling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,65 @@ protocol FilterControlling: RootReducing {

extension FilterControlling {
func installFilter(_ send: Send<Action>) async throws {
await api.securityEvent(.systemExtensionChangeRequested, "install")
_ = await filter.install()
try await mainQueue.sleep(for: .milliseconds(10))
let result = await xpc.establishConnection()
await self.api.securityEvent(.systemExtensionChangeRequested, "install")
_ = await self.filter.install()
try await self.mainQueue.sleep(for: .milliseconds(10))
let result = await self.xpc.establishConnection()
os_log("[G•] APP FilterControlling.installFilter() result: %{public}s", "\(result)")
await afterFilterChange(send, repairing: false)
await self.afterFilterChange(send, repairing: false)
}

func restartFilter(_ send: Send<Action>) async throws {
await api.securityEvent(.systemExtensionChangeRequested, "restart")
_ = await filter.restart()
try await mainQueue.sleep(for: .milliseconds(100))
let result = await xpc.establishConnection()
await self.api.securityEvent(.systemExtensionChangeRequested, "restart")
_ = await self.filter.restart()
try await self.mainQueue.sleep(for: .milliseconds(100))
let result = await self.xpc.establishConnection()
os_log("[G•] APP FilterControlling.restartFilter() result: %{public}s", "\(result)")
await afterFilterChange(send, repairing: false)
await self.afterFilterChange(send, repairing: false)
}

func startFilter(_ send: Send<Action>) async throws {
await api.securityEvent(.systemExtensionChangeRequested, "start")
_ = await filter.start()
try await mainQueue.sleep(for: .milliseconds(100))
let result = await xpc.establishConnection()
await self.api.securityEvent(.systemExtensionChangeRequested, "start")
_ = await self.filter.start()
try await self.mainQueue.sleep(for: .milliseconds(100))
let result = await self.xpc.establishConnection()
os_log("[G•] APP FilterControlling.startFilter() result: %{public}s", "\(result)")
await afterFilterChange(send, repairing: false)
await self.afterFilterChange(send, repairing: false)
}

func replaceFilter(
_ send: Send<Action>,
attempt: Int = 1,
reinstallOnFail: Bool = true
) async throws {
_ = await filter.replace()
await api.securityEvent(.systemExtensionChangeRequested, "replace")
var result = await xpc.establishConnection()
_ = await self.filter.replace()
await self.api.securityEvent(.systemExtensionChangeRequested, "replace")
var result = await self.xpc.establishConnection()
os_log(
"[G•] APP FilterControlling.replaceFilter() attempt: %{public}d, result: %{public}s",
attempt,
"\(result)"
)
await afterFilterChange(send, repairing: true)
await self.afterFilterChange(send, repairing: true)

// trying up to 4 times seems to get past some funky states fairly
// reliably, especially the one i observe locally only, where the filter
// shows up in an "orange" state in the system preferences pane
if attempt < 4, await xpc.notConnected() {
return try await self.replaceFilter(
if attempt < 4, await self.xpc.notConnected() {
return try await self.self.replaceFilter(
send,
attempt: attempt + 1,
reinstallOnFail: reinstallOnFail
)
}

if reinstallOnFail, await xpc.notConnected() {
if reinstallOnFail, await self.xpc.notConnected() {
os_log("[G•] APP FilterControlling.replaceFilter() failed, reinstalling")
_ = await filter.reinstall()
try await mainQueue.sleep(for: .milliseconds(500))
result = await xpc.establishConnection()
_ = await self.filter.reinstall()
try await self.mainQueue.sleep(for: .milliseconds(500))
result = await self.xpc.establishConnection()
os_log("[G•] APP FilterControlling.replaceFilter() final: %{public}s", "\(result)")
await afterFilterChange(send, repairing: false)
await self.afterFilterChange(send, repairing: false)
}
}
}
6 changes: 4 additions & 2 deletions macapp/App/Sources/App/FilterFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct FilterFeature: Feature {

struct Reducer: FeatureReducer {
@Dependency(\.api) var api
@Dependency(\.filterXpc) var xpc

func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
Expand Down Expand Up @@ -134,7 +135,7 @@ extension FilterFeature.RootReducer {
if let expiration = state.filter.currentSuspensionExpiration, expiration <= now {
state.filter.currentSuspensionExpiration = nil
}
return .none
return .exec { _ in _ = await self.xpc.sendAlive() }

case .heartbeat(.everyFiveMinutes):
let appVersionString = state.appUpdates.installedVersion
Expand Down Expand Up @@ -200,7 +201,8 @@ extension FilterFeature.RootReducer {
let installResult = await self.filterExtension.install()
switch installResult {
case .installedSuccessfully:
break
try await self.mainQueue.sleep(for: .milliseconds(10))
_ = await self.xpc.establishConnection()
case .timedOutWaiting:
// event `9ffabfe5` logged w/ more detail in FilterFeature.swift
await send(.focusedNotification(.filterInstallTimeout))
Expand Down
Loading