mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-22 16:28:11 +00:00
feat: QR code scanning for gateway onboarding
iOS:
- QR scanner view using DataScannerViewController
- Photo library QR detection via CIDetector for saved QR images
- Deep link parser for openclaw://gateway URLs and base64url setup codes
- Onboarding wizard: full-screen welcome with "Scan QR Code" button,
auto-connect on scan, back navigation, step indicators for manual flow
Backend:
- Add /pair qr action to device-pair extension for QR code generation
- TUI/WebUI differentiation: ASCII QR for TUI, markdown image for WebUI
- Telegram: send QR as media attachment via sendMessageTelegram
- Add data URI support to loadWebMedia for generic base64 media handling
- Export renderQrPngBase64 from plugin SDK for extension use
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit d79ed65be0)
This commit is contained in:
committed by
Mariano Belinky
parent
df6d0ee92b
commit
06adadc759
@@ -2,6 +2,56 @@ import Foundation
|
||||
|
||||
public enum DeepLinkRoute: Sendable, Equatable {
|
||||
case agent(AgentDeepLink)
|
||||
case gateway(GatewayConnectDeepLink)
|
||||
}
|
||||
|
||||
public struct GatewayConnectDeepLink: Codable, Sendable, Equatable {
|
||||
public let host: String
|
||||
public let port: Int
|
||||
public let tls: Bool
|
||||
public let token: String?
|
||||
public let password: String?
|
||||
|
||||
public init(host: String, port: Int, tls: Bool, token: String?, password: String?) {
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.tls = tls
|
||||
self.token = token
|
||||
self.password = password
|
||||
}
|
||||
|
||||
public var websocketURL: URL? {
|
||||
let scheme = self.tls ? "wss" : "ws"
|
||||
return URL(string: "\(scheme)://\(self.host):\(self.port)")
|
||||
}
|
||||
|
||||
/// Parse a device-pair setup code (base64url-encoded JSON: `{url, token?, password?}`).
|
||||
public static func fromSetupCode(_ code: String) -> GatewayConnectDeepLink? {
|
||||
guard let data = Self.decodeBase64Url(code) else { return nil }
|
||||
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }
|
||||
guard let urlString = json["url"] as? String,
|
||||
let parsed = URLComponents(string: urlString),
|
||||
let hostname = parsed.host, !hostname.isEmpty
|
||||
else { return nil }
|
||||
|
||||
let scheme = parsed.scheme ?? "ws"
|
||||
let tls = scheme == "wss"
|
||||
let port = parsed.port ?? 18789
|
||||
let token = json["token"] as? String
|
||||
let password = json["password"] as? String
|
||||
return GatewayConnectDeepLink(host: hostname, port: port, tls: tls, token: token, password: password)
|
||||
}
|
||||
|
||||
private static func decodeBase64Url(_ input: String) -> Data? {
|
||||
var base64 = input
|
||||
.replacingOccurrences(of: "-", with: "+")
|
||||
.replacingOccurrences(of: "_", with: "/")
|
||||
let remainder = base64.count % 4
|
||||
if remainder > 0 {
|
||||
base64.append(contentsOf: String(repeating: "=", count: 4 - remainder))
|
||||
}
|
||||
return Data(base64Encoded: base64)
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentDeepLink: Codable, Sendable, Equatable {
|
||||
@@ -69,6 +119,23 @@ public enum DeepLinkParser {
|
||||
channel: query["channel"],
|
||||
timeoutSeconds: timeoutSeconds,
|
||||
key: query["key"]))
|
||||
|
||||
case "gateway":
|
||||
guard let hostParam = query["host"],
|
||||
!hostParam.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
let port = query["port"].flatMap { Int($0) } ?? 18789
|
||||
let tls = (query["tls"] as NSString?)?.boolValue ?? false
|
||||
return .gateway(
|
||||
.init(
|
||||
host: hostParam,
|
||||
port: port,
|
||||
tls: tls,
|
||||
token: query["token"],
|
||||
password: query["password"]))
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user