mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Merge pull request #5743 from vector-im/langleyd/5017_uisi_autoreporter
App: UISI AutoReporting
This commit is contained in:
commit
0f89eb014e
13 changed files with 658 additions and 107 deletions
|
@ -191,6 +191,7 @@ final class BuildSettings: NSObject {
|
||||||
static let bugReportEndpointUrlString = "https://riot.im/bugreports"
|
static let bugReportEndpointUrlString = "https://riot.im/bugreports"
|
||||||
// Use the name allocated by the bug report server
|
// Use the name allocated by the bug report server
|
||||||
static let bugReportApplicationId = "riot-ios"
|
static let bugReportApplicationId = "riot-ios"
|
||||||
|
static let bugReportUISIId = "element-auto-uisi"
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Integrations
|
// MARK: - Integrations
|
||||||
|
@ -379,6 +380,9 @@ final class BuildSettings: NSObject {
|
||||||
// MARK: - Secrets Recovery
|
// MARK: - Secrets Recovery
|
||||||
static let secretsRecoveryAllowReset = true
|
static let secretsRecoveryAllowReset = true
|
||||||
|
|
||||||
|
// MARK: - UISI Autoreporting
|
||||||
|
static let cryptoUISIAutoReportingEnabled = false
|
||||||
|
|
||||||
// MARK: - Polls
|
// MARK: - Polls
|
||||||
|
|
||||||
static var pollsEnabled: Bool {
|
static var pollsEnabled: Bool {
|
||||||
|
|
|
@ -634,6 +634,7 @@ Tap the + to start adding people.";
|
||||||
"settings_labs_enable_ringing_for_group_calls" = "Ring for group calls";
|
"settings_labs_enable_ringing_for_group_calls" = "Ring for group calls";
|
||||||
"settings_labs_enabled_polls" = "Polls";
|
"settings_labs_enabled_polls" = "Polls";
|
||||||
"settings_labs_enable_threads" = "Threaded messaging";
|
"settings_labs_enable_threads" = "Threaded messaging";
|
||||||
|
"settings_labs_enable_auto_report_decryption_errors" = "Auto Report Decryption Errors";
|
||||||
"settings_labs_use_only_latest_user_avatar_and_name" = "Show latest avatar and name for users in message history";
|
"settings_labs_use_only_latest_user_avatar_and_name" = "Show latest avatar and name for users in message history";
|
||||||
|
|
||||||
"settings_version" = "Version %@";
|
"settings_version" = "Version %@";
|
||||||
|
@ -2435,7 +2436,7 @@ Tap the + to start adding people.";
|
||||||
"language_picker_title" = "Choose a language";
|
"language_picker_title" = "Choose a language";
|
||||||
"language_picker_default_language" = "Default (%@)";
|
"language_picker_default_language" = "Default (%@)";
|
||||||
|
|
||||||
/* -*-
|
/* -*-
|
||||||
Automatic localization for en
|
Automatic localization for en
|
||||||
|
|
||||||
The following key/value pairs were extracted from the android i18n file:
|
The following key/value pairs were extracted from the android i18n file:
|
||||||
|
@ -2518,17 +2519,17 @@ Tap the + to start adding people.";
|
||||||
"notice_room_history_visible_to_members_from_joined_point_by_you" = "You made future room history visible to all room members, from the point they joined.";
|
"notice_room_history_visible_to_members_from_joined_point_by_you" = "You made future room history visible to all room members, from the point they joined.";
|
||||||
"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "You made future messages visible to everyone, from when they joined.";
|
"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "You made future messages visible to everyone, from when they joined.";
|
||||||
|
|
||||||
// Room Screen
|
// Room Screen
|
||||||
|
|
||||||
// general errors
|
// general errors
|
||||||
|
|
||||||
// Home Screen
|
// Home Screen
|
||||||
|
|
||||||
// Last seen time
|
// Last seen time
|
||||||
|
|
||||||
// call events
|
// call events
|
||||||
|
|
||||||
/* -*-
|
/* -*-
|
||||||
Automatic localization for en
|
Automatic localization for en
|
||||||
|
|
||||||
The following key/value pairs were extracted from the android i18n file:
|
The following key/value pairs were extracted from the android i18n file:
|
||||||
|
@ -2536,9 +2537,9 @@ Tap the + to start adding people.";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// titles
|
// titles
|
||||||
|
|
||||||
// button names
|
// button names
|
||||||
"send" = "Send";
|
"send" = "Send";
|
||||||
"copy_button_name" = "Copy";
|
"copy_button_name" = "Copy";
|
||||||
"resend" = "Resend";
|
"resend" = "Resend";
|
||||||
|
@ -2546,7 +2547,7 @@ Tap the + to start adding people.";
|
||||||
"share" = "Share";
|
"share" = "Share";
|
||||||
"delete" = "Delete";
|
"delete" = "Delete";
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
"action_logout" = "Logout";
|
"action_logout" = "Logout";
|
||||||
"create_room" = "Create Room";
|
"create_room" = "Create Room";
|
||||||
"login" = "Login";
|
"login" = "Login";
|
||||||
|
@ -2561,32 +2562,32 @@ Tap the + to start adding people.";
|
||||||
"unban" = "Un-ban";
|
"unban" = "Un-ban";
|
||||||
"message_unsaved_changes" = "There are unsaved changes. Leaving will discard them.";
|
"message_unsaved_changes" = "There are unsaved changes. Leaving will discard them.";
|
||||||
|
|
||||||
// Login Screen
|
// Login Screen
|
||||||
"login_error_already_logged_in" = "Already logged in";
|
"login_error_already_logged_in" = "Already logged in";
|
||||||
"login_error_must_start_http" = "URL must start with http[s]://";
|
"login_error_must_start_http" = "URL must start with http[s]://";
|
||||||
|
|
||||||
// members list Screen
|
// members list Screen
|
||||||
|
|
||||||
// accounts list Screen
|
// accounts list Screen
|
||||||
|
|
||||||
// image size selection
|
// image size selection
|
||||||
|
|
||||||
// invitation members list Screen
|
// invitation members list Screen
|
||||||
|
|
||||||
// room creation dialog Screen
|
// room creation dialog Screen
|
||||||
|
|
||||||
// room info dialog Screen
|
// room info dialog Screen
|
||||||
|
|
||||||
// room details dialog screen
|
// room details dialog screen
|
||||||
|
|
||||||
// contacts list screen
|
// contacts list screen
|
||||||
"invitation_message" = "I\'d like to chat with you with matrix. Please, visit the website http://matrix.org to have more information.";
|
"invitation_message" = "I\'d like to chat with you with matrix. Please, visit the website http://matrix.org to have more information.";
|
||||||
|
|
||||||
// Settings screen
|
// Settings screen
|
||||||
"settings_title_config" = "Configuration";
|
"settings_title_config" = "Configuration";
|
||||||
"settings_title_notifications" = "Notifications";
|
"settings_title_notifications" = "Notifications";
|
||||||
|
|
||||||
// Notification settings screen
|
// Notification settings screen
|
||||||
"notification_settings_disable_all" = "Disable all notifications";
|
"notification_settings_disable_all" = "Disable all notifications";
|
||||||
"notification_settings_enable_notifications" = "Enable notifications";
|
"notification_settings_enable_notifications" = "Enable notifications";
|
||||||
"notification_settings_enable_notifications_warning" = "All notifications are currently disabled for all devices.";
|
"notification_settings_enable_notifications_warning" = "All notifications are currently disabled for all devices.";
|
||||||
|
@ -2613,10 +2614,10 @@ Tap the + to start adding people.";
|
||||||
"notification_settings_by_default" = "By default...";
|
"notification_settings_by_default" = "By default...";
|
||||||
"notification_settings_notify_all_other" = "Notify for all other messages/rooms";
|
"notification_settings_notify_all_other" = "Notify for all other messages/rooms";
|
||||||
|
|
||||||
// gcm section
|
// gcm section
|
||||||
"settings_config_identity_server" = "Identity server: %@";
|
"settings_config_identity_server" = "Identity server: %@";
|
||||||
|
|
||||||
// Settings keys
|
// Settings keys
|
||||||
|
|
||||||
// call string
|
// call string
|
||||||
"call_connecting" = "Connecting…";
|
"call_connecting" = "Connecting…";
|
||||||
|
@ -2649,4 +2650,3 @@ Tap the + to start adding people.";
|
||||||
"ssl_unexpected_existing_expl" = "The certificate has changed from one that was trusted by your phone. This is HIGHLY UNUSUAL. It is recommended that you DO NOT ACCEPT this new certificate.";
|
"ssl_unexpected_existing_expl" = "The certificate has changed from one that was trusted by your phone. This is HIGHLY UNUSUAL. It is recommended that you DO NOT ACCEPT this new certificate.";
|
||||||
"ssl_expected_existing_expl" = "The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.";
|
"ssl_expected_existing_expl" = "The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.";
|
||||||
"ssl_only_accept" = "ONLY accept the certificate if the server administrator has published a fingerprint that matches the one above.";
|
"ssl_only_accept" = "ONLY accept the certificate if the server administrator has published a fingerprint that matches the one above.";
|
||||||
|
|
||||||
|
|
26
Riot/Categories/Codable.swift
Normal file
26
Riot/Categories/Codable.swift
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// Copyright 2021 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Encodable {
|
||||||
|
/// Convenience method to get the json string of an Encodable
|
||||||
|
var jsonString: String? {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
guard let jsonData = try? encoder.encode(self) else { return nil }
|
||||||
|
return String(data: jsonData, encoding: .utf8)
|
||||||
|
}
|
||||||
|
}
|
114
Riot/Categories/MXBugReportRestClient+Riot.swift
Normal file
114
Riot/Categories/MXBugReportRestClient+Riot.swift
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
//
|
||||||
|
// Copyright 2021 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MatrixSDK
|
||||||
|
import GBDeviceInfo
|
||||||
|
|
||||||
|
extension MXBugReportRestClient {
|
||||||
|
|
||||||
|
@objc static func vc_bugReportRestClient(appName: String) -> MXBugReportRestClient {
|
||||||
|
let client = MXBugReportRestClient(bugReportEndpoint: BuildSettings.bugReportEndpointUrlString)
|
||||||
|
// App info
|
||||||
|
client.appName = appName
|
||||||
|
client.version = AppDelegate.theDelegate().appVersion
|
||||||
|
client.build = AppDelegate.theDelegate().build
|
||||||
|
|
||||||
|
client.deviceModel = GBDeviceInfo.deviceInfo().modelString
|
||||||
|
client.deviceOS = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func vc_sendBugReport(
|
||||||
|
description: String,
|
||||||
|
sendLogs: Bool,
|
||||||
|
sendCrashLog: Bool,
|
||||||
|
sendFiles: [URL]? = nil,
|
||||||
|
additionalLabels: [String]? = nil,
|
||||||
|
customFields: [String: String]? = nil,
|
||||||
|
progress: ((MXBugReportState, Progress?) -> Void)? = nil,
|
||||||
|
success: ((String?) -> Void)? = nil,
|
||||||
|
failure: ((Error?) -> Void)? = nil
|
||||||
|
) {
|
||||||
|
// User info (TODO: handle multi-account and find a way to expose them in rageshake API)
|
||||||
|
var userInfo = [String: String]()
|
||||||
|
let mainAccount = MXKAccountManager.shared().accounts.first
|
||||||
|
if let userId = mainAccount?.mxSession.myUser.userId {
|
||||||
|
userInfo["user_id"] = userId
|
||||||
|
}
|
||||||
|
if let deviceId = mainAccount?.mxSession.matrixRestClient.credentials.deviceId {
|
||||||
|
userInfo["device_id"] = deviceId
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo["locale"] = NSLocale.preferredLanguages[0]
|
||||||
|
userInfo["default_app_language"] = Bundle.main.preferredLocalizations[0] // The language chosen by the OS
|
||||||
|
userInfo["app_language"] = Bundle.mxk_language() ?? userInfo["default_app_language"] // The language chosen by the user
|
||||||
|
|
||||||
|
// Application settings
|
||||||
|
userInfo["lazy_loading"] = MXKAppSettings.standard().syncWithLazyLoadOfRoomMembers ? "ON" : "OFF"
|
||||||
|
|
||||||
|
let currentDate = Date()
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||||
|
userInfo["local_time"] = dateFormatter.string(from: currentDate)
|
||||||
|
|
||||||
|
dateFormatter.timeZone = TimeZone(identifier: "UTC")
|
||||||
|
userInfo["utc_time"] = dateFormatter.string(from: currentDate)
|
||||||
|
|
||||||
|
if let customFields = customFields {
|
||||||
|
// combine userInfo with custom fields overriding with custom where there is a conflict
|
||||||
|
userInfo.merge(customFields) { (_, new) in new }
|
||||||
|
}
|
||||||
|
others = userInfo
|
||||||
|
|
||||||
|
var labels: [String] = additionalLabels ?? [String]()
|
||||||
|
// Add a Github label giving information about the version
|
||||||
|
if var versionLabel = version, let buildLabel = build {
|
||||||
|
|
||||||
|
// If this is not the app store version, be more accurate on the build origin
|
||||||
|
if buildLabel == VectorL10n.settingsConfigNoBuildInfo {
|
||||||
|
// This is a debug session from Xcode
|
||||||
|
versionLabel += "-debug"
|
||||||
|
} else if !buildLabel.contains("master") {
|
||||||
|
// This is a Jenkins build. Add the branch and the build number
|
||||||
|
let buildString = buildLabel.replacingOccurrences(of: " ", with: "-")
|
||||||
|
versionLabel += "-\(buildString)"
|
||||||
|
}
|
||||||
|
labels += [versionLabel]
|
||||||
|
}
|
||||||
|
if sendCrashLog {
|
||||||
|
labels += ["crash"]
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendDescription = description
|
||||||
|
if sendCrashLog,
|
||||||
|
let crashLogFile = MXLogger.crashLog(),
|
||||||
|
let crashLog = try? String(contentsOfFile: crashLogFile, encoding: .utf8) {
|
||||||
|
// Append the crash dump to the user description in order to ease triaging of GH issues
|
||||||
|
sendDescription += "\n\n\n--------------------------------------------------------------------------------\n\n\(crashLog)"
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBugReport(sendDescription,
|
||||||
|
sendLogs: sendLogs,
|
||||||
|
sendCrashLog: sendCrashLog,
|
||||||
|
sendFiles: sendFiles,
|
||||||
|
attachGitHubLabels: labels,
|
||||||
|
progress: progress,
|
||||||
|
success: success,
|
||||||
|
failure: failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
Riot/Categories/Publisher+Riot.swift
Normal file
37
Riot/Categories/Publisher+Riot.swift
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// Copyright 2021 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
extension Publisher {
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Buffer upstream items and guarantee a time interval spacing out the published items.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - spacingDelay: A delay in seconds to guarantee between emissions
|
||||||
|
/// - scheduler: The `DispatchQueue` on which to schedule emissions.
|
||||||
|
/// - Returns: The new wrapped publisher
|
||||||
|
func bufferAndSpace(spacingDelay: Int, scheduler: DispatchQueue = DispatchQueue.main) -> Publishers.FlatMap<
|
||||||
|
Publishers.SetFailureType<Publishers.Delay<Just<Publishers.Buffer<Self>.Output>, DispatchQueue>, Publishers.Buffer<Self>.Failure>,
|
||||||
|
Publishers.Buffer<Self>
|
||||||
|
> {
|
||||||
|
return buffer(size: .max, prefetch: .byRequest, whenFull: .dropNewest)
|
||||||
|
.flatMap(maxPublishers: .max(1)) {
|
||||||
|
Just($0).delay(for: .seconds(spacingDelay), scheduler: scheduler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6699,6 +6699,10 @@ public class VectorL10n: NSObject {
|
||||||
public static var settingsLabsE2eEncryptionPromptMessage: String {
|
public static var settingsLabsE2eEncryptionPromptMessage: String {
|
||||||
return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message")
|
return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message")
|
||||||
}
|
}
|
||||||
|
/// Auto Report Decryption Errors
|
||||||
|
public static var settingsLabsEnableAutoReportDecryptionErrors: String {
|
||||||
|
return VectorL10n.tr("Vector", "settings_labs_enable_auto_report_decryption_errors")
|
||||||
|
}
|
||||||
/// Ring for group calls
|
/// Ring for group calls
|
||||||
public static var settingsLabsEnableRingingForGroupCalls: String {
|
public static var settingsLabsEnableRingingForGroupCalls: String {
|
||||||
return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls")
|
return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls")
|
||||||
|
|
29
Riot/Managers/Settings/RiotSettings+Publisher.swift
Normal file
29
Riot/Managers/Settings/RiotSettings+Publisher.swift
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// Copyright 2021 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
extension RiotSettings {
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
func publisher(for key: String) -> AnyPublisher<Notification, Never> {
|
||||||
|
return NotificationCenter.default.publisher(for: .userDefaultValueUpdated)
|
||||||
|
.filter({ $0.object as? String == key })
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ final class RiotSettings: NSObject {
|
||||||
static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif"
|
static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif"
|
||||||
static let pinRoomsWithUnreadMessagesOnHome = "pinRoomsWithUnread"
|
static let pinRoomsWithUnreadMessagesOnHome = "pinRoomsWithUnread"
|
||||||
static let showAllRoomsInHomeSpace = "showAllRoomsInHomeSpace"
|
static let showAllRoomsInHomeSpace = "showAllRoomsInHomeSpace"
|
||||||
|
static let enableUISIAutoReporting = "enableUISIAutoReporting"
|
||||||
}
|
}
|
||||||
|
|
||||||
static let shared = RiotSettings()
|
static let shared = RiotSettings()
|
||||||
|
@ -146,6 +147,10 @@ final class RiotSettings: NSObject {
|
||||||
@UserDefault(key: "enableThreads", defaultValue: false, storage: defaults)
|
@UserDefault(key: "enableThreads", defaultValue: false, storage: defaults)
|
||||||
var enableThreads
|
var enableThreads
|
||||||
|
|
||||||
|
/// Indicates if auto reporting of decryption errors is enabled
|
||||||
|
@UserDefault(key: UserDefaultsKeys.enableUISIAutoReporting, defaultValue: BuildSettings.cryptoUISIAutoReportingEnabled, storage: defaults)
|
||||||
|
var enableUISIAutoReporting
|
||||||
|
|
||||||
// MARK: Calls
|
// MARK: Calls
|
||||||
|
|
||||||
/// Indicate if `allowStunServerFallback` settings has been set once.
|
/// Indicate if `allowStunServerFallback` settings has been set once.
|
||||||
|
|
236
Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift
Normal file
236
Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
//
|
||||||
|
// Copyright 2021 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MatrixSDK
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
struct UISIAutoReportData {
|
||||||
|
let eventId: String?
|
||||||
|
let roomId: String?
|
||||||
|
let senderKey: String?
|
||||||
|
let deviceId: String?
|
||||||
|
let userId: String?
|
||||||
|
let sessionId: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UISIAutoReportData: Codable {
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case eventId = "event_id"
|
||||||
|
case roomId = "room_id"
|
||||||
|
case senderKey = "sender_key"
|
||||||
|
case deviceId = "device_id"
|
||||||
|
case userId = "user_id"
|
||||||
|
case sessionId = "session_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Listens for failed decryption events and silently sends reports RageShake server.
|
||||||
|
/// Also requests that message senders send a matching report to have both sides of the interaction.
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
@objcMembers class UISIAutoReporter: NSObject, UISIDetectorDelegate {
|
||||||
|
|
||||||
|
struct ReportInfo: Hashable {
|
||||||
|
let roomId: String
|
||||||
|
let sessionId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private static let autoRsRequest = "im.vector.auto_rs_request"
|
||||||
|
private static let reportSpacing = 60
|
||||||
|
|
||||||
|
private let bugReporter: MXBugReportRestClient
|
||||||
|
private let dispatchQueue = DispatchQueue(label: "io.element.UISIAutoReporter.queue")
|
||||||
|
// Simple in memory cache of already sent report
|
||||||
|
private var alreadyReportedUisi = Set<ReportInfo>()
|
||||||
|
private let e2eDetectedSubject = PassthroughSubject<UISIDetectedMessage, Never>()
|
||||||
|
private let matchingRSRequestSubject = PassthroughSubject<MXEvent, Never>()
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
private var sessions = [MXSession]()
|
||||||
|
private var enabled = false {
|
||||||
|
didSet {
|
||||||
|
guard oldValue != enabled else { return }
|
||||||
|
detector.enabled = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Setup
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.bugReporter = MXBugReportRestClient.vc_bugReportRestClient(appName: BuildSettings.bugReportUISIId)
|
||||||
|
super.init()
|
||||||
|
// Simple rate limiting, for any rage-shakes emitted we guarantee a spacing between requests.
|
||||||
|
e2eDetectedSubject
|
||||||
|
.bufferAndSpace(spacingDelay: Self.reportSpacing)
|
||||||
|
.sink { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.sendRageShake(source: $0)
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
|
||||||
|
matchingRSRequestSubject
|
||||||
|
.bufferAndSpace(spacingDelay: Self.reportSpacing)
|
||||||
|
.sink { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.sendMatchingRageShake(source: $0)
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
|
||||||
|
self.enabled = RiotSettings.shared.enableUISIAutoReporting
|
||||||
|
RiotSettings.shared.publisher(for: RiotSettings.UserDefaultsKeys.enableUISIAutoReporting)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.enabled = RiotSettings.shared.enableUISIAutoReporting
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var detector: UISIDetector = {
|
||||||
|
let detector = UISIDetector()
|
||||||
|
detector.delegate = self
|
||||||
|
return detector
|
||||||
|
}()
|
||||||
|
|
||||||
|
var reciprocateToDeviceEventType: String {
|
||||||
|
return Self.autoRsRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
func uisiDetected(source: UISIDetectedMessage) {
|
||||||
|
dispatchQueue.async {
|
||||||
|
let reportInfo = ReportInfo(roomId: source.roomId, sessionId: source.sessionId)
|
||||||
|
let alreadySent = self.alreadyReportedUisi.contains(reportInfo)
|
||||||
|
if !alreadySent {
|
||||||
|
self.alreadyReportedUisi.insert(reportInfo)
|
||||||
|
self.e2eDetectedSubject.send(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(_ session: MXSession) {
|
||||||
|
sessions.append(session)
|
||||||
|
detector.enabled = enabled
|
||||||
|
session.eventStreamService.add(eventStreamListener: detector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(_ session: MXSession) {
|
||||||
|
if let index = sessions.firstIndex(of: session) {
|
||||||
|
sessions.remove(at: index)
|
||||||
|
}
|
||||||
|
session.eventStreamService.remove(eventStreamListener: detector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uisiReciprocateRequest(source: MXEvent) {
|
||||||
|
guard source.type == Self.autoRsRequest else { return }
|
||||||
|
self.matchingRSRequestSubject.send(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func sendRageShake(source: UISIDetectedMessage) {
|
||||||
|
MXLog.debug("[UISIAutoReporter] sendRageShake")
|
||||||
|
guard let session = sessions.first else { return }
|
||||||
|
let uisiData = UISIAutoReportData(
|
||||||
|
eventId: source.eventId,
|
||||||
|
roomId: source.roomId,
|
||||||
|
senderKey: source.senderKey,
|
||||||
|
deviceId: source.senderDeviceId,
|
||||||
|
userId: source.senderUserId,
|
||||||
|
sessionId: source.sessionId
|
||||||
|
).jsonString ?? ""
|
||||||
|
|
||||||
|
self.bugReporter.vc_sendBugReport(
|
||||||
|
description: "Auto-reporting decryption error",
|
||||||
|
sendLogs: true,
|
||||||
|
sendCrashLog: true,
|
||||||
|
additionalLabels: [
|
||||||
|
"Z-UISI",
|
||||||
|
"ios",
|
||||||
|
"uisi-recipient"
|
||||||
|
],
|
||||||
|
customFields: ["auto_uisi": uisiData],
|
||||||
|
success: { reportUrl in
|
||||||
|
let contentMap = MXUsersDevicesMap<NSDictionary>()
|
||||||
|
let content = [
|
||||||
|
"event_id": source.eventId,
|
||||||
|
"room_id": source.roomId,
|
||||||
|
"session_id": source.sessionId,
|
||||||
|
"device_id": source.senderDeviceId,
|
||||||
|
"user_id": source.senderUserId,
|
||||||
|
"sender_key": source.senderKey,
|
||||||
|
"recipient_rageshake": reportUrl
|
||||||
|
]
|
||||||
|
contentMap.setObject(content as NSDictionary, forUser: source.senderUserId, andDevice: source.senderDeviceId)
|
||||||
|
session.matrixRestClient.sendDirectToDevice(
|
||||||
|
eventType: Self.autoRsRequest,
|
||||||
|
contentMap: contentMap,
|
||||||
|
txnId: nil
|
||||||
|
) { response in
|
||||||
|
if response.isFailure {
|
||||||
|
MXLog.warning("failed to send auto-uisi to device")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
failure: { [weak self] error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.dispatchQueue.async {
|
||||||
|
self.alreadyReportedUisi.remove(ReportInfo(roomId: source.roomId, sessionId: source.sessionId))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendMatchingRageShake(source: MXEvent) {
|
||||||
|
MXLog.debug("[UISIAutoReporter] sendMatchingRageShake")
|
||||||
|
let eventId = source.content["event_id"] as? String
|
||||||
|
let roomId = source.content["room_id"] as? String
|
||||||
|
let sessionId = source.content["session_id"] as? String
|
||||||
|
let deviceId = source.content["device_id"] as? String
|
||||||
|
let userId = source.content["user_id"] as? String
|
||||||
|
let senderKey = source.content["sender_key"] as? String
|
||||||
|
let matchingIssue = source.content["recipient_rageshake"] as? String
|
||||||
|
|
||||||
|
var description = "Auto-reporting decryption error (sender)"
|
||||||
|
if let matchingIssue = matchingIssue {
|
||||||
|
description += "\nRecipient rageshake: \(matchingIssue)"
|
||||||
|
}
|
||||||
|
|
||||||
|
let uisiData = UISIAutoReportData(
|
||||||
|
eventId: eventId,
|
||||||
|
roomId: roomId,
|
||||||
|
senderKey: senderKey,
|
||||||
|
deviceId: deviceId,
|
||||||
|
userId: userId,
|
||||||
|
sessionId: sessionId
|
||||||
|
).jsonString ?? ""
|
||||||
|
|
||||||
|
self.bugReporter.vc_sendBugReport(
|
||||||
|
description: description,
|
||||||
|
sendLogs: true,
|
||||||
|
sendCrashLog: true,
|
||||||
|
additionalLabels: [
|
||||||
|
"Z-UISI",
|
||||||
|
"ios",
|
||||||
|
"uisi-sender"
|
||||||
|
],
|
||||||
|
customFields: [
|
||||||
|
"auto_uisi": uisiData,
|
||||||
|
"recipient_rageshake": matchingIssue ?? ""
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
115
Riot/Managers/UISIAutoReporter/UISIDetector.swift
Normal file
115
Riot/Managers/UISIAutoReporter/UISIDetector.swift
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
//
|
||||||
|
// Copyright 2021 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import MatrixSDK
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol UISIDetectorDelegate: AnyObject {
|
||||||
|
var reciprocateToDeviceEventType: String { get }
|
||||||
|
func uisiDetected(source: UISIDetectedMessage)
|
||||||
|
func uisiReciprocateRequest(source: MXEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UISIDetectedMessage {
|
||||||
|
let eventId: String
|
||||||
|
let roomId: String
|
||||||
|
let senderUserId: String
|
||||||
|
let senderDeviceId: String
|
||||||
|
let senderKey: String
|
||||||
|
let sessionId: String
|
||||||
|
|
||||||
|
static func fromEvent(event: MXEvent) -> UISIDetectedMessage {
|
||||||
|
return UISIDetectedMessage(
|
||||||
|
eventId: event.eventId ?? "",
|
||||||
|
roomId: event.roomId,
|
||||||
|
senderUserId: event.sender,
|
||||||
|
senderDeviceId: event.wireContent["device_id"] as? String ?? "",
|
||||||
|
senderKey: event.wireContent["sender_key"] as? String ?? "",
|
||||||
|
sessionId: event.wireContent["session_id"] as? String ?? ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detects decryption errors that occur and don't recover within a grace period.
|
||||||
|
/// see `UISIDetectorDelegate` for listening to detections.
|
||||||
|
class UISIDetector: MXLiveEventListener {
|
||||||
|
|
||||||
|
weak var delegate: UISIDetectorDelegate?
|
||||||
|
var enabled = false
|
||||||
|
|
||||||
|
var initialSyncCompleted = false
|
||||||
|
private var trackedUISIs = [String: DispatchSourceTimer]()
|
||||||
|
private let dispatchQueue = DispatchQueue(label: "io.element.UISIDetector.queue")
|
||||||
|
private static let gracePeriodSeconds = 30
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
func onSessionStateChanged(state: MXSessionState) {
|
||||||
|
dispatchQueue.async {
|
||||||
|
self.initialSyncCompleted = state == .running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onLiveEventDecryptionAttempted(event: MXEvent, result: MXEventDecryptionResult) {
|
||||||
|
guard enabled, let eventId = event.eventId, let roomId = event.roomId else { return }
|
||||||
|
dispatchQueue.async {
|
||||||
|
let trackedId = Self.trackedEventId(roomId: eventId, eventId: roomId)
|
||||||
|
|
||||||
|
if let timer = self.trackedUISIs[trackedId],
|
||||||
|
result.clearEvent != nil {
|
||||||
|
// successfully decrypted during grace period, cancel timer.
|
||||||
|
self.trackedUISIs[trackedId] = nil
|
||||||
|
timer.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard self.initialSyncCompleted,
|
||||||
|
result.clearEvent == nil
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
// track uisi and report it only if it is not decrypted before grade period ends
|
||||||
|
let timer = DispatchSource.makeTimerSource(queue: self.dispatchQueue)
|
||||||
|
timer.schedule(deadline: .now() + .seconds(Self.gracePeriodSeconds))
|
||||||
|
timer.setEventHandler { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.trackedUISIs[trackedId] = nil
|
||||||
|
MXLog.verbose("[UISIDetector] onLiveEventDecryptionAttempted: Timeout on \(eventId)")
|
||||||
|
self.triggerUISI(source: UISIDetectedMessage.fromEvent(event: event))
|
||||||
|
}
|
||||||
|
self.trackedUISIs[trackedId] = timer
|
||||||
|
timer.activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onLiveToDeviceEvent(event: MXEvent) {
|
||||||
|
guard enabled, event.type == delegate?.reciprocateToDeviceEventType else { return }
|
||||||
|
delegate?.uisiReciprocateRequest(source: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func triggerUISI(source: UISIDetectedMessage) {
|
||||||
|
guard enabled else { return }
|
||||||
|
MXLog.info("[UISIDetector] triggerUISI: Unable To Decrypt \(source)")
|
||||||
|
self.delegate?.uisiDetected(source: source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Static
|
||||||
|
|
||||||
|
private static func trackedEventId(roomId: String, eventId: String) -> String {
|
||||||
|
return "\(roomId)-\(eventId)"
|
||||||
|
}
|
||||||
|
}
|
|
@ -225,6 +225,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||||
@property (nonatomic, strong) PushNotificationStore *pushNotificationStore;
|
@property (nonatomic, strong) PushNotificationStore *pushNotificationStore;
|
||||||
@property (nonatomic, strong) LocalAuthenticationService *localAuthenticationService;
|
@property (nonatomic, strong) LocalAuthenticationService *localAuthenticationService;
|
||||||
@property (nonatomic, strong, readwrite) CallPresenter *callPresenter;
|
@property (nonatomic, strong, readwrite) CallPresenter *callPresenter;
|
||||||
|
@property (nonatomic, strong, readwrite) id uisiAutoReporter;
|
||||||
|
|
||||||
@property (nonatomic, strong) MajorUpdateManager *majorUpdateManager;
|
@property (nonatomic, strong) MajorUpdateManager *majorUpdateManager;
|
||||||
|
|
||||||
|
@ -471,6 +472,10 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||||
|
|
||||||
self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new];
|
self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new];
|
||||||
|
|
||||||
|
if (@available(iOS 14.0, *)) {
|
||||||
|
self.uisiAutoReporter = [[UISIAutoReporter alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
// Add matrix observers, and initialize matrix sessions if the app is not launched in background.
|
// Add matrix observers, and initialize matrix sessions if the app is not launched in background.
|
||||||
[self initMatrixSessions];
|
[self initMatrixSessions];
|
||||||
|
|
||||||
|
@ -2152,6 +2157,17 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||||
// register the session to the call service
|
// register the session to the call service
|
||||||
[_callPresenter addMatrixSession:mxSession];
|
[_callPresenter addMatrixSession:mxSession];
|
||||||
|
|
||||||
|
// register the session to the uisi auto-reporter
|
||||||
|
if (_uisiAutoReporter != nil)
|
||||||
|
{
|
||||||
|
if (@available(iOS 14.0, *))
|
||||||
|
{
|
||||||
|
UISIAutoReporter* uisiAutoReporter = (UISIAutoReporter*)_uisiAutoReporter;
|
||||||
|
[uisiAutoReporter add:mxSession];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[_callPresenter addMatrixSession:mxSession];
|
||||||
|
|
||||||
[mxSessionArray addObject:mxSession];
|
[mxSessionArray addObject:mxSession];
|
||||||
|
|
||||||
// Do the one time check on device id
|
// Do the one time check on device id
|
||||||
|
@ -2167,6 +2183,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||||
|
|
||||||
// remove session from the call service
|
// remove session from the call service
|
||||||
[_callPresenter removeMatrixSession:mxSession];
|
[_callPresenter removeMatrixSession:mxSession];
|
||||||
|
|
||||||
|
// register the session to the uisi auto-reporter
|
||||||
|
if (_uisiAutoReporter != nil)
|
||||||
|
{
|
||||||
|
if (@available(iOS 14.0, *))
|
||||||
|
{
|
||||||
|
UISIAutoReporter* uisiAutoReporter = (UISIAutoReporter*)_uisiAutoReporter;
|
||||||
|
[uisiAutoReporter remove:mxSession];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the widgets manager
|
// Update the widgets manager
|
||||||
[[WidgetManager sharedManager] removeMatrixSession:mxSession];
|
[[WidgetManager sharedManager] removeMatrixSession:mxSession];
|
||||||
|
|
|
@ -295,47 +295,8 @@
|
||||||
{
|
{
|
||||||
self.isSendingLogs = YES;
|
self.isSendingLogs = YES;
|
||||||
|
|
||||||
// Setup data to send
|
bugReportRestClient = [MXBugReportRestClient vc_bugReportRestClientWithAppName:BuildSettings.bugReportApplicationId];
|
||||||
bugReportRestClient = [[MXBugReportRestClient alloc] initWithBugReportEndpoint:BuildSettings.bugReportEndpointUrlString];
|
|
||||||
|
|
||||||
// App info
|
|
||||||
bugReportRestClient.appName = BuildSettings.bugReportApplicationId;
|
|
||||||
bugReportRestClient.version = [AppDelegate theDelegate].appVersion;
|
|
||||||
bugReportRestClient.build = [AppDelegate theDelegate].build;
|
|
||||||
|
|
||||||
// Device info
|
|
||||||
bugReportRestClient.deviceModel = [GBDeviceInfo deviceInfo].modelString;
|
|
||||||
bugReportRestClient.deviceOS = [NSString stringWithFormat:@"%@ %@", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion]];
|
|
||||||
|
|
||||||
// User info (TODO: handle multi-account and find a way to expose them in rageshake API)
|
|
||||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
|
||||||
MXKAccount *mainAccount = [MXKAccountManager sharedManager].accounts.firstObject;
|
|
||||||
if (mainAccount.mxSession.myUser.userId)
|
|
||||||
{
|
|
||||||
userInfo[@"user_id"] = mainAccount.mxSession.myUser.userId;
|
|
||||||
}
|
|
||||||
if (mainAccount.mxSession.matrixRestClient.credentials.deviceId)
|
|
||||||
{
|
|
||||||
userInfo[@"device_id"] = mainAccount.mxSession.matrixRestClient.credentials.deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfo[@"locale"] = [NSLocale preferredLanguages][0];
|
|
||||||
userInfo[@"default_app_language"] = [[NSBundle mainBundle] preferredLocalizations][0]; // The language chosen by the OS
|
|
||||||
userInfo[@"app_language"] = [NSBundle mxk_language] ? [NSBundle mxk_language] : userInfo[@"default_app_language"]; // The language chosen by the user
|
|
||||||
|
|
||||||
// Application settings
|
|
||||||
userInfo[@"lazy_loading"] = [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers ? @"ON" : @"OFF";
|
|
||||||
|
|
||||||
NSDate *currentDate = [NSDate date];
|
|
||||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
|
||||||
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
|
|
||||||
userInfo[@"local_time"] = [dateFormatter stringFromDate:currentDate];
|
|
||||||
|
|
||||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
|
||||||
userInfo[@"utc_time"] = [dateFormatter stringFromDate:currentDate];
|
|
||||||
|
|
||||||
bugReportRestClient.others = userInfo;
|
|
||||||
|
|
||||||
// Screenshot
|
// Screenshot
|
||||||
NSArray<NSURL*> *files;
|
NSArray<NSURL*> *files;
|
||||||
if (_screenshot && _sendScreenshot)
|
if (_screenshot && _sendScreenshot)
|
||||||
|
@ -347,56 +308,23 @@
|
||||||
|
|
||||||
files = @[screenShotFile];
|
files = @[screenShotFile];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare labels to attach to the GitHub issue
|
|
||||||
NSMutableArray<NSString*> *gitHubLabels = [NSMutableArray array];
|
|
||||||
if (_reportCrash)
|
|
||||||
{
|
|
||||||
// Label the GH issue as "crash"
|
|
||||||
[gitHubLabels addObject:@"crash"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a Github label giving information about the version
|
|
||||||
if (bugReportRestClient.version && bugReportRestClient.build)
|
|
||||||
{
|
|
||||||
NSString *build = bugReportRestClient.build;
|
|
||||||
NSString *versionLabel = bugReportRestClient.version;
|
|
||||||
|
|
||||||
// If this is not the app store version, be more accurate on the build origin
|
|
||||||
if ([build isEqualToString:[VectorL10n settingsConfigNoBuildInfo]])
|
|
||||||
{
|
|
||||||
// This is a debug session from Xcode
|
|
||||||
versionLabel = [versionLabel stringByAppendingString:@"-debug"];
|
|
||||||
}
|
|
||||||
else if (build && ![build containsString:@"master"])
|
|
||||||
{
|
|
||||||
// This is a Jenkins build. Add the branch and the build number
|
|
||||||
NSString *buildString = [build stringByReplacingOccurrencesOfString:@" " withString:@"-"];
|
|
||||||
versionLabel = [[versionLabel stringByAppendingString:@"-"] stringByAppendingString:buildString];
|
|
||||||
}
|
|
||||||
|
|
||||||
[gitHubLabels addObject:versionLabel];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSMutableString *bugReportDescription = [NSMutableString stringWithString:_bugReportDescriptionTextView.text];
|
NSMutableString *bugReportDescription = [NSMutableString stringWithString:_bugReportDescriptionTextView.text];
|
||||||
|
|
||||||
if (_reportCrash)
|
|
||||||
{
|
|
||||||
// Append the crash dump to the user description in order to ease triaging of GH issues
|
|
||||||
NSString *crashLogFile = [MXLogger crashLog];
|
|
||||||
NSString *crashLog = [NSString stringWithContentsOfFile:crashLogFile encoding:NSUTF8StringEncoding error:nil];
|
|
||||||
[bugReportDescription appendFormat:@"\n\n\n--------------------------------------------------------------------------------\n\n%@", crashLog];
|
|
||||||
}
|
|
||||||
|
|
||||||
// starting a background task to have a bit of extra time in case of user forgets about the report and sends the app to background
|
// starting a background task to have a bit of extra time in case of user forgets about the report and sends the app to background
|
||||||
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
||||||
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
|
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
|
||||||
operationBackgroundId = UIBackgroundTaskInvalid;
|
operationBackgroundId = UIBackgroundTaskInvalid;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// Submit
|
[bugReportRestClient vc_sendBugReportWithDescription:bugReportDescription
|
||||||
[bugReportRestClient sendBugReport:bugReportDescription sendLogs:_sendLogs sendCrashLog:_reportCrash sendFiles:files attachGitHubLabels:gitHubLabels progress:^(MXBugReportState state, NSProgress *progress) {
|
sendLogs:_sendLogs
|
||||||
|
sendCrashLog:_reportCrash
|
||||||
|
sendFiles:files
|
||||||
|
additionalLabels:nil
|
||||||
|
customFields:nil
|
||||||
|
progress:^(MXBugReportState state, NSProgress *progress) {
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case MXBugReportStateProgressZipping:
|
case MXBugReportStateProgressZipping:
|
||||||
|
@ -413,7 +341,7 @@
|
||||||
|
|
||||||
self.sendingProgress.progress = progress.fractionCompleted;
|
self.sendingProgress.progress = progress.fractionCompleted;
|
||||||
|
|
||||||
} success:^{
|
} success:^(NSString *reportUrl){
|
||||||
|
|
||||||
self->bugReportRestClient = nil;
|
self->bugReportRestClient = nil;
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ typedef NS_ENUM(NSUInteger, LABS_ENABLE)
|
||||||
LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0,
|
LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0,
|
||||||
LABS_ENABLE_THREADS_INDEX,
|
LABS_ENABLE_THREADS_INDEX,
|
||||||
LABS_ENABLE_MESSAGE_BUBBLES_INDEX,
|
LABS_ENABLE_MESSAGE_BUBBLES_INDEX,
|
||||||
|
LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS,
|
||||||
LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX
|
LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -573,6 +574,7 @@ TableViewSectionsDelegate>
|
||||||
[sectionLabs addRowWithTag:LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX];
|
[sectionLabs addRowWithTag:LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX];
|
||||||
[sectionLabs addRowWithTag:LABS_ENABLE_THREADS_INDEX];
|
[sectionLabs addRowWithTag:LABS_ENABLE_THREADS_INDEX];
|
||||||
[sectionLabs addRowWithTag:LABS_ENABLE_MESSAGE_BUBBLES_INDEX];
|
[sectionLabs addRowWithTag:LABS_ENABLE_MESSAGE_BUBBLES_INDEX];
|
||||||
|
[sectionLabs addRowWithTag:LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS];
|
||||||
[sectionLabs addRowWithTag:LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX];
|
[sectionLabs addRowWithTag:LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX];
|
||||||
sectionLabs.headerTitle = [VectorL10n settingsLabs];
|
sectionLabs.headerTitle = [VectorL10n settingsLabs];
|
||||||
if (sectionLabs.hasAnyRows)
|
if (sectionLabs.hasAnyRows)
|
||||||
|
@ -1486,6 +1488,21 @@ TableViewSectionsDelegate>
|
||||||
return labelAndSwitchCell;
|
return labelAndSwitchCell;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (UITableViewCell *)buildAutoReportDecryptionErrorsCellForTableView:(UITableView*)tableView
|
||||||
|
atIndexPath:(NSIndexPath*)indexPath
|
||||||
|
{
|
||||||
|
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
|
||||||
|
|
||||||
|
labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableAutoReportDecryptionErrors];
|
||||||
|
|
||||||
|
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableUISIAutoReporting;
|
||||||
|
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
|
||||||
|
labelAndSwitchCell.mxkSwitch.enabled = YES;
|
||||||
|
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableAutoReportDecryptionErrors:) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
|
||||||
|
return labelAndSwitchCell;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - 3Pid Add
|
#pragma mark - 3Pid Add
|
||||||
|
|
||||||
- (void)showAuthenticationIfNeededForAdding:(MX3PIDMedium)medium withSession:(MXSession*)session completion:(void (^)(NSDictionary* authParams))completion
|
- (void)showAuthenticationIfNeededForAdding:(MX3PIDMedium)medium withSession:(MXSession*)session completion:(void (^)(NSDictionary* authParams))completion
|
||||||
|
@ -2458,6 +2475,10 @@ TableViewSectionsDelegate>
|
||||||
{
|
{
|
||||||
cell = [self buildMessageBubblesCellForTableView:tableView atIndexPath:indexPath];
|
cell = [self buildMessageBubblesCellForTableView:tableView atIndexPath:indexPath];
|
||||||
}
|
}
|
||||||
|
else if (row == LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS)
|
||||||
|
{
|
||||||
|
cell = [self buildAutoReportDecryptionErrorsCellForTableView:tableView atIndexPath:indexPath];
|
||||||
|
}
|
||||||
else if (row == LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX)
|
else if (row == LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX)
|
||||||
{
|
{
|
||||||
MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
|
MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
|
||||||
|
@ -3903,6 +3924,12 @@ TableViewSectionsDelegate>
|
||||||
[roomDataSourceManager reset];
|
[roomDataSourceManager reset];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (void)toggleEnableAutoReportDecryptionErrors:(UISwitch *)sender
|
||||||
|
{
|
||||||
|
RiotSettings.shared.enableUISIAutoReporting = sender.isOn;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - TextField listener
|
#pragma mark - TextField listener
|
||||||
|
|
||||||
- (IBAction)textFieldDidChange:(id)sender
|
- (IBAction)textFieldDidChange:(id)sender
|
||||||
|
|
Loading…
Reference in a new issue