Files
wristivo/Packages/WristivoKit/Sources/SamsungProtocol/SamsungRemoteEncoder.swift
T

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"
}
}