Skip to content

Commit

Permalink
Merge pull request #130 from gertrude-app/filter-polices-app
Browse files Browse the repository at this point in the history
macapp: filter blocks user reqs if macapp awol
  • Loading branch information
jaredh159 authored Dec 31, 2024
2 parents 6bdc689 + ab3441a commit 128970c
Show file tree
Hide file tree
Showing 37 changed files with 896 additions and 260 deletions.
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

0 comments on commit 128970c

Please sign in to comment.