67 lines
2.7 KiB
Swift
67 lines
2.7 KiB
Swift
import Foundation
|
|
import RemoteCore
|
|
|
|
/// Prototype wire format to verify against physical TV models. No network or TLS
|
|
/// policy is implemented here. In particular, callers must never disable trust checks.
|
|
public enum SamsungRemoteEncoder {
|
|
public static let clientName = "Wristivo"
|
|
|
|
public static func key(for command: RemoteCommand) -> String {
|
|
switch command {
|
|
case .up: "KEY_UP"
|
|
case .down: "KEY_DOWN"
|
|
case .left: "KEY_LEFT"
|
|
case .right: "KEY_RIGHT"
|
|
case .select: "KEY_ENTER"
|
|
case .back: "KEY_RETURN"
|
|
case .home: "KEY_HOME"
|
|
case .playPause: "KEY_PLAYPAUSE"
|
|
case .volumeUp: "KEY_VOLUP"
|
|
case .volumeDown: "KEY_VOLDOWN"
|
|
case .mute: "KEY_MUTE"
|
|
}
|
|
}
|
|
|
|
public static func clickFrame(for command: RemoteCommand) throws -> Data {
|
|
try JSONEncoder().encode(Frame(params: Parameters(DataOfCmd: key(for: command))))
|
|
}
|
|
|
|
public enum EndpointError: Error, Sendable { case invalidHost }
|
|
|
|
/// Accepts a bare hostname or IPv4 address, or a bracketed IPv6 literal.
|
|
/// Treat token as a secret: do not log the returned URL or persist it in telemetry.
|
|
public static func endpoint(host: String, token: String? = nil) throws -> URL {
|
|
guard !host.isEmpty, !host.contains(where: { $0.isWhitespace }),
|
|
!host.contains("/"), !host.contains("@"),
|
|
!host.contains("?"), !host.contains("#"),
|
|
!host.contains("%"), !host.contains("\\"),
|
|
(!host.contains(":") || (host.hasPrefix("[") && host.hasSuffix("]"))) else {
|
|
throw EndpointError.invalidHost
|
|
}
|
|
var parts = URLComponents()
|
|
parts.scheme = "wss"
|
|
parts.host = host
|
|
parts.port = 8002
|
|
parts.path = "/api/v2/channels/samsung.remote.control"
|
|
var items = [URLQueryItem(name: "name", value: Data(clientName.utf8).base64EncodedString())]
|
|
if let token { items.append(URLQueryItem(name: "token", value: token)) }
|
|
parts.queryItems = items
|
|
// '+' is legal in an RFC 3986 query, but some embedded servers treat it as
|
|
// a form-space. Encode it explicitly while retaining URLComponents escaping.
|
|
parts.percentEncodedQuery = parts.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
|
|
guard let url = parts.url, url.host != nil else { throw EndpointError.invalidHost }
|
|
return url
|
|
}
|
|
|
|
private struct Frame: Encodable {
|
|
let method = "ms.remote.control"
|
|
let params: Parameters
|
|
}
|
|
private struct Parameters: Encodable {
|
|
let Cmd = "Click"
|
|
let DataOfCmd: String
|
|
let Option = "false"
|
|
let TypeOfRemote = "SendRemoteKey"
|
|
}
|
|
}
|