From 5fdb9fdcd426a58bb0b4c496a3b564f880661b57 Mon Sep 17 00:00:00 2001 From: Revanth Date: Wed, 26 Aug 2026 12:26:24 +0530 Subject: [PATCH] Capture traffic from sessions that own their configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit URLProtocol.registerClass only reaches URLSession.shared. Any URLSession built with its own configuration resolves its handler chain from that configuration's protocolClasses, snapshotted at init, and never consults the global registry — so Ktor's Darwin engine (PTEShared), and any SDK that builds its own session, was invisible to the inspector. Swizzle the protocolClasses getter so InspectorURLProtocol lands in the snapshot as each configuration is read during session construction. Because those sessions hold a direct class reference, unregister() no longer stops them, so gate shouldIntercept on an isCapturing flag to keep disable() effective. Co-Authored-By: Claude Opus 5 (1M context) --- .../NetworkInspector/NetworkInterceptor.swift | 5 +++ .../URLSessionConfigurationSwizzle.swift | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 Sources/NetworkInspector/URLSessionConfigurationSwizzle.swift diff --git a/Sources/NetworkInspector/NetworkInterceptor.swift b/Sources/NetworkInspector/NetworkInterceptor.swift index b9af80c..ec9535a 100644 --- a/Sources/NetworkInspector/NetworkInterceptor.swift +++ b/Sources/NetworkInspector/NetworkInterceptor.swift @@ -11,12 +11,16 @@ import Foundation final class NetworkInterceptor { private static var allowedBaseURLs: [String] = [] + private static var isCapturing = false static func register() { + isCapturing = true + URLSessionConfiguration.installInspectorSwizzle() URLProtocol.registerClass(InspectorURLProtocol.self) } static func unregister() { + isCapturing = false URLProtocol.unregisterClass(InspectorURLProtocol.self) } @@ -32,6 +36,7 @@ final class NetworkInterceptor { } static func shouldIntercept(_ url: URL?) -> Bool { + guard isCapturing else { return false } guard let absoluteString = url?.absoluteString.lowercased() else { return false } guard !allowedBaseURLs.isEmpty else { return true } diff --git a/Sources/NetworkInspector/URLSessionConfigurationSwizzle.swift b/Sources/NetworkInspector/URLSessionConfigurationSwizzle.swift new file mode 100644 index 0000000..be6ab36 --- /dev/null +++ b/Sources/NetworkInspector/URLSessionConfigurationSwizzle.swift @@ -0,0 +1,41 @@ +// +// URLSessionConfigurationSwizzle.swift +// NetworkInspector +// +// Created by Revanth A on 26/08/26. +// + + +import Foundation +import ObjectiveC + +extension URLSessionConfiguration { + + private static var isSwizzled = false + + @objc dynamic var inspector_protocolClasses: [AnyClass]? { + var classes = self.inspector_protocolClasses ?? [] + if !classes.contains(where: { $0 == InspectorURLProtocol.self }) { + classes.insert(InspectorURLProtocol.self, at: 0) + } + return classes + } + + static func installInspectorSwizzle() { + guard !isSwizzled else { return } + + guard + let original = class_getInstanceMethod( + URLSessionConfiguration.self, + #selector(getter: URLSessionConfiguration.protocolClasses) + ), + let replacement = class_getInstanceMethod( + URLSessionConfiguration.self, + #selector(getter: URLSessionConfiguration.inspector_protocolClasses) + ) + else { return } + + method_exchangeImplementations(original, replacement) + isSwizzled = true + } +}