Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add urlsession extension for async methods #189

Merged
merged 9 commits into from
Sep 12, 2024

Conversation

marandaneto
Copy link
Member

@marandaneto marandaneto commented Sep 11, 2024

💡 Motivation and Context

https://posthog.slack.com/archives/C05U2CNV6DV/p1725979956451989

Swift methods that are not exposed in the objC runtime cannot be swizzled.
So if people are using those methods for network calls, they don't get network logs.
This exposes an extension that makes their life easier.

💚 How did you test it?

Running the sample with such methods

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

@cameronehrlich
Copy link

cameronehrlich commented Sep 11, 2024

@marandaneto could you also provide postHog* methods for the other async methods on URLSession? Like this:

public extension URLSession {

    func postHogData(for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) {
        let before = Date()
        var after: Date?
        do {
            let (data, response) = try await self.data(for: request, delegate: delegate)
            after = Date()
            captureData(request: request, response: response, before: before, after: after)
            return (data, response)
        } catch {
            captureData(request: request, response: nil, before: before, after: after)
            throw error
        }
    }

    func postHogData(from url: URL, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) {
        let before = Date()
        var after: Date?
        do {
            let (data, response) = try await self.data(from: url, delegate: delegate)
            after = Date()
            captureData(request: nil, response: response, before: before, after: after)
            return (data, response)
        } catch {
            captureData(request: nil, response: nil, before: before, after: after)
            throw error
        }
    }

    func postHogUpload(for request: URLRequest, fromFile fileURL: URL, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) {
        let before = Date()
        var after: Date?
        do {
            let (data, response) = try await self.upload(for: request, fromFile: fileURL, delegate: delegate)
            after = Date()
            captureData(request: request, response: response, before: before, after: after)
            return (data, response)
        } catch {
            captureData(request: request, response: nil, before: before, after: after)
            throw error
        }
    }

    func postHogUpload(for request: URLRequest, from bodyData: Data, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) {
        let before = Date()
        var after: Date?
        do {
            let (data, response) = try await self.upload(for: request, from: bodyData, delegate: delegate)
            after = Date()
            captureData(request: request, response: response, before: before, after: after)
            return (data, response)
        } catch {
            captureData(request: request, response: nil, before: before, after: after)
            throw error
        }
    }

    func postHogDownload(for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (URL, URLResponse) {
        let before = Date()
        var after: Date?
        do {
            let (fileURL, response) = try await self.download(for: request, delegate: delegate)
            after = Date()
            captureData(request: request, response: response, before: before, after: after)
            return (fileURL, response)
        } catch {
            captureData(request: request, response: nil, before: before, after: after)
            throw error
        }
    }

    func postHogDownload(from url: URL, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (URL, URLResponse) {
        let before = Date()
        var after: Date?
        do {
            let (fileURL, response) = try await self.download(from: url, delegate: delegate)
            after = Date()
            captureData(request: nil, response: response, before: before, after: after)
            return (fileURL, response)
        } catch {
            captureData(request: nil, response: nil, before: before, after: after)
            throw error
        }
    }

    func postHogDownload(resumeFrom resumeData: Data, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (URL, URLResponse) {
        let before = Date()
        var after: Date?
        do {
            let (fileURL, response) = try await self.download(resumeFrom: resumeData, delegate: delegate)
            after = Date()
            captureData(request: nil, response: response, before: before, after: after)
            return (fileURL, response)
        } catch {
            captureData(request: nil, response: nil, before: before, after: after)
            throw error
        }
    }

    // MARK: Private methods
    private func captureData(request: URLRequest? = nil, response: URLResponse? = nil, before: Date, after: Date? = nil) {
        // we dont check config.sessionReplayConfig.captureNetworkTelemetry here since this extension
        // has to be called manually anyway
        if !PostHogSDK.shared.isSessionReplayActive() {
            return
        }
        let currentAfter = after ?? Date()

        PostHogReplayIntegration.dispatchQueue.async {
            var snapshotsData: [Any] = []

            var requestsData: [String: Any] = ["duration": currentAfter.toMillis() - before.toMillis(),
                                               "method": request?.httpMethod ?? "GET",
                                               "name": request?.url?.absoluteString ?? (response?.url?.absoluteString ?? ""),
                                               "initiatorType": "fetch",
                                               "entryType": "resource",
                                               "transferSize": response?.expectedContentLength ?? 0,
                                               "timestamp": before.toMillis()]

            if let urlResponse = response as? HTTPURLResponse {
                requestsData["responseStatus"] = urlResponse.statusCode
            }

            let payloadData: [String: Any] = ["requests": [requestsData]]
            let pluginData: [String: Any] = ["plugin": "rrweb/network@1", "payload": payloadData]

            let recordingData: [String: Any] = ["type": 6, "data": pluginData, "timestamp": currentAfter.toMillis()]
            snapshotsData.append(recordingData)

            PostHogSDK.shared.capture("$snapshot", properties: ["$snapshot_source": "mobile", "$snapshot_data": snapshotsData])
        }
    }
}

@marandaneto marandaneto marked this pull request as ready for review September 12, 2024 09:34
@marandaneto marandaneto requested a review from a team September 12, 2024 09:34
Copy link
Member

@pauldambra pauldambra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 my extensive Swift experience being brought to bear 🤣

@marandaneto marandaneto merged commit 994d899 into main Sep 12, 2024
5 of 6 checks passed
@marandaneto marandaneto deleted the chore/urlsession-extension branch September 12, 2024 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants