Commit 67e5e954b4
Verified · cmc
Layout: unified · split
docs/superpowers/plans/2026-09-20-push-notifications.md +14 −4
| @@ -572,8 +572,11 @@ private func twoAccounts() async throws -> (SessionStore, StubProtocol.Box) { | |||
| 572 | let (session, _) = try await twoAccounts() | 572 | let (session, _) = try await twoAccounts() |
| 573 | let victim = try #require(session.accounts.first) | 573 | let victim = try #require(session.accounts.first) |
| 574 | var sawTokenDuringHook: Bool? | 574 | var sawTokenDuringHook: Bool? |
| 575 | session.willRemoveAccount = { account in | 575 | // [weak session] because this closure is stored ON session; the |
| 576 | sawTokenDuringHook = session.client(for: account) != nil | 576 | // production wiring in gitbayApp captures the registrar weakly for |
| 577 | // the same reason. | ||
| 578 | session.willRemoveAccount = { [weak session] account in | ||
| 579 | sawTokenDuringHook = session?.client(for: account) != nil | ||
| 577 | } | 580 | } |
| 578 | await session.remove(victim) | 581 | await session.remove(victim) |
| 579 | #expect(sawTokenDuringHook == true) | 582 | #expect(sawTokenDuringHook == true) |
| @@ -753,7 +756,12 @@ final class PushRegistrar { | |||
| 753 | ["notifications", "device", "add", "--label", Self.deviceLabel], | 756 | ["notifications", "device", "add", "--label", Self.deviceLabel], |
| 754 | stdin: token, as: Registered.self) | 757 | stdin: token, as: Registered.self) |
| 755 | if let id = out?.id { | 758 | if let id = out?.id { |
| 756 | defaults.set(id, forKey: Self.idKey(account)) | 759 | // Stored as Int, which UserDefaults handles natively — |
| 760 | // it has no Int64 overload, so an Int64 goes in as a | ||
| 761 | // boxed NSNumber and comes back out through a | ||
| 762 | // conditional bridge. Int is 64-bit on every device | ||
| 763 | // this ships to. | ||
| 764 | defaults.set(Int(id), forKey: Self.idKey(account)) | ||
| 757 | } | 765 | } |
| 758 | } catch { | 766 | } catch { |
| 759 | // A side channel. The account still works and the next | 767 | // A side channel. The account still works and the next |
| @@ -767,7 +775,9 @@ final class PushRegistrar { | |||
| 767 | /// wired, and that hook is awaited before the token is discarded. | 775 | /// wired, and that hook is awaited before the token is discarded. |
| 768 | func deregister(_ account: Account) async { | 776 | func deregister(_ account: Account) async { |
| 769 | let key = Self.idKey(account) | 777 | let key = Self.idKey(account) |
| 770 | guard let id = defaults.object(forKey: key) as? Int64, | 778 | // object(forKey:) rather than integer(forKey:), which cannot |
| 779 | // tell a stored 0 from an absent key. | ||
| 780 | guard let id = defaults.object(forKey: key) as? Int, | ||
| 771 | let client = session.client(for: account) else { return } | 781 | let client = session.client(for: account) else { return } |
| 772 | try? await client.run(["notifications", "device", "remove", String(id)]) | 782 | try? await client.run(["notifications", "device", "remove", String(id)]) |
| 773 | defaults.removeObject(forKey: key) | 783 | defaults.removeObject(forKey: key) |