diff --git a/appleApp/FTWKit/Sources/FTWShared/PushAlert.swift b/appleApp/FTWKit/Sources/FTWShared/PushAlert.swift index 2a2d4c1..72e681a 100644 --- a/appleApp/FTWKit/Sources/FTWShared/PushAlert.swift +++ b/appleApp/FTWKit/Sources/FTWShared/PushAlert.swift @@ -26,6 +26,13 @@ public enum PushKind { public static let boxUnreachable = "box.unreachable" public static let cloudGranted = "cloud.granted" public static let cloudRevoked = "cloud.revoked" + /// Access from outside the house, for every owner (docs/agents.md in + /// srcfl/ftw). They come as the box's `notice`, never as an event. + public static let agentAdded = "agent.added" + public static let agentRequest = "agent.request" + public static let agentExpiring = "agent.expiring" + public static let approvalKeySet = "approval.key.set" + public static let ownerAdded = "owner.added" /// The box asks which car is on a charger. The buttons, one per car, /// come with the car app (docs/cars.md in srcfl/ftw). public static let carWhich = "car.which" @@ -33,8 +40,13 @@ public enum PushKind { /// In the catalogue's order. public static let all = [ chargingConnected, chargingSessionComplete, chargingGoalAtRisk, chargingInterrupted, updateInstalled, - driverOffline, fuseOverLimit, boxUnreachable, cloudGranted, cloudRevoked, carWhich, + driverOffline, fuseOverLimit, boxUnreachable, cloudGranted, cloudRevoked, + agentAdded, agentRequest, agentExpiring, approvalKeySet, ownerAdded, carWhich, ] + + /// The kinds only owners may read. A box sends them as a `notice`; an + /// `event` that names one reaches viewers too, so it reads as generic. + public static let notices: Set = [agentAdded, agentRequest, agentExpiring, approvalKeySet, ownerAdded] } /// What a push says once checked. Only `read` makes one, so a kind that has @@ -134,7 +146,8 @@ public struct PushAlert: Sendable, Equatable { } if let ts = ev.ts { at = min(at, ts) } let age = nowMs.subtractingReportingOverflow(at) - guard PushKind.all.contains(ev.kind), ev.kind != PushKind.boxUnreachable, !age.overflow, age.partialValue <= maxAgeMs else { + guard PushKind.all.contains(ev.kind), ev.kind != PushKind.boxUnreachable, !PushKind.notices.contains(ev.kind), + !age.overflow, age.partialValue <= maxAgeMs else { return generic } return PushAlert(.event, home: home, kind: ev.kind, args: ev.args, src: ev.src, lp: ev.lp, boot: ev.boot.map { Data($0) }, seq: ev.seq, atMs: at) diff --git a/appleApp/FTWKit/Sources/FTWShared/PushText.swift b/appleApp/FTWKit/Sources/FTWShared/PushText.swift index 20415fa..e5beca7 100644 --- a/appleApp/FTWKit/Sources/FTWShared/PushText.swift +++ b/appleApp/FTWKit/Sources/FTWShared/PushText.swift @@ -76,6 +76,11 @@ public enum PushText { PushKind.boxUnreachable: Sentence(title: "Your box is out of reach", body: "It stopped answering. Power cut, or the internet at home is down."), PushKind.cloudGranted: Sentence(title: "FTW Cloud is on", body: "Your box now shares a copy of its readings with FTW Cloud. You can turn it off in the app or on the box."), PushKind.cloudRevoked: Sentence(title: "FTW Cloud is off", body: "Your box stopped sharing with FTW Cloud and asked it to delete its copy."), + PushKind.agentAdded: Sentence(title: "New access to your box", body: "You gave an assistant or Sourceful access. See who has access in the app."), + PushKind.agentRequest: Sentence(title: "Waiting for your answer", body: "An assistant asks to change a setting. Open FTW to confirm or decline."), + PushKind.agentExpiring: Sentence(title: "Access ends tomorrow", body: "An assistant's or Sourceful's access to your box runs out in a day. Renew it in the app if it should go on."), + PushKind.approvalKeySet: Sentence(title: "A phone can now approve access", body: "A phone set up the key it uses to approve access to your box. If that was not you, remove the phone in the app."), + PushKind.ownerAdded: Sentence(title: "A new owner", body: "A phone was paired as an owner of your box. If that was not you, remove it in the app."), PushKind.carWhich: Sentence(title: "Which car is plugged in?", body: "Tap the car so the box plans for its level and goal."), ] @@ -90,6 +95,11 @@ public enum PushText { PushKind.boxUnreachable: Sentence(title: "Din box går inte att nå", body: "Den har slutat svara. Strömavbrott, eller så är internet hemma nere."), PushKind.cloudGranted: Sentence(title: "FTW Cloud är på", body: "Din box delar nu en kopia av sina mätvärden med FTW Cloud. Du kan stänga av det i appen eller på boxen."), PushKind.cloudRevoked: Sentence(title: "FTW Cloud är av", body: "Din box har slutat dela med FTW Cloud och bett den radera sin kopia."), + PushKind.agentAdded: Sentence(title: "Ny åtkomst till din box", body: "En assistent eller Sourceful har fått åtkomst. Se vem som har åtkomst i appen."), + PushKind.agentRequest: Sentence(title: "Väntar på ditt svar", body: "En assistent vill ändra en inställning. Öppna FTW för att godkänna eller avböja."), + PushKind.agentExpiring: Sentence(title: "Åtkomsten upphör i morgon", body: "En assistents eller Sourcefuls åtkomst till din box går ut om ett dygn. Förnya den i appen om den ska fortsätta."), + PushKind.approvalKeySet: Sentence(title: "En telefon kan nu godkänna åtkomst", body: "En telefon har satt upp nyckeln den använder för att godkänna åtkomst till din box. Var det inte du, ta bort telefonen i appen."), + PushKind.ownerAdded: Sentence(title: "En ny ägare", body: "En telefon har parats som ägare av din box. Var det inte du, ta bort den i appen."), PushKind.carWhich: Sentence(title: "Vilken bil är inkopplad?", body: "Tryck på bilen så planerar boxen efter dess nivå och mål."), ] diff --git a/appleApp/FTWKit/Tests/FTWSharedTests/PushAlertTests.swift b/appleApp/FTWKit/Tests/FTWSharedTests/PushAlertTests.swift index 4787756..5c5568a 100644 --- a/appleApp/FTWKit/Tests/FTWSharedTests/PushAlertTests.swift +++ b/appleApp/FTWKit/Tests/FTWSharedTests/PushAlertTests.swift @@ -189,6 +189,14 @@ import Testing #expect(a.src == nil) } + /// Access news is for owners only. A box sends it as a `notice`; as an + /// `event` it also reaches viewers, so it reads as generic. + @Test func accessNewsAsAnEventIsGeneric() { + for kind in PushKind.notices { + #expect(Self.read(Self.envelope(Self.body(kind: kind))).verdict == .generic, "\(kind)") + } + } + /// Unknown keys are a newer box, not a fault. @Test func unknownKeysAreIgnored() { #expect(Self.read(Self.envelope(Self.body([("later", .m([("x", .u(1))]))]))).verdict == .event) diff --git a/protocol/push-catalogue.yaml b/protocol/push-catalogue.yaml index 2693e2f..0c9e8c5 100644 --- a/protocol/push-catalogue.yaml +++ b/protocol/push-catalogue.yaml @@ -60,6 +60,32 @@ events: # To every owner, when FTW Cloud is turned off at either door. title: FTW Cloud is off body: "Your box stopped sharing with FTW Cloud and asked it to delete its copy." + - kind: agent.added + # To every owner, when an owner gives an assistant, Sourceful support or + # the release check access to the box. Access from outside the house is + # news every owner should hear, as with cloud.granted. + title: New access to your box + body: "You gave an assistant or Sourceful access. See who has access in the app." + - kind: agent.request + # To every owner, when an agent asks for a durable change. It waits for + # one of them to confirm it, and runs out after twelve hours. + title: Waiting for your answer + body: "An assistant asks to change a setting. Open FTW to confirm or decline." + - kind: agent.expiring + # To every owner, a day before an agent's grant runs out, so access that + # should go on is renewed and access that should end can be left to end. + title: Access ends tomorrow + body: "An assistant's or Sourceful's access to your box runs out in a day. Renew it in the app if it should go on." + - kind: approval.key.set + # To every owner, when a phone registers or replaces the key that + # approves access. A new key is a new way to let others in. + title: A phone can now approve access + body: "A phone set up the key it uses to approve access to your box. If that was not you, remove the phone in the app." + - kind: owner.added + # To every owner, when a phone pairs as an owner. An owner can do + # anything, including give others access. + title: A new owner + body: "A phone was paired as an owner of your box. If that was not you, remove it in the app." - kind: car.which # When a car is plugged in and the box cannot tell which of its owners' # cars it is: no charger identity, no word from the car within a minute,