Merge nucleic/dusky-willow-weasel-znsn into dev
This commit is contained in:
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "fa70224ebd92e3bfa55fbdf5bbea5d2a51a1f6402e805a443a323509fbc905ed",
|
"originHash" : "6a454662a64d0761bc14f81f12302560e4ac3eba029e64f1e0b0b7f004590631",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "async-http-client",
|
"identity" : "async-http-client",
|
||||||
@@ -267,8 +267,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/migueldeicaza/SwiftTerm.git",
|
"location" : "https://github.com/migueldeicaza/SwiftTerm.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "8e7a1e154f470e19c709a00a8768df348ba5fc43",
|
"revision" : "849e8a4f3d6f79ddee07152400137f1370c32621",
|
||||||
"version" : "1.13.0"
|
"version" : "1.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,6 +150,16 @@ final class HostConnection {
|
|||||||
private var reconnectAttempts = 0
|
private var reconnectAttempts = 0
|
||||||
private var seenSeq: Set<UInt64> = []
|
private var seenSeq: Set<UInt64> = []
|
||||||
|
|
||||||
|
/// The relay's short-lived connection token, cached for reuse across reconnects. The token is a
|
||||||
|
/// stateless HMAC bearer that isn't enforced single-use (`handleRelay` re-verifies it every
|
||||||
|
/// upgrade with no per-token state), so within its TTL a reconnect reuses it and skips the
|
||||||
|
/// `/v1/relay/connect` POST — a cold TLS round-trip to Cloudflare that dominates the reconnect
|
||||||
|
/// after an iOS lock (the app was suspended and the socket silently died). Invalidated when the
|
||||||
|
/// host reissues the relay membership (the room may move) or when a reused token is refused at
|
||||||
|
/// the upgrade. Survives `teardown()` on purpose — reuse across the very reconnect it triggers
|
||||||
|
/// is the point.
|
||||||
|
private var relayConnectionToken: RelayAPI.MintedConnectionToken?
|
||||||
|
|
||||||
private enum TransportAttempt {
|
private enum TransportAttempt {
|
||||||
case lan(NWEndpoint)
|
case lan(NWEndpoint)
|
||||||
case tailnet(host: String, port: UInt16)
|
case tailnet(host: String, port: UInt16)
|
||||||
@@ -354,7 +364,7 @@ final class HostConnection {
|
|||||||
connectTask = Task { [weak self] in
|
connectTask = Task { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
do {
|
do {
|
||||||
let channel = try await RelayFrameChannel.dial(
|
let channel = try await self.dialRelay(
|
||||||
base: base, membershipToken: membershipToken)
|
base: base, membershipToken: membershipToken)
|
||||||
guard !Task.isCancelled else { channel.close(); return }
|
guard !Task.isCancelled else { channel.close(); return }
|
||||||
// A room with no live host swallows frames silently until presence says
|
// A room with no live host swallows frames silently until presence says
|
||||||
@@ -376,6 +386,29 @@ final class HostConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open the relay socket, reusing a cached connection token whenever one is still comfortably
|
||||||
|
/// valid so the common reconnect (back from a lock, on the same relay room) skips the
|
||||||
|
/// `/v1/relay/connect` POST. On a cold cache it mints — and caches — a fresh token. If a reused
|
||||||
|
/// token is refused at the upgrade (expired under our margin, or the membership rotated), it
|
||||||
|
/// drops the cache and mints once before propagating the failure to the candidate/retry loop.
|
||||||
|
private func dialRelay(base: URL, membershipToken: String) async throws -> RelayFrameChannel {
|
||||||
|
// Reuse only with enough slack that the token outlives the handshake it's about to gate;
|
||||||
|
// a token about to expire mid-handshake would just force the re-mint below anyway.
|
||||||
|
if let cached = relayConnectionToken, cached.expiresAt.timeIntervalSinceNow > 15 {
|
||||||
|
do {
|
||||||
|
return try await RelayFrameChannel.dial(base: base, connectionToken: cached.token)
|
||||||
|
} catch {
|
||||||
|
// Teardown cancelled us — don't burn a mint on the way out.
|
||||||
|
if Task.isCancelled { throw error }
|
||||||
|
// The reused token was refused or its socket failed; drop it and try a fresh one.
|
||||||
|
relayConnectionToken = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let minted = try await RelayAPI.mintConnectionToken(base: base, membershipToken: membershipToken)
|
||||||
|
relayConnectionToken = minted
|
||||||
|
return try await RelayFrameChannel.dial(base: base, connectionToken: minted.token)
|
||||||
|
}
|
||||||
|
|
||||||
/// A candidate that dials asynchronously (tailnet, relay) failed before producing a
|
/// A candidate that dials asynchronously (tailnet, relay) failed before producing a
|
||||||
/// channel — fall through to the next candidate or schedule a retry.
|
/// channel — fall through to the next candidate or schedule a retry.
|
||||||
private func asyncAttemptFailed(_ error: Error, isPairing: Bool) {
|
private func asyncAttemptFailed(_ error: Error, isPairing: Bool) {
|
||||||
@@ -667,6 +700,9 @@ final class HostConnection {
|
|||||||
pinnedHost?.relayMembershipToken = membership.token
|
pinnedHost?.relayMembershipToken = membership.token
|
||||||
pinnedHost?.relayURL = membership.url
|
pinnedHost?.relayURL = membership.url
|
||||||
}
|
}
|
||||||
|
// A fresh membership can name a new room, which the cached connection token (minted
|
||||||
|
// against the old one) wouldn't admit us to — drop it so the next dial re-mints.
|
||||||
|
relayConnectionToken = nil
|
||||||
case .pairingCode(let qr):
|
case .pairingCode(let qr):
|
||||||
// This Mac minted a join code we asked for ("add a device to this mesh") — hand it up
|
// This Mac minted a join code we asked for ("add a device to this mesh") — hand it up
|
||||||
// to RemoteStore for the QR/copy sheet.
|
// to RemoteStore for the QR/copy sheet.
|
||||||
|
|||||||
Reference in New Issue
Block a user