Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(182)

Side by Side Diff: packages/usage/lib/src/usage_impl.dart

Issue 2990843002: Removed fixed dependencies (Closed)
Patch Set: Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « packages/usage/example/ga.dart ('k') | packages/usage/lib/src/usage_impl_html.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library usage_impl;
6
5 import 'dart:async'; 7 import 'dart:async';
6 import 'dart:math' as math; 8 import 'dart:math' as math;
7 9
10 import 'uuid.dart';
8 import '../usage.dart'; 11 import '../usage.dart';
9 import '../uuid/uuid.dart'; 12
13 final int _MAX_EXCEPTION_LENGTH = 100;
10 14
11 String postEncode(Map<String, dynamic> map) { 15 String postEncode(Map<String, dynamic> map) {
12 // &foo=bar 16 // &foo=bar
13 return map.keys.map((key) { 17 return map.keys.map((key) {
14 String value = '${map[key]}'; 18 String value = '${map[key]}';
15 return "${key}=${Uri.encodeComponent(value)}"; 19 return "${key}=${Uri.encodeComponent(value)}";
16 }).join('&'); 20 }).join('&');
17 } 21 }
18 22
19 /** 23 /**
20 * A throttling algorithm. This models the throttling after a bucket with 24 * A throttling algorithim. This models the throttling after a bucket with
21 * water dripping into it at the rate of 1 drop per second. If the bucket has 25 * water dripping into it at the rate of 1 drop per second. If the bucket has
22 * water when an operation is requested, 1 drop of water is removed and the 26 * water when an operation is requested, 1 drop of water is removed and the
23 * operation is performed. If not the operation is skipped. This algorithm 27 * operation is performed. If not the operation is skipped. This algorithim
24 * lets operations be performed in bursts without throttling, but holds the 28 * lets operations be peformed in bursts without throttling, but holds the
25 * overall average rate of operations to 1 per second. 29 * overall average rate of operations to 1 per second.
26 */ 30 */
27 class ThrottlingBucket { 31 class ThrottlingBucket {
28 final int startingCount; 32 final int startingCount;
29 int drops; 33 int drops;
30 int _lastReplenish; 34 int _lastReplenish;
31 35
32 ThrottlingBucket(this.startingCount) { 36 ThrottlingBucket(this.startingCount) {
33 drops = startingCount; 37 drops = startingCount;
34 _lastReplenish = new DateTime.now().millisecondsSinceEpoch; 38 _lastReplenish = new DateTime.now().millisecondsSinceEpoch;
(...skipping 14 matching lines...) Expand all
49 int now = new DateTime.now().millisecondsSinceEpoch; 53 int now = new DateTime.now().millisecondsSinceEpoch;
50 54
51 if (_lastReplenish + 1000 >= now) { 55 if (_lastReplenish + 1000 >= now) {
52 int inc = (now - _lastReplenish) ~/ 1000; 56 int inc = (now - _lastReplenish) ~/ 1000;
53 drops = math.min(drops + inc, startingCount); 57 drops = math.min(drops + inc, startingCount);
54 _lastReplenish += (1000 * inc); 58 _lastReplenish += (1000 * inc);
55 } 59 }
56 } 60 }
57 } 61 }
58 62
59 class AnalyticsImpl implements Analytics { 63 abstract class AnalyticsImpl extends Analytics {
60 static const String _defaultAnalyticsUrl = 64 static const String _defaultAnalyticsUrl = 'https://www.google-analytics.com/c ollect';
61 'https://www.google-analytics.com/collect';
62 65
63 @override 66 /// Tracking ID / Property ID.
64 final String trackingId; 67 final String trackingId;
65 @override
66 final String applicationName;
67 @override
68 final String applicationVersion;
69 68
70 final PersistentProperties properties; 69 final PersistentProperties properties;
71 final PostHandler postHandler; 70 final PostHandler postHandler;
72 71
73 final ThrottlingBucket _bucket = new ThrottlingBucket(20); 72 final ThrottlingBucket _bucket = new ThrottlingBucket(20);
74 final Map<String, dynamic> _variableMap = {}; 73 final Map<String, dynamic> _variableMap = {};
75 74
76 final List<Future> _futures = []; 75 final List<Future> _futures = [];
77 76
78 @override
79 AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
80
81 String _url; 77 String _url;
82 78
83 StreamController<Map<String, dynamic>> _sendController = 79 AnalyticsImpl(
84 new StreamController.broadcast(sync: true); 80 this.trackingId,
85 81 this.properties,
86 AnalyticsImpl(this.trackingId, this.properties, this.postHandler, 82 this.postHandler, {
87 {this.applicationName, this.applicationVersion, String analyticsUrl}) { 83 String applicationName,
84 String applicationVersion,
85 String analyticsUrl
86 }) {
88 assert(trackingId != null); 87 assert(trackingId != null);
89 88
90 if (applicationName != null) setSessionValue('an', applicationName); 89 if (applicationName != null) setSessionValue('an', applicationName);
91 if (applicationVersion != null) setSessionValue('av', applicationVersion); 90 if (applicationVersion != null) setSessionValue('av', applicationVersion);
92 91
93 _url = analyticsUrl ?? _defaultAnalyticsUrl; 92 _url = analyticsUrl ?? _defaultAnalyticsUrl;
94 } 93 }
95 94
96 bool _firstRun; 95 bool get optIn => properties['optIn'] == true;
97 96
98 @override 97 set optIn(bool value) {
99 bool get firstRun { 98 properties['optIn'] = value;
100 if (_firstRun == null) {
101 _firstRun = properties['firstRun'] == null;
102
103 if (properties['firstRun'] != false) {
104 properties['firstRun'] = false;
105 }
106 }
107
108 return _firstRun;
109 } 99 }
110 100
111 @override 101 bool get hasSetOptIn => properties['optIn'] != null;
112 bool get enabled {
113 bool optIn = analyticsOpt == AnalyticsOpt.optIn;
114 return optIn
115 ? properties['enabled'] == true
116 : properties['enabled'] != false;
117 }
118 102
119 @override 103 Future sendScreenView(String viewName) {
120 set enabled(bool value) {
121 properties['enabled'] = value;
122 }
123
124 @override
125 Future sendScreenView(String viewName, {Map<String, String> parameters}) {
126 Map<String, dynamic> args = {'cd': viewName}; 104 Map<String, dynamic> args = {'cd': viewName};
127 if (parameters != null) {
128 args.addAll(parameters);
129 }
130 return _sendPayload('screenview', args); 105 return _sendPayload('screenview', args);
131 } 106 }
132 107
133 @override 108 Future sendEvent(String category, String action, {String label, int value}) {
134 Future sendEvent(String category, String action, 109 if (!optIn) return new Future.value();
135 {String label, int value, Map<String, String> parameters}) { 110
136 Map<String, dynamic> args = {'ec': category, 'ea': action}; 111 Map<String, dynamic> args = {'ec': category, 'ea': action};
137 if (label != null) args['el'] = label; 112 if (label != null) args['el'] = label;
138 if (value != null) args['ev'] = value; 113 if (value != null) args['ev'] = value;
139 if (parameters != null) {
140 args.addAll(parameters);
141 }
142 return _sendPayload('event', args); 114 return _sendPayload('event', args);
143 } 115 }
144 116
145 @override
146 Future sendSocial(String network, String action, String target) { 117 Future sendSocial(String network, String action, String target) {
118 if (!optIn) return new Future.value();
119
147 Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target}; 120 Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target};
148 return _sendPayload('social', args); 121 return _sendPayload('social', args);
149 } 122 }
150 123
151 @override 124 Future sendTiming(String variableName, int time, {String category,
152 Future sendTiming(String variableName, int time, 125 String label}) {
153 {String category, String label}) { 126 if (!optIn) return new Future.value();
127
154 Map<String, dynamic> args = {'utv': variableName, 'utt': time}; 128 Map<String, dynamic> args = {'utv': variableName, 'utt': time};
155 if (label != null) args['utl'] = label; 129 if (label != null) args['utl'] = label;
156 if (category != null) args['utc'] = category; 130 if (category != null) args['utc'] = category;
157 return _sendPayload('timing', args); 131 return _sendPayload('timing', args);
158 } 132 }
159 133
160 @override 134 AnalyticsTimer startTimer(String variableName, {String category, String label} ) {
161 AnalyticsTimer startTimer(String variableName, 135 return new AnalyticsTimer(this,
162 {String category, String label}) { 136 variableName, category: category, label: label);
163 return new AnalyticsTimer(this, variableName,
164 category: category, label: label);
165 } 137 }
166 138
167 @override
168 Future sendException(String description, {bool fatal}) { 139 Future sendException(String description, {bool fatal}) {
169 // We trim exceptions to a max length; google analytics will apply it's own 140 if (!optIn) return new Future.value();
170 // truncation, likely around 150 chars or so.
171 const int maxExceptionLength = 1000;
172 141
173 // In order to ensure that the client of this API is not sending any PII 142 // In order to ensure that the client of this API is not sending any PII
174 // data, we strip out any stack trace that may reference a path on the 143 // data, we strip out any stack trace that may reference a path on the
175 // user's drive (file:/...). 144 // user's drive (file:/...).
176 if (description.contains('file:/')) { 145 if (description.contains('file:/')) {
177 description = description.substring(0, description.indexOf('file:/')); 146 description = description.substring(0, description.indexOf('file:/'));
178 } 147 }
179 148
180 description = description.replaceAll('\n', '; '); 149 if (description != null && description.length > _MAX_EXCEPTION_LENGTH) {
181 150 description = description.substring(0, _MAX_EXCEPTION_LENGTH);
182 if (description.length > maxExceptionLength) {
183 description = description.substring(0, maxExceptionLength);
184 } 151 }
185 152
186 Map<String, dynamic> args = {'exd': description}; 153 Map<String, dynamic> args = {'exd': description};
187 if (fatal != null && fatal) args['exf'] = '1'; 154 if (fatal != null && fatal) args['exf'] = '1';
188 return _sendPayload('exception', args); 155 return _sendPayload('exception', args);
189 } 156 }
190 157
191 @override
192 dynamic getSessionValue(String param) => _variableMap[param];
193
194 @override
195 void setSessionValue(String param, dynamic value) { 158 void setSessionValue(String param, dynamic value) {
196 if (value == null) { 159 if (value == null) {
197 _variableMap.remove(param); 160 _variableMap.remove(param);
198 } else { 161 } else {
199 _variableMap[param] = value; 162 _variableMap[param] = value;
200 } 163 }
201 } 164 }
202 165
203 @override
204 Stream<Map<String, dynamic>> get onSend => _sendController.stream;
205
206 @override
207 Future waitForLastPing({Duration timeout}) { 166 Future waitForLastPing({Duration timeout}) {
208 Future f = Future.wait(_futures).catchError((e) => null); 167 Future f = Future.wait(_futures).catchError((e) => null);
209 168
210 if (timeout != null) { 169 if (timeout != null) {
211 f = f.timeout(timeout, onTimeout: () => null); 170 f = f.timeout(timeout, onTimeout: () => null);
212 } 171 }
213 172
214 return f; 173 return f;
215 } 174 }
216 175
217 @override 176 /**
218 void close() => postHandler.close(); 177 * Anonymous Client ID. The value of this field should be a random UUID v4.
178 */
179 String get _clientId => properties['clientId'];
219 180
220 @override 181 void _initClientId() {
221 String get clientId => properties['clientId'] ??= new Uuid().generateV4(); 182 if (_clientId == null) {
222 183 properties['clientId'] = new Uuid().generateV4();
223 /** 184 }
224 * Send raw data to analytics. Callers should generally use one of the typed
225 * methods (`sendScreenView`, `sendEvent`, ...).
226 *
227 * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
228 * 'transaction', 'item', 'social', 'exception', and 'timing'.
229 */
230 Future sendRaw(String hitType, Map<String, dynamic> args) {
231 return _sendPayload(hitType, args);
232 } 185 }
233 186
234 /** 187 // Valid values for [hitType] are: 'pageview', 'screenview', 'event',
235 * Valid values for [hitType] are: 'pageview', 'screenview', 'event', 188 // 'transaction', 'item', 'social', 'exception', and 'timing'.
236 * 'transaction', 'item', 'social', 'exception', and 'timing'.
237 */
238 Future _sendPayload(String hitType, Map<String, dynamic> args) { 189 Future _sendPayload(String hitType, Map<String, dynamic> args) {
239 if (!enabled) return new Future.value(); 190 if (_bucket.removeDrop()) {
191 _initClientId();
240 192
241 if (_bucket.removeDrop()) {
242 _variableMap.forEach((key, value) { 193 _variableMap.forEach((key, value) {
243 args[key] = value; 194 args[key] = value;
244 }); 195 });
245 196
246 args['v'] = '1'; // protocol version 197 args['v'] = '1'; // protocol version
247 args['tid'] = trackingId; 198 args['tid'] = trackingId;
248 args['cid'] = clientId; 199 args['cid'] = _clientId;
249 args['t'] = hitType; 200 args['t'] = hitType;
250 201
251 _sendController.add(args);
252
253 return _recordFuture(postHandler.sendPost(_url, args)); 202 return _recordFuture(postHandler.sendPost(_url, args));
254 } else { 203 } else {
255 return new Future.value(); 204 return new Future.value();
256 } 205 }
257 } 206 }
258 207
259 Future _recordFuture(Future f) { 208 Future _recordFuture(Future f) {
260 _futures.add(f); 209 _futures.add(f);
261 return f.whenComplete(() => _futures.remove(f)); 210 return f.whenComplete(() => _futures.remove(f));
262 } 211 }
263 } 212 }
264 213
265 /** 214 /**
266 * A persistent key/value store. An [AnalyticsImpl] instance expects to have one 215 * A persistent key/value store. An [AnalyticsImpl] instance expects to have one
267 * of these injected into it. There are default implementations for `dart:io` 216 * of these injected into it. There are default implementations for `dart:io`
268 * and `dart:html` clients. 217 * and `dart:html` clients.
269 * 218 *
270 * The [name] parameter is used to uniquely store these properties on disk / 219 * The [name] paramater is used to uniquely store these properties on disk /
271 * persistent storage. 220 * persistent storage.
272 */ 221 */
273 abstract class PersistentProperties { 222 abstract class PersistentProperties {
274 final String name; 223 final String name;
275 224
276 PersistentProperties(this.name); 225 PersistentProperties(this.name);
277 226
278 dynamic operator [](String key); 227 dynamic operator[](String key);
279 void operator []=(String key, dynamic value); 228 void operator[]=(String key, dynamic value);
280
281 /// Re-read settings from the backing store. This may be a no-op on some
282 /// platforms.
283 void syncSettings();
284 } 229 }
285 230
286 /** 231 /**
287 * A utility class to perform HTTP POSTs. An [AnalyticsImpl] instance expects to 232 * A utility class to perform HTTP POSTs. An [AnalyticsImpl] instance expects to
288 * have one of these injected into it. There are default implementations for 233 * have one of these injected into it. There are default implementations for
289 * `dart:io` and `dart:html` clients. 234 * `dart:io` and `dart:html` clients.
290 * 235 *
291 * The POST information should be sent on a best-effort basis. The `Future` from 236 * The POST information should be sent on a best-effort basis. The `Future` from
292 * [sendPost] should complete when the operation is finished, but failures to 237 * [sendPost] should complete when the operation is finished, but failures to
293 * send the information should be silent. 238 * send the information should be silent.
294 */ 239 */
295 abstract class PostHandler { 240 abstract class PostHandler {
296 Future sendPost(String url, Map<String, dynamic> parameters); 241 Future sendPost(String url, Map<String, dynamic> parameters);
297
298 /// Free any used resources.
299 void close();
300 } 242 }
OLDNEW
« no previous file with comments | « packages/usage/example/ga.dart ('k') | packages/usage/lib/src/usage_impl_html.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698