Skip to content

Commit 545d7e1

Browse files
authored
Honor legacy opt out status (#80)
* Function added to check dart and flutter legacy analytics files * Sort members in utils.dart * Logic updated to change config string if legacy optout * Update tests for both dart and flutter legacy analytics * Doc clean up * On error, assume user has opted out of legacy analytics * Update CHANGELOG.md * Alter consent message based on tool using package * Clean up try blocks * Update try blocks for add'l exception + ignore lint for http client * Change home directory location for windows * Update documentation on http client
1 parent 2308c67 commit 545d7e1

File tree

8 files changed

+384
-7
lines changed

8 files changed

+384
-7
lines changed

pkgs/unified_analytics/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 1.1.0
22

33
- Added a `okToSend` getter so that clients can easily and accurately check the state of the consent mechanism.
4+
- Initialize the config file with user opted out if user was opted out in legacy Flutter and Dart analytics
45

56
## 1.0.1
67

pkgs/unified_analytics/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ include: package:lints/recommended.yaml
1818
linter:
1919
rules:
2020
- always_declare_return_types
21+
- avoid_catches_without_on_clauses
2122
- camel_case_types
2223
- prefer_single_quotes
2324
- unawaited_futures

pkgs/unified_analytics/lib/src/analytics.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,15 @@ class AnalyticsImpl implements Analytics {
376376
}
377377

378378
@override
379-
String get getConsentMessage =>
380-
kToolsMessage.replaceAll('[tool name]', tool.description);
379+
String get getConsentMessage {
380+
// The command to swap in the consent message
381+
final String commandString =
382+
tool == DashTool.flutterTool ? 'flutter' : 'dart';
383+
384+
return kToolsMessage
385+
.replaceAll('[tool name]', tool.description)
386+
.replaceAll('[dart|flutter]', commandString);
387+
}
381388

382389
/// Checking the [telemetryEnabled] boolean reflects what the
383390
/// config file reflects

pkgs/unified_analytics/lib/src/ga_client.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,28 @@ class GAClient {
4747

4848
/// Receive the payload in Map form and parse
4949
/// into JSON to send to GA
50+
///
51+
/// The [Response] returned from this method can be
52+
/// checked to ensure that events have been sent. A response
53+
/// status code of `2xx` indicates a successful send event.
54+
/// A response status code of `500` indicates an error occured on the send
55+
/// can the error message can be found in the [Response.body]
5056
Future<http.Response> sendData(Map<String, Object?> body) async {
57+
final Uri uri = Uri.parse(postUrl);
58+
59+
/// Using a try catch all since post method can result in several
60+
/// errors; clients using this method can check the awaited status
61+
/// code to get a specific error message if the status code returned
62+
/// is a 500 error status code
5163
try {
5264
return await _client.post(
53-
Uri.parse(postUrl),
65+
uri,
5466
headers: <String, String>{
5567
'Content-Type': 'application/json; charset=UTF-8',
5668
},
5769
body: jsonEncode(body),
5870
);
71+
// ignore: avoid_catches_without_on_clauses
5972
} catch (error) {
6073
return Future<http.Response>.value(http.Response(error.toString(), 500));
6174
}

pkgs/unified_analytics/lib/src/initializer.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,16 @@ class Initializer {
6060
required int toolsMessageVersion,
6161
}) {
6262
configFile.createSync(recursive: true);
63-
configFile.writeAsStringSync(kConfigString);
63+
64+
// If the user was previously opted out, then we will
65+
// replace the line that assumes automatic opt in with
66+
// an opt out from the start
67+
if (legacyOptOut(fs: fs, home: homeDirectory)) {
68+
configFile.writeAsStringSync(
69+
kConfigString.replaceAll('reporting=1', 'reporting=0'));
70+
} else {
71+
configFile.writeAsStringSync(kConfigString);
72+
}
6473
}
6574

6675
/// Creates that log file that will store the record formatted

pkgs/unified_analytics/lib/src/utils.dart

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:convert';
56
import 'dart:io' as io;
67
import 'dart:math' show Random;
78

89
import 'package:file/file.dart';
10+
import 'package:path/path.dart' as p;
911

1012
import 'enums.dart';
1113
import 'user_property.dart';
@@ -75,12 +77,88 @@ Directory getHomeDirectory(FileSystem fs) {
7577
} else if (io.Platform.isLinux) {
7678
home = envVars['HOME'];
7779
} else if (io.Platform.isWindows) {
78-
home = envVars['UserProfile'];
80+
home = envVars['AppData'];
7981
}
8082

8183
return fs.directory(home!);
8284
}
8385

86+
/// Returns `true` if user has opted out of legacy analytics in Dart or Flutter
87+
///
88+
/// Checks legacy opt-out status for the Flutter
89+
/// and Dart in the following locations
90+
///
91+
/// Dart: `$HOME/.dart/dartdev.json`
92+
///
93+
/// Flutter: `$HOME/.flutter`
94+
bool legacyOptOut({
95+
required FileSystem fs,
96+
required Directory home,
97+
}) {
98+
final File dartLegacyConfigFile =
99+
fs.file(p.join(home.path, '.dart', 'dartdev.json'));
100+
final File flutterLegacyConfigFile = fs.file(p.join(home.path, '.flutter'));
101+
102+
// Example of what the file looks like for dart
103+
//
104+
// {
105+
// "firstRun": false,
106+
// "enabled": false, <-- THIS USER HAS OPTED OUT
107+
// "disclosureShown": true,
108+
// "clientId": "52710e60-7c70-4335-b3a4-9d922630f12a"
109+
// }
110+
if (dartLegacyConfigFile.existsSync()) {
111+
try {
112+
// Read in the json object into a Map and check for
113+
// the enabled key being set to false; this means the user
114+
// has opted out of analytics for dart
115+
final Map<String, Object?> dartObj =
116+
jsonDecode(dartLegacyConfigFile.readAsStringSync());
117+
if (dartObj.containsKey('enabled') && dartObj['enabled'] == false) {
118+
return true;
119+
}
120+
} on FormatException {
121+
// In the case of an error when parsing the json file, return true
122+
// which will result in the user being opted out of unified_analytics
123+
//
124+
// A corrupted file could mean they opted out previously but for some
125+
// reason, the file was written incorrectly
126+
return true;
127+
} on FileSystemException {
128+
return true;
129+
}
130+
}
131+
132+
// Example of what the file looks like for flutter
133+
//
134+
// {
135+
// "firstRun": false,
136+
// "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
137+
// "enabled": false <-- THIS USER HAS OPTED OUT
138+
// }
139+
if (flutterLegacyConfigFile.existsSync()) {
140+
try {
141+
// Same process as above for dart
142+
final Map<String, Object?> flutterObj =
143+
jsonDecode(dartLegacyConfigFile.readAsStringSync());
144+
if (flutterObj.containsKey('enabled') && flutterObj['enabled'] == false) {
145+
return true;
146+
}
147+
} on FormatException {
148+
// In the case of an error when parsing the json file, return true
149+
// which will result in the user being opted out of unified_analytics
150+
//
151+
// A corrupted file could mean they opted out previously but for some
152+
// reason, the file was written incorrectly
153+
return true;
154+
} on FileSystemException {
155+
return true;
156+
}
157+
}
158+
159+
return false;
160+
}
161+
84162
/// A UUID generator.
85163
///
86164
/// This will generate unique IDs in the format:

0 commit comments

Comments
 (0)