Skip to content

Commit 7b4f29a

Browse files
committed
added privacy consent form on first launch
1 parent 51ee72a commit 7b4f29a

9 files changed

Lines changed: 225 additions & 78 deletions

File tree

native/app/Source/Application.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ class Application {
8383
SentrySDK.start { options in
8484
options.dsn = Constants.SENTRY_ENDPOINT
8585
options.sampleRate = 0.1
86+
87+
// Only send crash reports if user gave consent
88+
options.beforeSend = { event in
89+
if (store.state.settings.doCollectCrashReports) {
90+
return event
91+
}
92+
return nil
93+
}
8694
}
8795
}
8896

native/app/Source/Settings/Settings.swift

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,60 +12,23 @@ import ServiceManagement
1212
import LaunchAtLogin
1313
import SwiftyUserDefaults
1414
import ReSwift
15+
import Sentry
1516

1617
enum IconMode: String, Codable {
17-
case dock = "dock"
18-
case statusBar = "statusBar"
19-
case both = "both"
18+
case dock = "dock"
19+
case statusBar = "statusBar"
20+
case both = "both"
2021
}
2122

2223
extension IconMode {
23-
static let allValues = [
24-
dock.rawValue,
25-
statusBar.rawValue,
26-
both.rawValue
27-
]
24+
static let allValues = [
25+
dock.rawValue,
26+
statusBar.rawValue,
27+
both.rawValue
28+
]
2829
}
2930

3031
class Settings: StoreSubscriber {
31-
typealias StoreSubscriberStateType = SettingsState
32-
private func setupStateListener () {
33-
Application.store.subscribe(self) { subscription in
34-
subscription.select { state in state.settings }
35-
}
36-
}
37-
38-
func newState(state: SettingsState) {
39-
if (state.iconMode != Settings.iconMode) {
40-
Settings.iconMode = state.iconMode
41-
}
42-
}
43-
44-
init() {
45-
self.setupStateListener()
46-
({
47-
Settings.iconMode = Application.store.state.settings.iconMode
48-
})()
49-
}
50-
51-
static var launchOnStartup: Bool {
52-
get {
53-
return LaunchAtLogin.isEnabled
54-
}
55-
set {
56-
LaunchAtLogin.isEnabled = newValue
57-
}
58-
}
59-
60-
var launchOnStartup: Bool {
61-
get {
62-
return LaunchAtLogin.isEnabled
63-
}
64-
set {
65-
LaunchAtLogin.isEnabled = newValue
66-
}
67-
}
68-
6932
static var iconMode: IconMode = .both {
7033
didSet {
7134
let showDockIcon = self.iconMode == .both || self.iconMode == .dock
@@ -74,5 +37,45 @@ class Settings: StoreSubscriber {
7437
UI.statusItem.item.isVisible = showStatusBarIcon
7538
}
7639
}
77-
40+
41+
init() {
42+
self.setupStateListener()
43+
({
44+
Settings.iconMode = Application.store.state.settings.iconMode
45+
})()
46+
}
47+
48+
typealias StoreSubscriberStateType = SettingsState
49+
private func setupStateListener () {
50+
Application.store.subscribe(self) { subscription in
51+
subscription.select { state in state.settings }
52+
}
53+
}
54+
55+
func newState(state: SettingsState) {
56+
if (state.iconMode != Settings.iconMode) {
57+
Settings.iconMode = state.iconMode
58+
}
59+
}
60+
61+
static var launchOnStartup: Bool {
62+
get {
63+
return LaunchAtLogin.isEnabled
64+
}
65+
set {
66+
LaunchAtLogin.isEnabled = newValue
67+
}
68+
}
69+
70+
var launchOnStartup: Bool {
71+
get {
72+
return LaunchAtLogin.isEnabled
73+
}
74+
set {
75+
LaunchAtLogin.isEnabled = newValue
76+
}
77+
}
78+
79+
80+
7881
}

native/app/Source/Settings/SettingsDataBus.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ class SettingsDataBus: DataBus {
4545

4646
return "Settings have been set"
4747
}
48+
49+
self.on(.GET, "/collect-crash-reports") { data, _ in
50+
return [ "doCollectCrashReports": self.state.doCollectCrashReports ]
51+
}
52+
53+
self.on(.POST, "/collect-crash-reports") { data, _ in
54+
let doCollectCrashReports = data["doCollectCrashReports"] as? Bool
55+
if doCollectCrashReports == nil {
56+
throw "Invalid 'doCollectCrashReports' parameter, must be a Boolean"
57+
}
58+
Application.dispatchAction(SettingsAction.setDoCollectCrashReports(doCollectCrashReports!))
59+
60+
return "Crash Report collection consent has been set"
61+
}
4862

4963
}
5064
}

native/app/Source/Settings/SettingsState.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ import ReSwift
1313

1414
struct SettingsState: State {
1515
var iconMode: IconMode = .both
16+
var doCollectCrashReports = false
1617
}
1718

1819
enum SettingsAction: Action {
1920
case setIconMode(IconMode)
21+
case setDoCollectCrashReports(Bool)
2022
}
2123

2224
func SettingsStateReducer(action: Action, state: SettingsState?) -> SettingsState {
2325
var state = state ?? SettingsState()
2426
switch action as? SettingsAction {
2527
case .setIconMode(let iconMode)?:
2628
state.iconMode = iconMode
29+
case .setDoCollectCrashReports(let doCollect)?:
30+
state.doCollectCrashReports = doCollect
2731
case .none:
2832
break
2933
}

ui/src/app/app.component.ts

Lines changed: 101 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import {
77
import { UtilitiesService } from './services/utilities.service'
88
import { UIService, UIDimensions, UIShownChangedEventCallback } from './services/ui.service'
99
import { FadeInOutAnimation, FromTopAnimation } from '@eqmac/components'
10-
import { MatDialog } from '@angular/material/dialog'
10+
import { MatDialog, MatDialogRef } from '@angular/material/dialog'
1111
import { TransitionService } from './services/transitions.service'
1212
import { AnalyticsService } from './services/analytics.service'
1313
import { ApplicationService } from './services/app.service'
1414
import { SettingsService, IconMode } from './sections/settings/settings.service'
1515
import { ConfirmDialogComponent } from './components/confirm-dialog/confirm-dialog.component'
16+
import { SemanticVersion } from './services/semantic-version.service'
17+
import { OptionsDialogComponent } from './components/options-dialog/options-dialog.component'
18+
import { Option, Options } from './components/options/options.component'
1619

1720
@Component({
1821
selector: 'app-root',
@@ -47,29 +50,107 @@ export class AppComponent implements OnInit, AfterContentInit {
4750
async ngOnInit () {
4851
await this.sync()
4952
await this.fixUIMode()
53+
await this.setupPrivacy()
54+
}
5055

51-
const uiSettings = await this.ui.getSettings()
52-
53-
if (typeof uiSettings.doCollectTelemetry !== 'boolean') {
54-
uiSettings.doCollectTelemetry = await this.dialog.open(ConfirmDialogComponent, {
55-
hasBackdrop: true,
56-
disableClose: true,
57-
data: {
58-
text: `Is it okay with you if eqMac will collect anonymous Telemetry analytics data like:
56+
async setupPrivacy () {
57+
const [ uiSettings, info ] = await Promise.all([
58+
this.ui.getSettings(),
59+
this.app.getInfo()
60+
])
5961

60-
• macOS Version
61-
• App and UI Version
62-
• Country (IP Addresses are anonymized)
62+
// Starting from v1.1.0 we need to show the Crash Reports consent as well
63+
if (new SemanticVersion(info.version).isGreaterThanOrEqualTo('1.1.0')) {
64+
let doCollectCrashReports = await this.settings.getDoCollectCrashReports()
65+
if (typeof uiSettings.privacyFormSeen !== 'boolean') {
66+
const doCollectTelemetryOption: Option = {
67+
type: 'checkbox',
68+
label: 'Send Anonymous Analytics data',
69+
tooltip: `
70+
eqMac would collect anonymous Telemetry analytics data like:
71+
72+
• macOS Version
73+
• App and UI Version
74+
• Country (IP Addresses are anonymized)
75+
76+
This helps us understand distribution of our users.
77+
`,
78+
value: uiSettings.doCollectTelemetry ?? false,
79+
toggled: doCollectTelemetry => {
80+
uiSettings.doCollectTelemetry = doCollectTelemetry
81+
this.ui.setSettings({ doCollectTelemetry })
82+
}
83+
}
6384

64-
This helps us understand distribution of eqMac's users.
65-
You can change this setting any time later in the Settings.`,
66-
cancelText: 'Don\'t collect',
67-
confirmText: 'It\'s okay'
85+
const doCollectCrashReportsOption: Option = {
86+
type: 'checkbox',
87+
label: 'Send Anonymous Crash reports',
88+
tooltip: `
89+
eqMac would send anonymized crash reports
90+
back to the developer in case eqMac crashes.
91+
This helps us understand improve eqMac
92+
and make it a more stable product.
93+
`,
94+
value: doCollectCrashReports,
95+
toggled: doCollect => {
96+
doCollectCrashReports = doCollect
97+
this.settings.setDoCollectCrashReports({
98+
doCollectCrashReports
99+
})
100+
}
68101
}
69-
}).afterClosed().toPromise()
70-
await this.ui.setSettings({
71-
doCollectTelemetry: uiSettings.doCollectTelemetry
72-
})
102+
const privacyDialog: MatDialogRef<OptionsDialogComponent> = this.dialog.open(OptionsDialogComponent, {
103+
hasBackdrop: true,
104+
disableClose: true,
105+
data: {
106+
options: [
107+
[ { type: 'label', label: 'Privacy' } ],
108+
[ {
109+
type: 'label', label: `eqMac respects it's user's privacy
110+
and is giving you a choice what data you wish to share with the developer.
111+
This data would help us improve and grow the product.`
112+
} ],
113+
[ doCollectTelemetryOption ],
114+
[ doCollectCrashReportsOption ],
115+
[
116+
{
117+
type: 'button',
118+
label: 'Save',
119+
action: () => privacyDialog.close()
120+
}
121+
]
122+
] as Options
123+
}
124+
})
125+
126+
await privacyDialog.afterClosed().toPromise()
127+
await this.ui.setSettings({
128+
privacyFormSeen: true
129+
})
130+
}
131+
} else {
132+
// Can only show Analytics consent form on < v1.1.0
133+
if (typeof uiSettings.doCollectTelemetry !== 'boolean') {
134+
uiSettings.doCollectTelemetry = await this.dialog.open(ConfirmDialogComponent, {
135+
hasBackdrop: true,
136+
disableClose: true,
137+
data: {
138+
text: `Is it okay with you if eqMac will collect anonymous Telemetry analytics data like:
139+
140+
• macOS Version
141+
• App and UI Version
142+
• Country (IP Addresses are anonymized)
143+
144+
This helps us understand distribution of eqMac's users.
145+
You can change this setting any time later in the Settings.`,
146+
cancelText: 'Don\'t collect',
147+
confirmText: 'It\'s okay'
148+
}
149+
}).afterClosed().toPromise()
150+
await this.ui.setSettings({
151+
doCollectTelemetry: uiSettings.doCollectTelemetry
152+
})
153+
}
73154
}
74155

75156
if (uiSettings.doCollectTelemetry) {

0 commit comments

Comments
 (0)