Skip to content

Commit

Permalink
Refactor external bus messages and add tests for it
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoncal committed Nov 27, 2023
1 parent 7f5910f commit bde2378
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 71 deletions.
16 changes: 16 additions & 0 deletions HomeAssistant.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@
42CA28BB2B1028330093B31A /* MockThreadClientService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28BA2B1028330093B31A /* MockThreadClientService.swift */; };
42DD84112B14A6E700936F16 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B15042273188300635D5C /* Assets.swift */; };
42DD84132B14ACAB00936F16 /* Color+ColorAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42DD84122B14ACAB00936F16 /* Color+ColorAsset.swift */; };
42DD84162B14D7AC00936F16 /* WebViewExternalBusMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42DD84142B14D68C00936F16 /* WebViewExternalBusMessage.swift */; };
42DD84192B14D83B00936F16 /* WebViewExternalBusMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42DD84182B14D83B00936F16 /* WebViewExternalBusMessageTests.swift */; };
42F5CAB92B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F5CAB82B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift */; };
42F5CABC2B10AE1A00409816 /* ServerFixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F5CABB2B10AE1A00409816 /* ServerFixture.swift */; };
42F5CAD02B10BE1E00409816 /* Roboto-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 42F5CAC32B10BE1D00409816 /* Roboto-BoldItalic.ttf */; };
Expand Down Expand Up @@ -1605,6 +1607,8 @@
42CA28B72B10279D0093B31A /* ThreadClientService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadClientService.swift; sourceTree = "<group>"; };
42CA28BA2B1028330093B31A /* MockThreadClientService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockThreadClientService.swift; sourceTree = "<group>"; };
42DD84122B14ACAB00936F16 /* Color+ColorAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+ColorAsset.swift"; sourceTree = "<group>"; };
42DD84142B14D68C00936F16 /* WebViewExternalBusMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewExternalBusMessage.swift; sourceTree = "<group>"; };
42DD84182B14D83B00936F16 /* WebViewExternalBusMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewExternalBusMessageTests.swift; sourceTree = "<group>"; };
42F5CAB82B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadCredentialsSharingViewModelTests.swift; sourceTree = "<group>"; };
42F5CABB2B10AE1A00409816 /* ServerFixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerFixture.swift; sourceTree = "<group>"; };
42F5CAC32B10BE1D00409816 /* Roboto-BoldItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Roboto-BoldItalic.ttf"; path = "../../../../../../Downloads/Roboto/Roboto-BoldItalic.ttf"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2625,11 +2629,13 @@
11AD2E2A2528FDB700FBC437 /* WebView */ = {
isa = PBXGroup;
children = (
42DD84172B14D83400936F16 /* Tests */,
11DE822F24FAE66F00E636B8 /* UIWindow+Additions.swift */,
113FB1122515A065000AC680 /* ScaleFactorMutator.swift */,
11DE822D24FAC51000E636B8 /* IncomingURLHandler.swift */,
B64BB3A71E9C6551001E8B46 /* WebViewController.swift */,
11EFCDD224F5F39100314D85 /* WebViewWindowController.swift */,
42DD84142B14D68C00936F16 /* WebViewExternalBusMessage.swift */,
);
path = WebView;
sourceTree = "<group>";
Expand Down Expand Up @@ -3034,6 +3040,14 @@
path = Mocks;
sourceTree = "<group>";
};
42DD84172B14D83400936F16 /* Tests */ = {
isa = PBXGroup;
children = (
42DD84182B14D83B00936F16 /* WebViewExternalBusMessageTests.swift */,
);
path = Tests;
sourceTree = "<group>";
};
42F5CAB72B10AD8C00409816 /* Tests */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -5664,6 +5678,7 @@
1185DFAE271FF53800ED7D9A /* OnboardingAuthStepConfig.swift in Sources */,
B661FB7A226C197900E541DD /* OnboardingManualURLViewController.swift in Sources */,
119A827C252A3C4700D7000D /* NFCNDEFPayload+Additions.swift in Sources */,
42DD84162B14D7AC00936F16 /* WebViewExternalBusMessage.swift in Sources */,
11EFCDD324F5F39100314D85 /* WebViewWindowController.swift in Sources */,
11EFCDE024F60E5900314D85 /* BasicSceneDelegate.swift in Sources */,
11A71C6F24A4644A00D9565F /* ZoneManagerIgnoreReason.swift in Sources */,
Expand All @@ -5680,6 +5695,7 @@
buildActionMask = 2147483647;
files = (
11A71C7624A5028200D9565F /* ZoneManagerEvent.test.swift in Sources */,
42DD84192B14D83B00936F16 /* WebViewExternalBusMessageTests.swift in Sources */,
116D3A4627252C3200EF5D21 /* OnboardingAuthStepConfig.test.swift in Sources */,
11C95E3628BC20EA00171F1C /* OnboardingAuthLoginViewController.test.swift in Sources */,
117D8A0A24A9381F00580913 /* UIColor+CSSRGB.test.swift in Sources */,
Expand Down
17 changes: 17 additions & 0 deletions Sources/App/WebView/Tests/WebViewExternalBusMessageTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@testable import HomeAssistant
import XCTest
final class WebViewExternalBusMessageTests: XCTestCase {
func test_externalBus_messageKeys() {
XCTAssertEqual(WebViewExternalBusMessage.configGet.rawValue, "config/get")
XCTAssertEqual(WebViewExternalBusMessage.configScreenShow.rawValue, "config_screen/show")
XCTAssertEqual(WebViewExternalBusMessage.haptic.rawValue, "haptic")
XCTAssertEqual(WebViewExternalBusMessage.connectionStatus.rawValue, "connection-status")
XCTAssertEqual(WebViewExternalBusMessage.tagRead.rawValue, "tag/read")
XCTAssertEqual(WebViewExternalBusMessage.tagWrite.rawValue, "tag/write")
XCTAssertEqual(WebViewExternalBusMessage.themeUpdate.rawValue, "theme-update")
XCTAssertEqual(WebViewExternalBusMessage.matterCommission.rawValue, "matter/commission")
XCTAssertEqual(WebViewExternalBusMessage.threadImportCredentials.rawValue, "thread/import_credentials")

XCTAssertEqual(WebViewExternalBusMessage.allCases.count, 9)
}
}
141 changes: 71 additions & 70 deletions Sources/App/WebView/WebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -996,81 +996,82 @@ extension WebViewController: WKScriptMessageHandler {

var response: Guarantee<WebSocketMessage>?

switch incomingMessage.MessageType {
case "config/get":
response = Guarantee { seal in
DispatchQueue.global(qos: .userInitiated).async {
seal(WebSocketMessage(
id: incomingMessage.ID!,
type: "result",
result: [
"hasSettingsScreen": !Current.isCatalyst,
"canWriteTag": Current.tags.isNFCAvailable,
"canCommissionMatter": Current.matter.isAvailable,
"canImportThreadCredentials": Current.threadCredentialsSharingEnabled,
]
))
if let externalBusMessage = WebViewExternalBusMessage(rawValue: incomingMessage.MessageType) {
switch externalBusMessage {
case .configGet:
response = Guarantee { seal in
DispatchQueue.global(qos: .userInitiated).async {
seal(WebSocketMessage(
id: incomingMessage.ID!,
type: "result",
result: [
"hasSettingsScreen": !Current.isCatalyst,
"canWriteTag": Current.tags.isNFCAvailable,
"canCommissionMatter": Current.matter.isAvailable,
"canImportThreadCredentials": Current.threadCredentialsSharingEnabled,
]
))
}
}
case .configScreenShow:
showSettingsViewController()
case .haptic:
guard let hapticType = incomingMessage.Payload?["hapticType"] as? String else {
Current.Log.error("Received haptic via bus but hapticType was not string! \(incomingMessage)")
return
}
handleHaptic(hapticType)
case .connectionStatus:
guard let connEvt = incomingMessage.Payload?["event"] as? String else {
Current.Log.error("Received connection-status via bus but event was not string! \(incomingMessage)")
return
}
// Possible values: connected, disconnected, auth-invalid
UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseInOut, animations: {
self.settingsButton.alpha = connEvt == "connected" ? 0.0 : 1.0
}, completion: nil)
case .tagRead:
response = Current.tags.readNFC().map { tag in
WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": true, "tag": tag])
}.recover { _ in
.value(WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": false]))
}
case .tagWrite:
let (promise, seal) = Guarantee<Bool>.pending()
response = promise.map { success in
WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": success])
}
}
case "config_screen/show":
showSettingsViewController()
case "haptic":
guard let hapticType = incomingMessage.Payload?["hapticType"] as? String else {
Current.Log.error("Received haptic via bus but hapticType was not string! \(incomingMessage)")
return
}
handleHaptic(hapticType)
case "connection-status":
guard let connEvt = incomingMessage.Payload?["event"] as? String else {
Current.Log.error("Received connection-status via bus but event was not string! \(incomingMessage)")
return
}
// Possible values: connected, disconnected, auth-invalid
UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseInOut, animations: {
self.settingsButton.alpha = connEvt == "connected" ? 0.0 : 1.0
}, completion: nil)
case "tag/read":
response = Current.tags.readNFC().map { tag in
WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": true, "tag": tag])
}.recover { _ in
.value(WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": false]))
}
case "tag/write":
let (promise, seal) = Guarantee<Bool>.pending()
response = promise.map { success in
WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": success])
}

firstly { () throws -> Promise<(tag: String, name: String?)> in
if let tag = incomingMessage.Payload?["tag"] as? String, tag.isEmpty == false {
return .value((tag: tag, name: incomingMessage.Payload?["name"] as? String))
} else {
throw HomeAssistantAPI.APIError.invalidResponse
firstly { () throws -> Promise<(tag: String, name: String?)> in
if let tag = incomingMessage.Payload?["tag"] as? String, tag.isEmpty == false {
return .value((tag: tag, name: incomingMessage.Payload?["name"] as? String))
} else {
throw HomeAssistantAPI.APIError.invalidResponse
}
}.then { tagInfo in
Current.tags.writeNFC(value: tagInfo.tag)
}.done { _ in
Current.Log.info("wrote tag via external bus")
seal(true)
}.catch { error in
Current.Log.error("couldn't write tag via external bus: \(error)")
seal(false)
}
}.then { tagInfo in
Current.tags.writeNFC(value: tagInfo.tag)
}.done { _ in
Current.Log.info("wrote tag via external bus")
seal(true)
}.catch { error in
Current.Log.error("couldn't write tag via external bus: \(error)")
seal(false)
}
case "theme-update":
webView.evaluateJavaScript("notifyThemeColors()", completionHandler: nil)
case "matter/commission":
Current.matter.commission(server).done {
Current.Log.info("commission call completed")
}.catch { error in
// we don't show a user-visible error because even a successful operation will return 'cancelled'
// but the errors aren't public, so we can't compare -- the apple ui shows errors visually though
Current.Log.error(error)
case .themeUpdate:
webView.evaluateJavaScript("notifyThemeColors()", completionHandler: nil)
case .matterCommission:
Current.matter.commission(server).done {
Current.Log.info("commission call completed")
}.catch { error in
// we don't show a user-visible error because even a successful operation will return 'cancelled'
// but the errors aren't public, so we can't compare -- the apple ui shows errors visually though
Current.Log.error(error)
}
case .threadImportCredentials:
threadCredentialsRequested()
}
case "thread/import_credentials":
threadCredentialsRequested()
default:
} else {
Current.Log.error("unknown: \(incomingMessage.MessageType)")
return
}

response?.then { [self] outgoing in
Expand Down
13 changes: 13 additions & 0 deletions Sources/App/WebView/WebViewExternalBusMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

enum WebViewExternalBusMessage: String, CaseIterable {
case configGet = "config/get"
case configScreenShow = "config_screen/show"
case haptic
case connectionStatus = "connection-status"
case tagRead = "tag/read"
case tagWrite = "tag/write"
case themeUpdate = "theme-update"
case matterCommission = "matter/commission"
case threadImportCredentials = "thread/import_credentials"
}
2 changes: 1 addition & 1 deletion Sources/Shared/Environment/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ public class AppEnvironment {
public var device = DeviceWrapper()

public var matter = MatterWrapper()

public var threadCredentialsSharingEnabled: Bool {
// For now mac is not returning thread credentials for some reason
#if canImport(ThreadNetwork) && !targetEnvironment(macCatalyst)
Expand Down

0 comments on commit bde2378

Please sign in to comment.