SentryClient.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2019-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CocoaLumberjackSwift
  22. import Sentry
  23. @objc class SentryClient: NSObject {
  24. private static let sentryNotEnabled = "SENTRY_NOT_ENABLED"
  25. @objc override init() {
  26. super.init()
  27. }
  28. /**
  29. Create Sentry and start crash handler.
  30. - Parameters:
  31. - rootViewController: Parent view controller
  32. */
  33. @objc func start(rootViewController: UIViewController?) {
  34. guard SentryClient.isEnabled(),
  35. let sentryDsn = BundleUtil.object(forInfoDictionaryKey: "SentryClientDsn") as? String else {
  36. return
  37. }
  38. do {
  39. let options = try Sentry.Options(dict: ["dsn": sentryDsn])
  40. options.beforeSend = { event in
  41. if let appDevice = event.context?["app"]?["device_app_hash"] as? String {
  42. // Save anonymous app device, it will be displayed under Settings - Advanced
  43. UserSettings.shared()?.sentryAppDevice = appDevice
  44. // Send app device hash to count users per event on sentry ui
  45. let user = User(userId: appDevice)
  46. event.user = user
  47. }
  48. if event.exceptions?.first?.value != nil {
  49. event.exceptions?.first?.value = self.redact(exceptionDescription: event.exceptions!.first!.value)
  50. }
  51. var send: Bool = false
  52. let dispatch = DispatchGroup()
  53. dispatch.enter()
  54. DispatchQueue.main.async {
  55. let confirm = UIAlertController(title: String(format: BundleUtil.localizedString(forKey: "sentry_crash_send_title"), "Threema"), message: BundleUtil.localizedString(forKey: "sentry_crash_send_description"), preferredStyle: .alert)
  56. confirm.addTextField { (textField) in
  57. textField.placeholder = BundleUtil.localizedString(forKey: "sentry_crash_comment_placeholder")
  58. }
  59. confirm.addAction(UIAlertAction(title: BundleUtil.localizedString(forKey: "sentry_crash_send_yes"), style: .default, handler: { (a) -> Void in
  60. if let textField = confirm.textFields?.first {
  61. event.message = textField.text ?? ""
  62. }
  63. send = true
  64. dispatch.leave()
  65. }))
  66. confirm.addAction(UIAlertAction(title: BundleUtil.localizedString(forKey: "sentry_crash_send_no"), style: .cancel, handler: { (a) -> Void in
  67. dispatch.leave()
  68. }))
  69. if let vc = rootViewController {
  70. DispatchQueue.main.async {
  71. vc.present(confirm, animated: true, completion: nil)
  72. }
  73. }
  74. else {
  75. dispatch.leave()
  76. }
  77. }
  78. dispatch.wait()
  79. if send {
  80. return event
  81. }
  82. return nil
  83. }
  84. SentrySDK.start(options: options)
  85. } catch {
  86. DDLogError("Could not start Sentry!")
  87. }
  88. }
  89. private func redact(exceptionDescription : String) -> String {
  90. let keys = ["encryptionKey", "k:", "password", "blobId", "blobThumbnailId", "\"k\""]
  91. var linesArray = exceptionDescription.linesArray
  92. for i in 0..<linesArray.count {
  93. for k in keys {
  94. if linesArray[i].contains(k) {
  95. let range = linesArray[i].range(of: k)!
  96. linesArray[i] = linesArray[i][..<range.lowerBound] + "***redacted***"
  97. }
  98. }
  99. }
  100. return linesArray.joined(separator: "\n")
  101. }
  102. /**
  103. Is Sentry enabled or not dependent on is file "SENTRY_NOT_ENABLED" exists in App documents directory. When the App crashes on start because of Sentry start handler, can the file adding per iTunes to disable it.
  104. - Returns: true -> start Sentry crash handler
  105. */
  106. private static func isEnabled() -> Bool {
  107. return !FileUtility.isExists(fileUrl: FileUtility.appDocumentsDirectory?.appendingPathComponent(sentryNotEnabled))
  108. }
  109. }