porthole capturing on URLSession · snaplen ∞ · in-process

porthole

0000  70 6f 72 74 68 6f 6c 65                          |porthole|

An in-app network inspector for iOS & macOS. no proxy · no certificates · no desktop app · just a SwiftUI viewer compiled into your build.

[ install » ] [ read the source ]
0000  6e 6f 20 70 72 6f 78 79  2e 20 6e 6f 20 63 65 72 |no proxy. no cer|
0010  74 2e                                            |t.|
0x0040 CAPTURE

Porthole hooks your app’s URLSession traffic and records every request, response, and WebSocket frame in memory, then renders them in a native SwiftUI viewer you drop anywhere. Interception happens in-process, so there is nothing to configure on the network.

A fork of Proxyman / Atlantis. Removes the Bonjour transport and desktop-app dependency, replaced by an on-device viewer.

porthole: live capture 5 captured · 0 dropped
GET   api.acme.io/v1/user        200   142 ms
POST  api.acme.io/v1/login       201   318 ms
GET   cdn.acme.io/feed.json      304    11 ms
WS    socket.acme.io/live        ↑↓    open
GET   cdn.acme.io/avatar.png     500   902 ms
0x0080 THE PACKET

Every feature, laid out as header fields. Read it like a packet.

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
SRC bytes 0–1
HTTP · HTTPS
Every URLSession request & response, captured in-process.
DST bytes 2–3
WebSocket
Frames in both directions: ↑ send, ↓ receive.
PAYLOAD bytes 4–7
SwiftUI Viewer
Headers, bodies, timing & status, inspected on-device.
EXPORT bytes 8–11
cURL · HAR
Lift any entry out of the app and replay it elsewhere.
FLAGS bytes 12–13
pause / resume / clear
Control capture at runtime from the store.
WINDOW byte 14
ring buffer
Fixed capacity; oldest evicted.
OPT byte 15
memory-only
Never touches disk.
0000  73 77 69 66 74 20 70 61  63 6b 61 67 65 20 61 64 |swift package ad|
0010  64                                               |d|
0x0100 INSTALL

Add via Swift Package Manager: File › Add Package Dependencies…, or in Package.swift:

// Package.swift
dependencies: [
    .package(
        url: "https://github.com/sohandotgit/Porthole.git",
        branch: "main"
    )
]
0x0140 START

Begin capture once, early in the app lifecycle, guarded to debug builds.

import SwiftUI
#if DEBUG
import Atlantis
#endif

@main
struct MyApp: App {
    init() {
        #if DEBUG
        Atlantis.start()
        #endif
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}
0x0180 INSPECT

Present the built-in viewer anywhere: a sheet, a tab, or a shake-gesture overlay.

import Atlantis

struct DebugMenu: View {
    @State private var showTraffic = false

    var body: some View {
        Button("Network Log") { showTraffic = true }
            .sheet(isPresented: $showTraffic) {
                NavigationStack {
                    AtlantisTrafficListView()
                }
            }
    }
}
0x01c0 CONTROLS

The shared TrafficStore exposes runtime controls.

MEMBER TYPE DESCRIPTION
clear() func Empties the ring buffer and removes all recorded traffic.
isPaused Bool When true, new requests are ignored until resumed.
capacity Int Maximum entries retained; oldest are evicted first. Default 500.
0x0200 EXPORT
  • »cURL: copy a single request as a runnable curl command, headers and body included.
  • »HAR: export the whole session as an HTTP Archive for Proxyman, Charles, or DevTools.
0x0240 REQUIRES

iOS 16.0+ · macOS 11+ · Mac Catalyst 13.0+ · tvOS 13.0+ · watchOS 10.0+ · Xcode 14+ · Swift 5.0+. No third-party dependencies.

0x0280 NOTES

Capture is memory-only: traffic lives in a bounded in-memory store for the process lifetime and is never written to disk or sent off-device. Debugging tools like Map Local, Breakpoint, and Scripting aren't supported; use a normal proxy for those. Keep Atlantis.start() behind #if DEBUG so no interception ships in release.

0000  6d 65 6d 6f 72 79 20 6f  6e 6c 79                |memory only|