A powerful, privacy-focused session replay SDK for iOS applications.
Capture user sessions with video recording, touch visualization, console logs, and network requestsβall synchronized for powerful debugging and UX analysis.
A complete demo application SessionReplayDemo
| Feature | Description |
|---|---|
| Video Recording | H.264 encoded screen capture with configurable quality and frame rate |
| Touch Visualization | Record and overlay touch events with visual indicators |
| Console Log Capture | Automatically intercept print(), NSLog(), and stderr |
| Network Tracking | Monitor all HTTP/HTTPS requests (works with Alamofire SSL pinning) |
| User Identification | Attach user info (userId, email, custom data) to sessions |
| Auto Start/Stop | Configurable automatic session lifecycle management |
| Crash Recovery | Periodic checkpoints to recover sessions after crashes |
| App Groups | Share session data between app and extensions |
| Screen Transitions | Track navigation flow between screens |
| Synchronized Timeline | All events timestamped for video sync playback |
| Local Storage | Save sessions locally with JSON metadata |
| Cloud Upload | Multipart form data upload to your backend |
| Privacy Controls | Redact headers, exclude URLs, sanitize logs |
| SwiftUI & UIKit | Full support with view modifiers and components |
Xcode:
- Go to File > Add Package Dependencies
- Enter:
https://github.com/AlmoutasemNabil/SessionReplaySDK - Select version and add to your target
Package.swift:
dependencies: [
.package(url: "https://github.com/AlmoutasemNabil/SessionReplaySDK", from: "0.2.0")
]import SessionReplaySDK
@main
struct MyApp: App {
init() {
var config = SessionReplayConfig()
config.captureFrameRate = 10
config.jpegCompressionQuality = 0.7
config.autoStartOnLaunch = false // Manual control
config.enableCrashRecovery = true // Save on crash
config.debugLogging = false // Disable in production
SessionReplaySDK.configure(
videoConfig: config,
logConfig: SessionLoggerConfig()
)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}For lightweight logging without video recording:
var config = SessionReplayConfig()
config.enableVideoRecording = false // Disable video, capture logs only
SessionReplaySDK.configure(
videoConfig: config,
logConfig: SessionLoggerConfig()
)
// Start/stop works the same way
SessionReplaySDK.start()
// ... session captures console logs and network requests only
SessionReplaySDK.stop()// After user logs in
SessionReplaySDK.identifyUser(
userId: "user_123",
email: "user@example.com",
name: "John Doe",
additionalInfo: ["plan": "premium", "branchId": "branch_456"]
)
// Or set custom info directly
SessionReplaySDK.setUserInfo([
"userId": "123",
"email": "user@example.com",
"customField": "value"
])// Start recording
SessionReplaySDK.startSession()
// Stop recording
SessionReplaySDK.stopSession()
// Check status
if SessionReplaySDK.isRecording {
print("Recording in progress...")
}struct HomeView: View {
var body: some View {
NavigationView {
// Your content
}
.trackScreen("HomeScreen")
}
}SessionReplaySDK.debug("Debug: User opened settings")
SessionReplaySDK.info("Info: Purchase completed")
SessionReplaySDK.warning("Warning: Low storage")
SessionReplaySDK.error("Error: Network timeout")// Configure endpoint
SessionReplaySDK.configureUpload(
baseURL: URL(string: "https://api.yourserver.com/sessions")!,
apiKey: "your-api-key"
)
// Upload specific session
SessionReplaySDK.uploadSession(sessionId: "abc-123") { result in
switch result {
case .success(let response):
print("Uploaded: \(response.sessionId)")
case .failure(let error):
print("Failed: \(error)")
}
}
// Upload all sessions
SessionReplaySDK.uploadAllSessions { results in
let successful = results.filter { $0.1.isSuccess }.count
print("Uploaded \(successful)/\(results.count) sessions")
}| Option | Default | Description |
|---|---|---|
enableVideoRecording |
true |
Enable video recording (false for logs-only mode) |
captureFrameRate |
1 |
Frames per second (1-30) |
jpegCompressionQuality |
0.3 |
JPEG quality (0.0-1.0) |
captureScale |
1.0 |
Resolution scale (0.25-1.0) |
videoBitrate |
75,000 |
H.264 bitrate in bps |
captureTouches |
true |
Record touch events |
showTouchIndicators |
true |
Draw touch dots on video |
maskSensitiveViews |
true |
Mask marked sensitive views |
sensitiveViewMaskColor |
.gray |
Color for masked areas |
autoMaskTextFields |
true |
Auto-mask text inputs |
autoMaskSecureTextFields |
true |
Auto-mask password fields |
autoMaskViewClasses |
[] |
Custom classes to auto-mask |
maxStorageSize |
50MB |
Max local storage |
storageDirectory |
Documents | Custom storage location |
| Option | Default | Description |
|---|---|---|
autoStartOnLaunch |
false |
Start recording on app launch |
autoStopOnBackground |
true |
Stop when app enters background |
autoStopOnTerminate |
true |
Emergency save on terminate |
enableCrashRecovery |
true |
Save checkpoints periodically |
crashRecoveryInterval |
5.0 |
Seconds between checkpoints |
debugLogging |
true |
Print debug logs to console |
| Option | Default | Description |
|---|---|---|
captureConsoleLogs |
true |
Capture stdout/stderr |
captureNetworkRequests |
true |
Intercept URLSession |
minimumLogLevel |
.debug |
Minimum capture level |
maxLogMessageLength |
2000 |
Truncate long messages |
maxBodySize |
100KB |
Max request/response body |
redactedHeaders |
[auth, cookie...] |
Headers to redact |
excludedURLPatterns |
[] |
URL regex patterns to skip |
logSanitizer |
nil |
Custom sanitization closure |
excludeSDKLogs |
true |
Exclude SDK internal logs from session data |
| Option | Default | Description |
|---|---|---|
baseURL |
Required | Your upload endpoint |
apiKey |
nil |
Bearer token auth |
additionalHeaders |
[:] |
Custom headers |
maxRetries |
3 |
Retry on failure |
timeoutInterval |
120s |
Request timeout |
deleteAfterUpload |
false |
Remove local files |
Share session data between your main app and extensions:
// Configure with App Group
SessionReplaySDK.configureWithAppGroup(
"group.com.yourcompany.yourapp",
videoConfig: config,
logConfig: logConfig
)
// Or manually set storage directory
var config = SessionReplayConfig()
if let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourapp") {
config.storageDirectory = containerURL.appendingPathComponent("SessionReplays", isDirectory: true)
}
SessionReplaySDK.configure(videoConfig: config)Attach user info to sessions for easier debugging:
// Simple identification
SessionReplaySDK.identifyUser(
userId: "user_123",
email: "user@example.com",
name: "John Doe"
)
// With additional custom data
SessionReplaySDK.identifyUser(
userId: "cashier_456",
email: "cashier@foodics.com",
additionalInfo: [
"branchId": "branch_123",
"role": "cashier",
"shiftId": "shift_789"
]
)
// Set individual values
SessionReplaySDK.setUserInfo(key: "subscriptionTier", value: "premium")
// Clear on logout
SessionReplaySDK.clearUserInfo()
// Access current info
let currentUser = SessionReplaySDK.userInfoUser info is saved in both metadata.userInfo and root userInfo in the session JSON.
The SDK automatically saves session checkpoints:
var config = SessionReplayConfig()
config.enableCrashRecovery = true // Enable checkpoints
config.crashRecoveryInterval = 3.0 // Save every 3 seconds
SessionReplaySDK.configure(videoConfig: config)
// Check for incomplete sessions after app restart
let incompleteSessions = SessionReplaySDK.recoverIncompleteSessions()
for sessionId in incompleteSessions {
print("Found incomplete session: \(sessionId)")
// Video segments may be available even if session didn't complete
}{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"startTime": "2025-01-27T10:30:00Z",
"endTime": "2025-01-27T10:35:00Z",
"durationMs": 300000,
"frameCount": 3000,
"videoSegments": ["session_segment0.mp4"],
"userInfo": {
"userId": "user_123",
"email": "user@example.com",
"branchId": "branch_456"
},
"touches": [
{"timestamp": 1500, "location": {"x": 200, "y": 400}, "phase": 0}
],
"logs": [
{"timestamp": 2000, "level": "info", "message": "Button tapped", "source": "stdout"}
],
"networkRequests": [
{"timestamp": 3000, "method": "GET", "url": "https://api.example.com/data", "statusCode": 200, "duration": 150}
],
"screenTransitions": [
{"timestamp": 0, "screenName": "HomeScreen"}
],
"metadata": {
"appVersion": "1.0.0",
"osVersion": "17.0",
"deviceModel": "iPhone15,2",
"locale": "en_US",
"userInfo": {
"userId": "user_123",
"email": "user@example.com"
}
}
}// Recording controls
RecordingControlView()
// Sessions list with upload
SessionsListView()
// Activity timeline (live during recording)
LiveActivityView()
// Activity timeline (for completed sessions)
ActivityTimelineView(session: selectedSession)
// Session player
SessionReplayViewer(session: selectedSession)
// Screen tracking modifier
.trackScreen("ScreenName")
// Touch indicators overlay
.withTouchIndicators()
// Mark sensitive content
.sensitiveContent()// Base view controller with tracking
class MyVC: SessionReplayViewController {
override var screenName: String { "MyScreen" }
}
// Debug view controller
let debugVC = SessionReplayDebugViewController()
present(debugVC, animated: true)
// Mark sensitive views
passwordField.markAsSensitive()The SDK captures all URLSession-based network requests, including:
- Direct URLSession usage
- Alamofire (including custom sessions with SSL pinning)
- FNetwork and other URLSession-based libraries
No additional configuration neededβnetwork capture works automatically.
The SDK sends multipart form data:
| Field | Type | Description |
|---|---|---|
sessionId |
String | Session identifier |
video |
File | MP4 video file |
metadata |
File | JSON metadata file |
Expected Response:
{
"sessionId": "...",
"videoURL": "https://...",
"metadataURL": "https://...",
"message": "Success"
}Testing Upload:
- Use webhook.site for instant testing
- Use requestbin.com as alternative
- Or
https://postman-echo.com/postfor echo testing
The SDK automatically masks sensitive content in screen recordings:
Automatic Masking (enabled by default):
- Secure text fields (password inputs)
- Regular text fields and text views
- Views marked with
markAsSensitive()or.sensitiveContent()
Configuration Options:
var config = SessionReplayConfig()
config.maskSensitiveViews = true // Enable/disable masking
config.sensitiveViewMaskColor = .gray // Mask color
config.autoMaskTextFields = true // Auto-mask UITextField/UITextView
config.autoMaskSecureTextFields = true // Auto-mask password fields
config.autoMaskViewClasses = ["CreditCardView"] // Custom classes to maskManual Masking - UIKit:
// Mark any UIView as sensitive
passwordField.markAsSensitive()
creditCardView.markAsSensitive()
// Check if view is marked
if myView.isSensitive { ... }Manual Masking - SwiftUI:
// Mark any SwiftUI view as sensitive
SecretDataView()
.sensitiveContent()
// With custom mask color
PaymentForm()
.sensitiveContent(maskColor: .black)- Header Redaction: Authorization, Cookie, API keys auto-redacted from network logs
- URL Exclusion: Regex patterns to exclude specific endpoints
- Log Sanitization: Custom closure for PII removal
- Local-First: All data stored locally, upload is opt-in
- No Dependencies: Pure Swift, no third-party tracking code
SessionReplaySDK/
βββ Core/
β βββ Models.swift # Data structures
β βββ SessionReplayManager.swift # Main controller
β βββ VideoWriter.swift # H.264 encoding
βββ Logging/
β βββ SessionLogger.swift # Console capture
β βββ NetworkInterceptor.swift # URL monitoring
βββ Upload/
β βββ SessionUploader.swift # Cloud upload
βββ Integration/
β βββ SwiftUIIntegration.swift # SwiftUI support
β βββ UIKitIntegration.swift # UIKit support
βββ SessionReplaySDK.swift # Public facade
- User Identification: New
identifyUser()andsetUserInfo()APIs to attach user data to sessions - App Group Support: Configure custom storage directory for sharing data between app and extensions
- Auto Start/Stop: New config options for automatic session lifecycle management
- Crash Recovery: Periodic checkpoints to recover sessions after crashes
- Network Capture Improvements: Works with all URLSession-based networking including Alamofire with SSL pinning
- Activity Timeline Views: New
LiveActivityViewandActivityTimelineViewSwiftUI components - Debug Logging Control: New
debugLoggingconfig to reduce console noise in production - Removed Verbose Touch Logging: Touch began/ended events no longer spam the console
- Initial release
- Video recording with H.264 encoding
- Touch event capture and visualization
- Console log interception
- Network request monitoring
- SwiftUI and UIKit integration
- Cloud upload support
We welcome contributions!
- Fork the repository
- Create feature branch:
git checkout -b feature/amazing - Commit changes:
git commit -m 'Add amazing feature' - Push:
git push origin feature/amazing - Open Pull Request
Please include tests and update documentation.
- iOS 15.0+
- Swift 5.9+
- Xcode 15.0+
- Issues: GitHub Issues
- Discussions: GitHub Discussions
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ by AlmoutasemNabil
Β© 2026 AlmoutasemNabil. All rights reserved.
β Star this repo if you find it helpful!