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

Revisit submission of OpenVPN diagnostic report #452

Merged
merged 8 commits into from
Jan 7, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
<EnvironmentVariable
key = "APP_TYPE"
value = "2"
isEnabled = "NO">
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "LOG_LEVEL"
Expand Down
8 changes: 8 additions & 0 deletions Passepartout/App/Extensions/DebugLog+Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ import Foundation
import PassepartoutLibrary

extension DebugLog {
static func decoratedMetadataString() -> String {
decoratedMetadataString(Constants.Global.appName, Constants.Global.appVersionString)
}

static func decoratedMetadataData() -> Data {
decoratedMetadataData(Constants.Global.appName, Constants.Global.appVersionString)
}

func decoratedString() -> String {
decoratedString(Constants.Global.appName, Constants.Global.appVersionString)
}
Expand Down
51 changes: 36 additions & 15 deletions Passepartout/App/L10n/Unlocalized.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,33 +74,54 @@ enum Unlocalized {

static let subject = "\(appName) - Report issue"

static func body(_ description: String, _ metadata: String) -> String {
"Hi,\n\n\(description)\n\n\(metadata)\n\nRegards"
}

static let template = "description of the issue: "
static let bodySentinel = "<replace this with a description of the issue>"

static let maxLogBytes = UInt64(20000)

enum Filenames {
static var debugLog: String {
static let mime = "text/plain"

static var appLog: String {
let fmt = DateFormatter()
fmt.dateFormat = "yyyyMMdd-HHmmss"
let iso = fmt.string(from: Date())
return "debug-\(iso).txt"
return "app-\(iso).txt"
}

static let configuration = "profile.ovpn"
// static let configuration = "profile.ovpn.txt"

static let template = "description of the issue: "
static var tunnelLog: String {
let fmt = DateFormatter()
fmt.dateFormat = "yyyyMMdd-HHmmss"
let iso = fmt.string(from: Date())
return "tunnel-\(iso).txt"
}
}

enum MIME {
static let debugLog = "text/plain"
private static func rawBody(_ description: String, _ metadata: String) -> String {
"Hi,\n\n\(description)\n\n\(metadata)\n\nRegards"
}

// static let configuration = "application/x-openvpn-profile"
static let configuration = "text/plain"
static func body(
providerMetadata: ProviderMetadata?,
lastUpdate: Date?,
purchasedProductIdentifiers: Set<String>?
) -> String {
var content: [String] = ["Hi,\n"]
content.append(bodySentinel)
content.append("\n--\n")
content.append(DebugLog.decoratedMetadataString())
if let providerMetadata {
if let lastUpdate {
content.append("Provider: \(providerMetadata.fullName) (last updated: \(lastUpdate))")
} else {
content.append("Provider: \(providerMetadata.fullName)")
}
}
if let purchasedProductIdentifiers {
content.append("Purchased: \(purchasedProductIdentifiers)")
}
content.append("\n--\n")
content.append("Regards")
return content.joined(separator: "\n")
}
}

Expand Down
9 changes: 5 additions & 4 deletions Passepartout/App/Views/DebugLogView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ struct DebugLogView: View {

private let url: URL

private let shareFilename: String

private let timer: AnyPublisher<Date, Never>

@State private var logLines: [String] = []
Expand All @@ -44,12 +46,11 @@ struct DebugLogView: View {

private let appVersion = Constants.Global.appVersionString

private let shareFilename = Unlocalized.Issues.Filenames.debugLog

init(title: String, url: URL, refreshInterval: TimeInterval?) {
init(title: String, url: URL, filename: String, refreshInterval: TimeInterval?) {
self.title = title
self.url = url
if let refreshInterval = refreshInterval {
shareFilename = filename
if let refreshInterval {
timer = Timer.TimerPublisher(interval: refreshInterval, runLoop: .main, mode: .common)
.autoconnect()
.eraseToAnyPublisher()
Expand Down
53 changes: 38 additions & 15 deletions Passepartout/App/Views/DiagnosticsView+OpenVPN.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,11 @@ private extension DiagnosticsView.OpenVPNView {
}

func reportIssueView() -> some View {
let logURL = vpnManager.debugLogURL(forProtocol: vpnProtocol)
var metadata: ProviderMetadata?
var lastUpdate: Date?
if let name = providerName {
metadata = providerManager.provider(withName: name)
lastUpdate = providerManager.lastUpdate(name, vpnProtocol: vpnProtocol)
}

return ReportIssueView(
ReportIssueView(
isPresented: $isReportingIssue,
vpnProtocol: vpnProtocol,
logURL: logURL,
providerMetadata: metadata,
lastUpdate: lastUpdate
messageBody: messageBody,
logs: logs
)
}

Expand All @@ -163,6 +154,40 @@ private extension DiagnosticsView.OpenVPNView {
return cfg.builder(withFallbacks: false)
}

var messageBody: String {
var providerMetadata: ProviderMetadata?
var lastUpdate: Date?
if let name = providerName {
providerMetadata = providerManager.provider(withName: name)
lastUpdate = providerManager.lastUpdate(name, vpnProtocol: vpnProtocol)
}
return Unlocalized.Issues.body(
providerMetadata: providerMetadata,
lastUpdate: lastUpdate,
purchasedProductIdentifiers: productManager.purchasedProductIdentifiers
)
}

var logs: [MailComposerView.Attachment] {
var pairs: [(url: URL, filename: String)] = []
if let appLogURL {
pairs.append((appLogURL, Unlocalized.Issues.Filenames.appLog))
}
if let tunnelLogURL {
pairs.append((tunnelLogURL, Unlocalized.Issues.Filenames.tunnelLog))
}
return pairs.map {
let logContent = $0.url.trailingContent(bytes: Unlocalized.Issues.maxLogBytes)
let attachment = DebugLog(content: logContent).decoratedData()

return MailComposerView.Attachment(
data: attachment,
mimeType: Unlocalized.Issues.Filenames.mime,
fileName: $0.filename
)
}
}

var appLogURL: URL? {
Passepartout.shared.logger.logFile
}
Expand All @@ -189,9 +214,7 @@ private extension DiagnosticsView.OpenVPNView {

func openReportIssueMailTo() {
let V = Unlocalized.Issues.self
let body = V.body(V.template, DebugLog(content: "--").decoratedString())

guard let url = URL.mailto(to: V.recipient, subject: V.subject, body: body) else {
guard let url = URL.mailto(to: V.recipient, subject: V.subject, body: messageBody) else {
return
}
guard URL.open(url) else {
Expand Down
8 changes: 6 additions & 2 deletions Passepartout/App/Views/DiagnosticsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ private extension DiagnosticsView.DebugLogGroup {
navigationLink(
withTitle: L10n.Diagnostics.Items.AppLog.title,
url: appLogURL,
filename: Unlocalized.Issues.Filenames.appLog,
refreshInterval: nil
)
}
Expand All @@ -106,19 +107,22 @@ private extension DiagnosticsView.DebugLogGroup {
navigationLink(
withTitle: Unlocalized.VPN.vpn,
url: tunnelLogURL,
filename: Unlocalized.Issues.Filenames.tunnelLog,
refreshInterval: refreshInterval
)
}

func navigationLink(withTitle title: String, url: URL?, refreshInterval: TimeInterval?) -> some View {
func navigationLink(withTitle title: String, url: URL?, filename: String, refreshInterval: TimeInterval?) -> some View {
NavigationLink(title) {
url.map {
DebugLogView(
title: title,
url: $0,
filename: filename,
refreshInterval: refreshInterval
)
}
}.disabled(url == nil)
}
.disabled(url == nil)
}
}
38 changes: 6 additions & 32 deletions Passepartout/App/Views/ReportIssueView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,46 +37,20 @@ struct ReportIssueView: View {

private let messageBody: String

private let attachments: [MailComposerView.Attachment]
private let logs: [MailComposerView.Attachment]

init(
isPresented: Binding<Bool>,
vpnProtocol: VPNProtocolType,
logURL: URL?,
providerMetadata: ProviderMetadata? = nil,
lastUpdate: Date? = nil
messageBody: String,
logs: [MailComposerView.Attachment]
) {
_isPresented = isPresented

toRecipients = [Unlocalized.Issues.recipient]
subject = Unlocalized.Issues.subject

let bodyContent = Unlocalized.Issues.template
var bodyMetadata = "--\n\n"
bodyMetadata += DebugLog(content: "").decoratedString()

if let metadata = providerMetadata {
bodyMetadata += "Provider: \(metadata.fullName)\n"
if let lastUpdate = lastUpdate {
bodyMetadata += "Last updated: \(lastUpdate)\n"
}
bodyMetadata += "\n"
}
bodyMetadata += "--"
messageBody = Unlocalized.Issues.body(bodyContent, bodyMetadata)

var attachments: [MailComposerView.Attachment] = []
if let logURL = logURL {
let logContent = logURL.trailingContent(bytes: Unlocalized.Issues.maxLogBytes)
let attachment = DebugLog(content: logContent).decoratedData()

attachments.append(.init(
data: attachment,
mimeType: Unlocalized.Issues.MIME.debugLog,
fileName: Unlocalized.Issues.Filenames.debugLog
))
}
self.attachments = attachments
self.messageBody = messageBody
self.logs = logs
}

var body: some View {
Expand All @@ -85,7 +59,7 @@ struct ReportIssueView: View {
toRecipients: toRecipients,
subject: subject,
messageBody: messageBody,
attachments: attachments
attachments: logs
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public final class ProductManager: NSObject, ObservableObject {

//

public var purchasedProductIdentifiers: Set<String> {
Set(purchasedFeatures.map(\.rawValue))
}

private var purchasedAppBuild: Int?

private var purchasedFeatures: Set<LocalProduct>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,23 @@ import AppKit
#endif

extension DebugLog {
public func decoratedString(_ appName: String, _ appVersion: String) -> String {
public static func decoratedMetadataString(_ appName: String, _ appVersion: String) -> String {
let osVersion: String
let deviceType: String?

#if os(iOS) || os(tvOS)
#if os(iOS) || os(tvOS)
let device: UIDevice = .current
osVersion = "\(device.systemName) \(device.systemVersion)"
#if targetEnvironment(macCatalyst)
#if targetEnvironment(macCatalyst)
deviceType = "\(device.model) (Catalyst)"
#else
#else
deviceType = "\(device.model) (\(device.userInterfaceIdiom.debugDescription))"
#endif
#else
#endif
#else
let os = ProcessInfo().operatingSystemVersion
osVersion = "macOS \(os.majorVersion).\(os.minorVersion).\(os.patchVersion)"
deviceType = nil
#endif
#endif

var metadata = [
"App: \(appName) \(appVersion)",
Expand All @@ -57,10 +57,20 @@ extension DebugLog {
metadata.append("Device: \(deviceType)")
}

var fullText = metadata.joined(separator: "\n")
fullText += "\n\n"
fullText += content
return fullText
return metadata.joined(separator: "\n")
}

public func decoratedString(_ appName: String, _ appVersion: String) -> String {
[Self.decoratedMetadataString(appName, appVersion), content]
.joined(separator: "\n\n")
}

public static func decoratedMetadataData(_ appName: String, _ appVersion: String) -> Data {
guard let data = decoratedMetadataString(appName, appVersion).data(using: .utf8) else {
assertionFailure("Could not encode log metadata to UTF8?")
return Data()
}
return data
}

public func decoratedData(_ appName: String, _ appVersion: String) -> Data {
Expand Down
Loading