Add an iPhone client sharing the macOS app's SwiftUI code

This commit is contained in:
Ian Gulliver
2026-10-21 22:00:54 -07:00
parent 57d660c57f
commit 9e81e9107d
46 changed files with 1625 additions and 287 deletions
+38 -16
View File
@@ -1,4 +1,3 @@
import AppKit
import Foundation
import Network
import Observation
@@ -24,7 +23,7 @@ func slug(_ text: String) -> String {
final class TailnetNode: Sendable {
let sd: tailscale
let hostname = "byre-app-\(slug(macName))"
let hostname = "byre-app-\(slug(deviceName))"
private let proxyHost: String
private let proxyPort: UInt16
private let proxyCredential: String
@@ -165,14 +164,17 @@ final class Tailnet {
try? await Task.sleep(for: .seconds(1))
}
}
#if os(macOS)
Task {
await watchService()
}
#endif
Task {
await discover()
}
}
#if os(macOS)
private func watchService() async {
var loopback: URL?
var asked: ContinuousClock.Instant?
@@ -198,6 +200,7 @@ final class Tailnet {
try? await Task.sleep(for: .seconds(2))
}
}
#endif
private func discover() async {
while true {
@@ -252,16 +255,24 @@ final class Tailnet {
struct TailnetLoginView: View {
var body: some View {
let tailnet = Tailnet.shared
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 12) {
Text("Sign in to Tailscale")
.font(Fonts.font(17, weight: 700))
Text("byre reaches its servers through Tailscale. Sign each of these nodes in to the tailnet your byre servers are on.")
.foregroundStyle(Palette.muted)
.fixedSize(horizontal: false, vertical: true)
.padding(.bottom, 4)
NodeLoginRow(hostname: tailnetNode.hostname, role: "this app", state: tailnet.state, authURL: tailnet.authURL)
if let service = tailnet.service {
NodeLoginRow(hostname: service.hostname, role: "this Mac's server", state: service.state, authURL: URL(string: service.authUrl))
}
}
.frame(width: 460)
.padding(.vertical, 24)
.padding(20)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Palette.raised, in: RoundedRectangle(cornerRadius: 10))
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Palette.border))
.frame(maxWidth: 460)
.padding(16)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onAppear {
Tailnet.shared.serviceWatchers += 1
@@ -279,23 +290,34 @@ private struct NodeLoginRow: View {
let authURL: URL?
var body: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 8) {
Text(hostname).font(Fonts.font(13, weight: 700))
Text(role).foregroundStyle(Palette.muted)
HStack(alignment: .center, spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(hostname).font(Fonts.font(13, weight: 700))
Text(role).foregroundStyle(Palette.muted)
}
if state == "Running" {
Text("Signed in").foregroundStyle(Palette.ok)
} else {
Text(authURL == nil ? "Waiting for Tailscale (\(state))…" : "Waiting for the sign-in (\(state))…")
.font(Fonts.font(12))
.foregroundStyle(Palette.muted)
}
}
if state == "Running" {
Text("Signed in").foregroundStyle(Palette.ok)
} else {
Button("Sign in to Tailscale") {
.frame(maxWidth: .infinity, alignment: .leading)
if state != "Running" {
Button("Sign in") {
if let authURL {
NSWorkspace.shared.open(authURL)
openExternal(authURL)
}
}
.buttonStyle(WebButtonStyle(primary: true))
.disabled(authURL == nil)
Text(authURL == nil ? "Waiting for Tailscale (\(state))…" : "Waiting for the sign-in (\(state))…")
.foregroundStyle(Palette.muted)
.fixedSize()
}
}
.padding(12)
.background(Palette.bg, in: RoundedRectangle(cornerRadius: 8))
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Palette.border))
}
}