Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions appleApp/FTWKit/Sources/FTWKit/Carrier/CloudCarrier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public final class CloudCarrier {
private let scheduler: Scheduler
private let makeSocket: WebSocketFactory
private var looking: Bool
/// What the service last heard: message 1's `hz`, then each `watch`.
/// Nil until message 1 of this dial goes.
private var told: Bool?

private var socket: WebSocketConnection?
private var generation = 0
Expand Down Expand Up @@ -91,11 +94,22 @@ public final class CloudCarrier {

/// Whether anyone can see the home. While someone can, the service keeps
/// the box at one update a second. A closed session takes the change
/// with it into message 1 of the next.
/// with it into message 1 of the next. A session still opening says it
/// once message 2 is in: a launch passes through inactive, so message 1
/// often says the phone looks away.
public func setLooking(_ on: Bool) {
guard on != looking else { return }
looking = on
send(CloudWire.msgWatch, .map([("hz", .int(on ? CloudWire.hzLooking : CloudWire.hzAway))]))
tellLooking()
}

/// Send `watch` when the service last heard otherwise and the session
/// is open.
private func tellLooking() {
guard let told, told != looking else { return }
if send(CloudWire.msgWatch, .map([("hz", .int(looking ? CloudWire.hzLooking : CloudWire.hzAway))])) {
self.told = looking
}
}

/// An owner suspends a device at FTW Cloud, or lifts it. It only narrows
Expand Down Expand Up @@ -151,6 +165,7 @@ public final class CloudCarrier {
let mine = generation
handshake = nil
transport = nil
told = nil
socket = makeSocket(endpoint.url(home: home), WebSocketEvents(
onOpen: { [weak self] in
guard let self, mine == self.generation else { return }
Expand Down Expand Up @@ -182,6 +197,7 @@ public final class CloudCarrier {
guard let msg1 = try? hs.writeMessage(now) else { return }
handshake = hs
socket.send(msg1)
told = looking
deadline?.cancel()
deadline = scheduler.after(Self.handshakeTimeoutMs) { [weak self] in
guard let self, self.transport == nil else { return }
Expand Down Expand Up @@ -216,6 +232,7 @@ public final class CloudCarrier {
attempt = 0
refusals = 0
setStatus(.open(sinceMs: arrival))
tellLooking()
onSnapshot(snapshot, arrival)
}

Expand Down
40 changes: 40 additions & 0 deletions appleApp/FTWKit/Tests/FTWKitTests/CloudCarrierTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,46 @@ final class CloudRig {
#expect(rig.service.watches == [1, 0])
}

/// A launch passes through inactive, so message 1 may say the phone
/// looks away. The app comes to the front before message 2: the service
/// hears it once the session opens, or the box stays at 5 s.
@Test func lookingChangedDuringTheHandshakeIsSaidOnceOpen() async throws {
let rig = try CloudRig(looking: false)
rig.carrier.start()
await rig.run(15)
#expect(rig.service.nows == [])
#expect(!rig.carrier.status.isOpen)
rig.carrier.setLooking(true)
await rig.run(50)
#expect(rig.service.nows == [CloudWire.hzAway])
#expect(rig.carrier.status.isOpen)
#expect(rig.service.watches == [1])

// And the other way: the phone looked when it dialled and looked
// away before the session opened.
let away = try CloudRig(looking: true)
away.carrier.start()
await away.run(15)
away.carrier.setLooking(false)
await away.run(50)
#expect(away.service.nows == [CloudWire.hzLooking])
#expect(away.service.watches == [0])
}

/// A change while the session is down rides in the next message 1, and
/// nothing more is said.
@Test func lookingChangedWhileClosedGoesInMessage1() async throws {
let rig = try CloudRig(looking: true)
rig.carrier.start()
await rig.run(50)
rig.service.close(code: 1006)
rig.carrier.setLooking(false)
await rig.run(600)
#expect(rig.carrier.status.isOpen)
#expect(rig.service.nows == [CloudWire.hzLooking, CloudWire.hzAway])
#expect(rig.service.watches == [])
}

/// A refusal is asked again slowly, as after a service restart that has
/// not heard from the box yet, and at once on the next foreground.
@Test func aRefusalIsAskedAgainSlowly() async throws {
Expand Down
Loading