1import UIKit
2
3enum Crash {
4 private static var offered = false
5
6 static func start() {
7 guard let folder = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { return }
8 let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "development"
9 _ = sb_crash_start(folder.appendingPathComponent("crash.txt").path, build, "iOS \(UIDevice.current.systemVersion)")
10 }
11
12 static func offer(from host: UIViewController) {
13 guard !offered, let saved = sb_crash_report() else { return }
14 let report = String(cString: saved)
15 sb_string_free(saved)
16 offered = true
17 let alert = UIAlertController(
18 title: "Snowbound quit unexpectedly", message: "Send a report to help fix the problem. You can review the report before sending it.", preferredStyle: .alert)
19 alert.addAction(UIAlertAction(title: "Send Report", style: .default) { _ in send(report, from: host) })
20 alert.addAction(UIAlertAction(title: "Show Report", style: .default) { _ in show(report, from: host) })
21 alert.addAction(UIAlertAction(title: "Don’t Send", style: .cancel) { _ in sb_crash_forget() })
22 host.present(alert, animated: true)
23 }
24
25 private static func show(_ report: String, from host: UIViewController) {
26 let page = UIViewController()
27 page.title = "Crash Report"
28 let text = UITextView()
29 text.text = report
30 text.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
31 text.isEditable = false
32 text.backgroundColor = .systemBackground
33 page.view = text
34 let navigation = UINavigationController(rootViewController: page)
35 page.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Don’t Send", primaryAction: UIAction { [weak navigation] _ in
36 sb_crash_forget()
37 navigation?.dismiss(animated: true)
38 })
39 page.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Send Report", primaryAction: UIAction { [weak navigation] _ in
40 navigation?.dismiss(animated: true) { send(report, from: host) }
41 })
42 host.present(navigation, animated: true)
43 }
44
45 private static func send(_ report: String, from host: UIViewController) {
46 let session = URLSession(configuration: .ephemeral)
47 var request = URLRequest(url: URL(string: String(cString: sb_crash_address()))!)
48 request.httpMethod = "POST"
49 request.setValue("text/plain; charset=utf-8", forHTTPHeaderField: "Content-Type")
50 request.httpBody = Data(report.utf8)
51 session.dataTask(with: request) { _, response, error in
52 session.finishTasksAndInvalidate()
53 let sent = error == nil && (response as? HTTPURLResponse).map { (200..<300).contains($0.statusCode) } == true
54 DispatchQueue.main.async {
55 if sent {
56 sb_crash_forget()
57 } else {
58 let alert = UIAlertController(title: "Report wasn’t sent", message: "You can try again.", preferredStyle: .alert)
59 alert.addAction(UIAlertAction(title: "Try Again", style: .default) { _ in send(report, from: host) })
60 alert.addAction(UIAlertAction(title: "Don’t Send", style: .cancel) { _ in sb_crash_forget() })
61 host.present(alert, animated: true)
62 }
63 }
64 }.resume()
65 }
66}