Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
test:
name: Test
runs-on: macos-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build and Test
run: |
xcodebuild test \
-scheme Traceback \
-destination 'platform=macOS,arch=arm64,variant=Mac Catalyst'

package-validation:
name: Package Validation
runs-on: macos-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate Package Resolution
run: swift package resolve

- name: Show Package Dependencies
run: swift package show-dependencies
13 changes: 13 additions & 0 deletions Sources/Traceback/Private/Dependencies/Logger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// TracebackLogger.swift
// traceback-ios
//
// Created by Sergi Hernanz on 7/4/25.
//

struct Logger {
var info: (_ message: @autoclosure @escaping () -> String) -> Void
var debug: (_ message: @autoclosure @escaping () -> String) -> Void
var error: (_ message: @autoclosure @escaping () -> String) -> Void
}

27 changes: 27 additions & 0 deletions Sources/Traceback/Private/Dependencies/Network.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// APIClient.swift
// traceback-ios
//
// Created by Sergi Hernanz on 7/4/25.
//

import Foundation

struct NetworkConfiguration: Sendable {
let host: URL
init(host: URL) {
self.host = host
}
}

struct Network: Sendable {
let fetchData: @Sendable (URLRequest) async throws -> (Data, URLResponse)
}

extension Network {
func fetch<T: Decodable>(_ type: T.Type, request: URLRequest) async throws -> T {
let (jsonData, _) = try await fetchData(request)
return try JSONDecoder().decode(type, from: jsonData)
}
}

18 changes: 18 additions & 0 deletions Sources/Traceback/Private/Dependencies/SystemInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// SystemInfo.swift
// Traceback
//
// Created by Sergi Hernanz on 27/9/25.
//

import Foundation

struct SystemInfo: Equatable {
let installationTime: TimeInterval
let deviceModelName: String
let sdkVersion: String
let localeIdentifier: String
let timezone: TimeZone
let osVersion: String
let bundleId: String
}
15 changes: 15 additions & 0 deletions Sources/Traceback/Private/Dependencies/WebViewInfoReader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Navigator.swift
// Traceback
//
// Created by Sergi Hernanz on 27/9/25.
//

struct WebViewInfo {
let language: String?
let appVersion: String?
}

struct WebViewInfoReader {
let getInfo: () async -> WebViewInfo?
}
32 changes: 32 additions & 0 deletions Sources/Traceback/Private/DependenciesImpl/LoggerImpl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// LoggerImpl.swift
// Traceback
//
// Created by Sergi Hernanz on 27/9/25.
//

import os

extension Logger {
// init {
static func live(
level: TracebackConfiguration.LogLevel = .info
) -> Logger {
let subsystem: String = "com.inqbarna.traceback"
let logger: os.Logger = os.Logger(subsystem: subsystem, category: "traceback")
let level: TracebackConfiguration.LogLevel = level
return Logger(
info: { message in
guard level == .info || level == .debug else { return }
logger.info("\(message())")
},
debug: { message in
guard level == .debug else { return }
logger.debug("\(message())")
},
error: { message in
logger.error("\(message())")
}
)
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
//
// APIClient.swift
// traceback-ios
// NetworkImpl.swift
// Traceback
//
// Created by Sergi Hernanz on 7/4/25.
// Created by Sergi Hernanz on 27/9/25.
//

import Foundation

struct NetworkConfiguration: Sendable {
let host: URL

init(host: URL) {
self.host = host
extension Network {
public static let live = Network { request in
do {
let (data, response) = try await URLSession.shared.data(for: request)
return (data, response)
} catch {
throw NetworkError(error: error)
}
}
}

struct Network: Sendable {
let fetchData: @Sendable (URLRequest) async throws -> (Data, URLResponse)
}

extension NetworkError {
init(error: Swift.Error) {
if let alreadyNetworkError = error as? Self {
Expand Down Expand Up @@ -49,21 +48,3 @@ extension NetworkError {
}
}
}

extension Network {
func fetch<T: Decodable>(_ type: T.Type, request: URLRequest) async throws -> T {
let (jsonData, _) = try await fetchData(request)
return try JSONDecoder().decode(type, from: jsonData)
}
}

extension Network {
public static let live = Network { request in
do {
let (data, response) = try await URLSession.shared.data(for: request)
return (data, response)
} catch {
throw NetworkError(error: error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@
import Foundation
import UIKit

struct SystemInfo {
let installationTime: TimeInterval
let deviceModelName: String
let sdkVersion: String
let localeIdentifier: String
let timezone: TimeZone
let osVersion: String
let bundleId: String
}

enum TracebackSystemImpl {
@MainActor
static func systemInfo() -> SystemInfo {
Expand Down Expand Up @@ -63,7 +53,8 @@ enum TracebackSystemImpl {
private static func sdkVersion() -> String {
guard let infoDictSDKVersion = Bundle(for: TracebackSDKImpl.self)
.infoDictionary?["CFBundleShortVersionString"] as? String else {
assertionFailure()
// fails in unit test
// assertionFailure()
return "1.0.0"
}
return infoDictSDKVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
// Created by Sergi Hernanz on 7/4/25.
//


import WebKit

final class WebViewNavigatorReader: NSObject, WKNavigationDelegate {
private var webView: WKWebView?
private var continuation: CheckedContinuation<Navigator?, Never>?

struct Navigator {
let language: String?
let appVersion: String?
extension WebViewInfoReader {
static func live() -> WebViewInfoReader {
WebViewInfoReader {
await WebViewNavigatorReader().getWebViewInfo()
}
}
}

private final class WebViewNavigatorReader: NSObject, WKNavigationDelegate {
private var webView: WKWebView?
private var continuation: CheckedContinuation<WebViewInfo?, Never>?

func getWebViewInfo() async -> Navigator? {
func getWebViewInfo() async -> WebViewInfo? {
await withCheckedContinuation { continuation in
self.continuation = continuation
let config = WKWebViewConfiguration()
Expand Down Expand Up @@ -48,7 +50,7 @@ final class WebViewNavigatorReader: NSObject, WKNavigationDelegate {
webView.evaluateJavaScript("window.generateDeviceLanguage()") { [weak self] language, _ in
let languageString = language as? String
self?.continuation?.resume(
returning: Navigator(
returning: WebViewInfo(
language: languageString,
appVersion: appVersionString
)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Traceback/Private/DeviceFingerprint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct DeviceFingerprint: Codable, Equatable, Sendable {
func createDeviceFingerprint(
system: SystemInfo,
linkFromClipboard: URL?,
webviewInfo: WebViewNavigatorReader.Navigator?
webviewInfo: WebViewInfo?
) -> DeviceFingerprint {

let isCompatibilityMode =
Expand Down
81 changes: 81 additions & 0 deletions Sources/Traceback/Private/DiagnosticsResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation

// MARK: - Diagnostics Domain Model

public struct DiagnosticsResult: Sendable {
let systemInfo: SystemInfo
let configuration: ConfigurationValidation
let appConfiguration: AppConfigurationValidation
let associatedDomains: AssociatedDomainsValidation
let summary: Summary

public struct ConfigurationValidation: Sendable {
let mainHostScheme: HostSchemeValidation
let mainHostname: HostnameValidation
let additionalHosts: [AdditionalHostValidation]
let clipboardWarning: Bool

public struct HostSchemeValidation: Sendable {
let isValid: Bool
let scheme: String?
}

public struct HostnameValidation: Sendable {
let isValid: Bool
let hostname: String?
}

public struct AdditionalHostValidation: Sendable {
let url: String
let isValid: Bool
}
}

public struct AppConfigurationValidation: Sendable {
let appDelegate: AppDelegateValidation
let urlScheme: URLSchemeValidation

public struct AppDelegateValidation: Sendable {
let hasDelegate: Bool
let respondsToOpenURL: Bool
}

public struct URLSchemeValidation: Sendable {
let expectedScheme: String
let isFound: Bool
}
}

public struct AssociatedDomainsValidation: Sendable {
let hasEntitlements: Bool
let mainDomain: DomainValidation?
let additionalDomains: [DomainValidation]

public struct DomainValidation: Sendable {
let domain: String
let isFound: Bool
}
}

public struct Summary: Sendable {
let errorCount: Int
let warningCount: Int
let isSimulator: Bool

public var status: Status {
if errorCount == 0 && warningCount == 0 {
return .success
} else if errorCount == 0 {
return .warningsOnly
} else {
return .hasErrors
}
}

public enum Status: Sendable {
case success
case warningsOnly
case hasErrors
}
}
}
32 changes: 0 additions & 32 deletions Sources/Traceback/Private/Logger.swift

This file was deleted.

Loading