Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 8ce3815

Browse files
authored
Crash report cohort ID support for iOS (#3692)
Task/Issue URL: https://app.asana.com/0/1208592102886666/1208759541597499/f Tech Design URL: https://app.asana.com/0/1208592102886666/1208660326715650/f **Description**: DO NOT MERGE - this is a draft for input, not ready to go live yet. iOS client support for CRCID send/receive (primarily supported in BSK, with changes under review in [BSK #1116](duckduckgo/BrowserServicesKit#1116)). This is pretty straightforward, just conforming to CrashCollection’s new init signature, and clearing CRCIDs when the user opts out of crash reporting. BSK handles everything else. **Steps to test this PR**: Note: Must be tested on a physical device, as the simulator does not produce crash logs (and thus doesn’t find and upload them either). To cause and report a crash: 1. Launch the app and force a crash, which can be done from Settings → All Debug Options → Crash (fatal error) or similar. Note that Crash (CPU/Memory) does not appear to produce a crash log, and thus won’t trigger crash uploading. 2. Launch the app again (easiest with a debugger) 1. For the first crash of an app install: You will be prompted to opt in or out of crash reporting when the app is launched. Opt in and watch logs for “crcid” and you should see logs from CrashReportSender:56, and CrashCollection:95-109. 2. On subsequent crashes, when opted in, you should see statements confirming the received crcid was sent, and that the server returned either the same matching one, or a new one (in which case the new one should be stored and used on subsequent crash reports) To test clearing of the crcid when opting out: 1. Navigate to Settings → About and switch “Send Crash Reports” off, then back on again (this step should clear the crcid) 2. Follow steps from “To cause and report a crash” above, and confirm that the crash is submitted without an initial crcid, and that the server assigns one and it is stored (causing and uploading a second crash should confirm this new value is used on send).
1 parent df948c9 commit 8ce3815

File tree

10 files changed

+70
-6
lines changed

10 files changed

+70
-6
lines changed

Core/PixelEvent.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,9 @@ extension Pixel {
526526
case dbCrashDetectedDaily
527527
case crashOnCrashHandlersSetUp
528528

529+
case crashReportCRCIDMissing
530+
case crashReportingSubmissionFailed
531+
529532
case dbMigrationError
530533
case dbRemovalError
531534
case dbDestroyError
@@ -1456,6 +1459,8 @@ extension Pixel.Event {
14561459

14571460
case .dbCrashDetected: return "m_d_crash"
14581461
case .dbCrashDetectedDaily: return "m_d_crash_daily"
1462+
case .crashReportCRCIDMissing: return "m_crashreporting_crcid-missing"
1463+
case .crashReportingSubmissionFailed: return "m_crashreporting_submission-failed"
14591464
case .crashOnCrashHandlersSetUp: return "m_d_crash_on_handlers_setup"
14601465
case .dbMigrationError: return "m_d_dbme"
14611466
case .dbRemovalError: return "m_d_dbre"

DuckDuckGo.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@
225225
37FCAABC2992F592000E420A /* MultilineScrollableTextFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAABB2992F592000E420A /* MultilineScrollableTextFix.swift */; };
226226
37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAABF29930E26000E420A /* FailedAssertionView.swift */; };
227227
37FD780F2A29E28B00B36DB1 /* SyncErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD780E2A29E28B00B36DB1 /* SyncErrorHandler.swift */; };
228+
46DD3D5A2D0A29F600F33D49 /* CrashReportSenderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46DD3D592D0A29F400F33D49 /* CrashReportSenderExtensions.swift */; };
228229
4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0295182537BC6700E00CEF /* ConfigurationDebugViewController.swift */; };
229230
4B0F3F502B9BFF2100392892 /* NetworkProtectionFAQView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F3F4F2B9BFF2100392892 /* NetworkProtectionFAQView.swift */; };
230231
4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */; };
@@ -1597,6 +1598,7 @@
15971598
37FCAABF29930E26000E420A /* FailedAssertionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAssertionView.swift; sourceTree = "<group>"; };
15981599
37FCAACB2993149A000E420A /* Waitlist */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Waitlist; sourceTree = "<group>"; };
15991600
37FD780E2A29E28B00B36DB1 /* SyncErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorHandler.swift; sourceTree = "<group>"; };
1601+
46DD3D592D0A29F400F33D49 /* CrashReportSenderExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportSenderExtensions.swift; sourceTree = "<group>"; };
16001602
4B0295182537BC6700E00CEF /* ConfigurationDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationDebugViewController.swift; sourceTree = "<group>"; };
16011603
4B0F3F4F2B9BFF2100392892 /* NetworkProtectionFAQView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFAQView.swift; sourceTree = "<group>"; };
16021604
4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWidgetRefreshModel.swift; sourceTree = "<group>"; };
@@ -3888,6 +3890,7 @@
38883890
37CF915E2BB4735F00BADCAE /* Crashes */ = {
38893891
isa = PBXGroup;
38903892
children = (
3893+
46DD3D592D0A29F400F33D49 /* CrashReportSenderExtensions.swift */,
38913894
37CF915F2BB4737300BADCAE /* CrashCollectionOnboarding.swift */,
38923895
37CF91612BB474AA00BADCAE /* CrashCollectionOnboardingView.swift */,
38933896
37CF91632BB4A82A00BADCAE /* CrashCollectionOnboardingViewModel.swift */,
@@ -8235,6 +8238,7 @@
82358238
BDF8D0022C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift in Sources */,
82368239
9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */,
82378240
8590CB67268A2E520089F6BF /* RootDebugViewController.swift in Sources */,
8241+
46DD3D5A2D0A29F600F33D49 /* CrashReportSenderExtensions.swift in Sources */,
82388242
1DEAADEA2BA4539800E25A97 /* SettingsAppearanceView.swift in Sources */,
82398243
B623C1C22862CA9E0043013E /* DownloadSession.swift in Sources */,
82408244
317CA3432CFF82E100F88848 /* SettingsAIChatView.swift in Sources */,
@@ -11719,7 +11723,7 @@
1171911723
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
1172011724
requirement = {
1172111725
kind = exactVersion;
11722-
version = 221.3.0;
11726+
version = 222.1.0;
1172311727
};
1172411728
};
1172511729
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {

DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DuckDuckGo/AppDelegate.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ import os.log
8383
private var syncStateCancellable: AnyCancellable?
8484
private var isSyncInProgressCancellable: AnyCancellable?
8585

86-
private let crashCollection = CrashCollection(platform: .iOS)
86+
private let crashCollection = CrashCollection(crashReportSender: CrashReportSender(platform: .iOS,
87+
pixelEvents: CrashReportSender.pixelEvents),
88+
crashCollectionStorage: UserDefaults())
8789
private var crashReportUploaderOnboarding: CrashCollectionOnboarding?
8890

8991
private var autofillPixelReporter: AutofillPixelReporter?

DuckDuckGo/AppUserDefaults.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public class AppUserDefaults: AppSettings {
7676

7777
static let crashCollectionOptInStatus = "com.duckduckgo.ios.crashCollectionOptInStatus"
7878
static let crashCollectionShouldRevertOptedInStatusTrigger = "com.duckduckgo.ios.crashCollectionShouldRevertOptedInStatusTrigger"
79-
79+
8080
static let duckPlayerMode = "com.duckduckgo.ios.duckPlayerMode"
8181
static let duckPlayerAskModeOverlayHidden = "com.duckduckgo.ios.duckPlayerAskModeOverlayHidden"
8282
static let duckPlayerOpenInNewTab = "com.duckduckgo.ios.duckPlayerOpenInNewTab"

DuckDuckGo/CrashCollectionOnboarding.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ final class CrashCollectionOnboarding: NSObject {
5555
func presentOnboardingIfNeeded(for payloads: [Data], from viewController: UIViewController, sendReport: @escaping () -> Void) {
5656
let isCurrentlyPresenting = viewController.presentedViewController != nil
5757

58+
// Note: DO NOT TURN THIS ON until updated screens for the opt-in prompt and screen for reviewing the kinds of data
59+
// we collect are updated (project coming soon)
5860
if featureFlagger.isFeatureOn(.crashReportOptInStatusResetting) {
5961
if appSettings.crashCollectionOptInStatus == .optedIn &&
6062
appSettings.crashCollectionShouldRevertOptedInStatusTrigger < crashCollectionShouldRevertOptedInStatusTriggerTargetValue {
63+
6164
appSettings.crashCollectionOptInStatus = .undetermined
6265
appSettings.crashCollectionShouldRevertOptedInStatusTrigger = crashCollectionShouldRevertOptedInStatusTriggerTargetValue
6366
}

DuckDuckGo/CrashCollectionOnboardingViewModel.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import Foundation
2121
import SwiftUI
22+
import Crashes
2223

2324
final class CrashCollectionOnboardingViewModel: ObservableObject {
2425

@@ -106,6 +107,11 @@ final class CrashCollectionOnboardingViewModel: ObservableObject {
106107
}
107108
set {
108109
appSettings.crashCollectionOptInStatus = newValue
110+
if appSettings.crashCollectionOptInStatus == .optedOut {
111+
let crashCollection = CrashCollection.init(crashReportSender: CrashReportSender(platform: .iOS,
112+
pixelEvents: CrashReportSender.pixelEvents))
113+
crashCollection.clearCRCID()
114+
}
109115
}
110116
}
111117
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// CrashReportSenderExtensions.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2024 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Crashes
21+
import Common
22+
import Core
23+
24+
extension CrashReportSender {
25+
26+
static let pixelEvents: EventMapping<CrashReportSenderError> = .init { event, _, _, _ in
27+
switch event {
28+
case CrashReportSenderError.crcidMissing:
29+
Pixel.fire(pixel: .crashReportCRCIDMissing)
30+
31+
case CrashReportSenderError.submissionFailed(let error):
32+
if let error {
33+
Pixel.fire(pixel: .crashReportingSubmissionFailed,
34+
withAdditionalParameters: ["HTTPStatusCode": "\(error.statusCode)"])
35+
} else {
36+
Pixel.fire(pixel: .crashReportingSubmissionFailed)
37+
}
38+
}
39+
}
40+
}

DuckDuckGo/SettingsViewModel.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Common
2525
import Combine
2626
import SyncUI
2727
import DuckPlayer
28+
import Crashes
2829

2930
import Subscription
3031
import NetworkProtection
@@ -377,6 +378,10 @@ final class SettingsViewModel: ObservableObject {
377378
Binding<Bool>(
378379
get: { self.state.crashCollectionOptInStatus == .optedIn },
379380
set: {
381+
if self.appSettings.crashCollectionOptInStatus == .optedIn && $0 == false {
382+
let crashCollection = CrashCollection(crashReportSender: CrashReportSender(platform: .iOS, pixelEvents: CrashReportSender.pixelEvents))
383+
crashCollection.clearCRCID()
384+
}
380385
self.appSettings.crashCollectionOptInStatus = $0 ? .optedIn : .optedOut
381386
self.state.crashCollectionOptInStatus = $0 ? .optedIn : .optedOut
382387
}

DuckDuckGoTests/AppSettingsMock.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ class AppSettingsMock: AppSettings {
8282
var autoconsentEnabled = true
8383

8484
var crashCollectionOptInStatus: CrashCollectionOptInStatus = .undetermined
85-
8685
var crashCollectionShouldRevertOptedInStatusTrigger: Int = 0
8786

8887
var newTabPageSectionsEnabled: Bool = false

0 commit comments

Comments
 (0)