• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supabase-swift / 17238365990

26 Aug 2025 12:38PM UTC coverage: 48.936% (-28.5%) from 77.386%
17238365990

Pull #781

github

web-flow
Merge 95ac7642e into e4d8c3718
Pull Request #781: RFC: Migrate HTTP networking from URLSession to Alamofire

287 of 986 new or added lines in 26 files covered. (29.11%)

1397 existing lines in 30 files now uncovered.

3448 of 7046 relevant lines covered (48.94%)

5.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

26.32
/Sources/Auth/Internal/SessionManager.swift
1
import Foundation
2

3
struct SessionManager: Sendable {
4
  var session: @Sendable () async throws -> Session
5
  var refreshSession: @Sendable (_ refreshToken: String) async throws -> Session
6
  var update: @Sendable (_ session: Session) async -> Void
7
  var remove: @Sendable () async -> Void
8

9
  var startAutoRefresh: @Sendable () async -> Void
10
  var stopAutoRefresh: @Sendable () async -> Void
11
}
12

13
extension SessionManager {
14
  static func live(clientID: AuthClientID) -> Self {
6✔
15
    let instance = LiveSessionManager(clientID: clientID)
6✔
16
    return Self(
6✔
17
      session: { try await instance.session() },
6✔
18
      refreshSession: { try await instance.refreshSession($0) },
6✔
19
      update: { await instance.update($0) },
6✔
20
      remove: { await instance.remove() },
6✔
21
      startAutoRefresh: { await instance.startAutoRefreshToken() },
6✔
22
      stopAutoRefresh: { await instance.stopAutoRefreshToken() }
6✔
23
    )
6✔
24
  }
6✔
25
}
26

27
private actor LiveSessionManager {
UNCOV
28
  private var configuration: AuthClient.Configuration { Dependencies[clientID].configuration }
×
29
  private var sessionStorage: SessionStorage { Dependencies[clientID].sessionStorage }
3✔
UNCOV
30
  private var eventEmitter: AuthStateChangeEventEmitter { Dependencies[clientID].eventEmitter }
×
31
  private var logger: (any SupabaseLogger)? { Dependencies[clientID].logger }
5✔
UNCOV
32
  private var api: APIClient { Dependencies[clientID].api }
×
33

34
  private var inFlightRefreshTask: Task<Session, any Error>?
35
  private var startAutoRefreshTokenTask: Task<Void, Never>?
36

37
  let clientID: AuthClientID
38

39
  init(clientID: AuthClientID) {
6✔
40
    self.clientID = clientID
6✔
41
  }
6✔
42

43
  func session() async throws -> Session {
3✔
44
    try await trace(using: logger) {
3✔
45
      guard let currentSession = sessionStorage.get() else {
3✔
46
        logger?.debug("session missing")
2✔
47
        throw AuthError.sessionMissing
2✔
48
      }
2✔
49

1✔
50
      if !currentSession.isExpired {
1✔
UNCOV
51
        return currentSession
×
52
      }
1✔
53

1✔
54
      logger?.debug("session expired")
1✔
55
      return try await refreshSession(currentSession.refreshToken)
1✔
56
    }
3✔
57
  }
1✔
58

UNCOV
59
  func refreshSession(_ refreshToken: String) async throws -> Session {
×
UNCOV
60
    try await SupabaseLoggerTaskLocal.$additionalContext.withValue(
×
UNCOV
61
      merging: [
×
UNCOV
62
        "refresh_id": .string(UUID().uuidString),
×
UNCOV
63
        "refresh_token": .string(refreshToken),
×
UNCOV
64
      ]
×
UNCOV
65
    ) {
×
UNCOV
66
      try await trace(using: logger) {
×
UNCOV
67
        if let inFlightRefreshTask {
×
UNCOV
68
          logger?.debug("Refresh already in flight")
×
UNCOV
69
          return try await inFlightRefreshTask.value
×
UNCOV
70
        }
×
UNCOV
71

×
UNCOV
72
        inFlightRefreshTask = Task {
×
UNCOV
73
          logger?.debug("Refresh task started")
×
UNCOV
74

×
UNCOV
75
          defer {
×
UNCOV
76
            inFlightRefreshTask = nil
×
UNCOV
77
            logger?.debug("Refresh task ended")
×
UNCOV
78
          }
×
UNCOV
79

×
UNCOV
80
          let session = try await api.execute(
×
NEW
81
            configuration.url.appendingPathComponent("token"),
×
NEW
82
            method: .post,
×
NEW
83
            query: ["grant_type": "refresh_token"],
×
NEW
84
            body: UserCredentials(refreshToken: refreshToken)
×
UNCOV
85
          )
×
NEW
86
          .serializingDecodable(Session.self, decoder: configuration.decoder)
×
NEW
87
          .value
×
UNCOV
88

×
UNCOV
89
          update(session)
×
UNCOV
90
          eventEmitter.emit(.tokenRefreshed, session: session)
×
UNCOV
91

×
UNCOV
92
          return session
×
UNCOV
93
        }
×
UNCOV
94

×
UNCOV
95
        return try await inFlightRefreshTask!.value
×
UNCOV
96
      }
×
UNCOV
97
    }
×
UNCOV
98
  }
×
99

UNCOV
100
  func update(_ session: Session) {
×
UNCOV
101
    sessionStorage.store(session)
×
UNCOV
102
  }
×
103

UNCOV
104
  func remove() {
×
UNCOV
105
    sessionStorage.delete()
×
UNCOV
106
  }
×
107

108
  func startAutoRefreshToken() {
×
109
    logger?.debug("start auto refresh token")
×
110

×
111
    startAutoRefreshTokenTask?.cancel()
×
112
    startAutoRefreshTokenTask = Task {
×
113
      while !Task.isCancelled {
×
114
        await autoRefreshTokenTick()
×
115
        try? await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(autoRefreshTickDuration))
×
116
      }
×
117
    }
×
118
  }
×
119

120
  func stopAutoRefreshToken() {
×
121
    logger?.debug("stop auto refresh token")
×
122
    startAutoRefreshTokenTask?.cancel()
×
123
    startAutoRefreshTokenTask = nil
×
124
  }
×
125

126
  private func autoRefreshTokenTick() async {
×
127
    await trace(using: logger) {
×
128
      let now = Date().timeIntervalSince1970
×
129

×
130
      guard let session = sessionStorage.get() else {
×
131
        return
×
132
      }
×
133

×
134
      let expiresInTicks = Int((session.expiresAt - now) / autoRefreshTickDuration)
×
135
      logger?.debug(
×
136
        "access token expires in \(expiresInTicks) ticks, a tick lasts \(autoRefreshTickDuration)s, refresh threshold is \(autoRefreshTickThreshold) ticks"
×
137
      )
×
138

×
139
      if expiresInTicks <= autoRefreshTickThreshold {
×
140
        _ = try? await refreshSession(session.refreshToken)
×
141
      }
×
142
    }
×
143
  }
×
144
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc