···22// APEnvironment.swift
33// CoreATProtocol
44//
55-// Created by Thomas Rademaker on 10/10/25.
66-//
7586import JWTKit
9788+/// Session-scoped state for a single AT Protocol session.
99+///
1010+/// Today this is a process-wide singleton accessed via ``shared``. A future
1111+/// major release will make it instance-based so a single process can host
1212+/// multiple concurrent sessions without the routing layer needing a global
1313+/// handle. Until then, callers should treat ``shared`` as the one-and-only
1414+/// session and avoid reading or mutating its state from outside CoreATProtocol
1515+/// — use the free functions in `CoreATProtocol.swift` (`setup`, `updateTokens`,
1616+/// etc.) as the public API.
1017@APActor
1111-public class APEnvironment {
1212- public static var current: APEnvironment = APEnvironment()
1313-1818+public final class ATProtoSession {
1919+ public static let shared = ATProtoSession()
2020+1421 public var host: String?
1522 public var accessToken: String?
1623 public var refreshToken: String?
1717- public var atProtocoldelegate: CoreATProtocolDelegate?
2424+ public var atProtocolDelegate: CoreATProtocolDelegate?
1825 public var tokenRefreshHandler: (@Sendable () async throws -> Bool)?
1926 public var dpopPrivateKey: ES256PrivateKey?
2027 public var dpopKeys: JWTKeyCollection?
2128 public let dpopNonceStore = DPoPNonceStore()
2229 public let clockSkewStore = ClockSkewStore()
2330 public let routerDelegate = APRouterDelegate()
2424-2525- private init() {}
2626-2727-// func setup(apiKey: String, apiSecret: String, userAgent: String) {
2828-// self.apiKey = apiKey
2929-// self.apiSecret = apiSecret
3030-// self.userAgent = userAgent
3131-// }
3131+3232+ internal init() {}
3333+3434+ /// Clears all mutable session state. Intended for test harnesses.
3535+ ///
3636+ /// DPoP nonce and clock-skew stores are reset to empty; tokens, keys, host
3737+ /// and delegates are nilled out. The router delegate and its coordinator
3838+ /// are preserved (they hold no user-scoped state).
3939+ public func reset() async {
4040+ host = nil
4141+ accessToken = nil
4242+ refreshToken = nil
4343+ atProtocolDelegate = nil
4444+ tokenRefreshHandler = nil
4545+ dpopPrivateKey = nil
4646+ dpopKeys = nil
4747+ await dpopNonceStore.clear()
4848+ await clockSkewStore.update(offset: 0)
4949+ }
5050+}
5151+5252+// MARK: - Deprecation shim
5353+//
5454+// The original name was `APEnvironment` and the singleton was `.current`.
5555+// The renames below preserve source compatibility for downstream callers
5656+// (bskyKit, EffemKit, Atprosphere) while emitting fix-its that point at the
5757+// new names. A future major release will remove these aliases.
5858+5959+@available(*, deprecated, renamed: "ATProtoSession")
6060+public typealias APEnvironment = ATProtoSession
6161+6262+extension ATProtoSession {
6363+ @available(*, deprecated, renamed: "shared")
6464+ public static var current: ATProtoSession { shared }
3265}