Files
tinycast/Tinycast/Features/Extensions/Service/ExtensionNameResolver.swift
T
Jonas List ca3da2ee4e Add the WebSocket and mDNS support Home Assistant needs (#901)
* Run extensions that speak WebSocket

A bundled `ws` handshakes through `http.request` and wants a raw socket back,
which the fetch-backed shim had none of. Sockets are `URLSessionWebSocketTask`
now, and the upgrade path hands `ws` one that re-frames RFC 6455 both ways.
Home Assistant is the reference case.

* Resolve a .local host without joining a multicast group

Home Assistant's default `homeassistant.local` is resolved by the extension
itself with multicast-dns, which died on `dgram.createSocket`. `dgram` now
answers an address query out of `getaddrinfo` — mDNSResponder handles
`.local` — so nothing multicasts and no entitlement is needed.

* Harden the WebSocket and dgram shims

A rejected host call poisoned the send queue, so every later send and the
close after it rejected too; the queue now recovers while the caller still
sees the failure. A ping is answered by the peer through
`URLSessionWebSocketTask`, not by a pong the adapter invents. An upgrade no
listener claims destroys the socket, the way Node does, so the native task
goes with it. `dgram` answers address questions only. And a socket id that
is not a whole number falls back instead of trapping.
2026-09-19 03:40:08 +06:00

37 lines
1.5 KiB
Swift

import Foundation
/// Host-name lookups for the `dgram` shim: the system resolver answers `.local` through Bonjour.
enum ExtensionNameResolver {
static func resolve(_ name: RenderValue?) async -> [String] {
let host = name?.stringValue ?? ""
guard !host.isEmpty else { return [] }
return await Task.detached(priority: .userInitiated) { addresses(of: host) }.value
}
/// Blocking, hence the detached task.
private static func addresses(of host: String) -> [String] {
var hints = addrinfo()
hints.ai_family = AF_INET
hints.ai_socktype = SOCK_STREAM
var head: UnsafeMutablePointer<addrinfo>?
guard getaddrinfo(host, nil, &hints, &head) == 0, let first = head else { return [] }
defer { freeaddrinfo(first) }
var found: [String] = []
var entry: UnsafeMutablePointer<addrinfo>? = first
while let current = entry {
var text = [CChar](repeating: 0, count: Int(NI_MAXHOST))
if getnameinfo(
current.pointee.ai_addr, current.pointee.ai_addrlen, &text, socklen_t(text.count),
nil, 0, NI_NUMERICHOST) == 0
{
let digits = text.prefix { $0 != 0 }.map { UInt8(bitPattern: $0) }
let address = String(decoding: digits, as: UTF8.self)
if !found.contains(address) { found.append(address) }
}
entry = current.pointee.ai_next
}
return found
}
}