Onboarding/Media: harden QR scanner and data URL parsing

This commit is contained in:
Mariano Belinky
2026-02-14 14:45:04 +00:00
committed by Mariano Belinky
parent 06adadc759
commit bc05143e4e
4 changed files with 143 additions and 14 deletions

View File

@@ -7,16 +7,35 @@ struct QRScannerView: UIViewControllerRepresentable {
let onError: (String) -> Void
let onDismiss: () -> Void
func makeUIViewController(context: Context) -> DataScannerViewController {
func makeUIViewController(context: Context) -> UIViewController {
guard DataScannerViewController.isSupported else {
context.coordinator.reportError("QR scanning is not supported on this device.")
return UIViewController()
}
guard DataScannerViewController.isAvailable else {
context.coordinator.reportError("Camera scanning is currently unavailable.")
return UIViewController()
}
let scanner = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: [.qr])],
isHighlightingEnabled: true)
scanner.delegate = context.coordinator
try? scanner.startScanning()
do {
try scanner.startScanning()
} catch {
context.coordinator.reportError("Could not start QR scanner.")
}
return scanner
}
func updateUIViewController(_: DataScannerViewController, context _: Context) {}
func updateUIViewController(_: UIViewController, context _: Context) {}
static func dismantleUIViewController(_ uiViewController: UIViewController, coordinator: Coordinator) {
if let scanner = uiViewController as? DataScannerViewController {
scanner.stopScanning()
}
coordinator.parent.onDismiss()
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
@@ -25,11 +44,20 @@ struct QRScannerView: UIViewControllerRepresentable {
final class Coordinator: NSObject, DataScannerViewControllerDelegate {
let parent: QRScannerView
private var handled = false
private var reportedError = false
init(parent: QRScannerView) {
self.parent = parent
}
func reportError(_ message: String) {
guard !self.reportedError else { return }
self.reportedError = true
Task { @MainActor in
self.parent.onError(message)
}
}
func dataScanner(_: DataScannerViewController, didAdd items: [RecognizedItem], allItems _: [RecognizedItem]) {
guard !self.handled else { return }
for item in items {
@@ -58,8 +86,11 @@ struct QRScannerView: UIViewControllerRepresentable {
func dataScanner(_: DataScannerViewController, didRemove _: [RecognizedItem], allItems _: [RecognizedItem]) {}
func dataScanner(_: DataScannerViewController, becameUnavailableWithError error: DataScannerViewController.ScanningUnavailable) {
self.parent.onError("Camera is not available on this device.")
func dataScanner(
_: DataScannerViewController,
becameUnavailableWithError _: DataScannerViewController.ScanningUnavailable)
{
self.reportError("Camera is not available on this device.")
}
}
}

View File

@@ -76,4 +76,34 @@ import Testing
timeoutSeconds: nil,
key: nil)))
}
@Test func parseGatewayLinkParsesCommonFields() {
let url = URL(
string: "openclaw://gateway?host=openclaw.local&port=18789&tls=1&token=abc&password=def")!
#expect(
DeepLinkParser.parse(url) == .gateway(
.init(host: "openclaw.local", port: 18789, tls: true, token: "abc", password: "def")))
}
@Test func parseGatewaySetupCodeParsesBase64UrlPayload() {
let payload = #"{"url":"wss://gateway.example.com:443","token":"tok","password":"pw"}"#
let encoded = Data(payload.utf8)
.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
let link = GatewayConnectDeepLink.fromSetupCode(encoded)
#expect(link == .init(
host: "gateway.example.com",
port: 443,
tls: true,
token: "tok",
password: "pw"))
}
@Test func parseGatewaySetupCodeRejectsInvalidInput() {
#expect(GatewayConnectDeepLink.fromSetupCode("not-a-valid-setup-code") == nil)
}
}