Dubhe
Committed by GitHub

Merge pull request #2 from epoll-j/feat/request-listener

Feat/request listener
1 -## 0.0.1 1 +## 0.0.7
2 2
3 -* TODO: Describe initial release. 3 +* remove dio dependency
  4 +* add http request listener
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 2
3 > Flutter全埋点插件,支持 Android 和 iOS 3 > Flutter全埋点插件,支持 Android 和 iOS
4 4
5 -低侵入全局自动埋点,自动记录页面进入、退出,点击、滑动等事件,并支持自定义事件。 5 +低侵入全局自动埋点,自动记录页面进入、退出,点击、滑动、http请求等事件,并支持自定义事件。
6 6
7 7
8 ## Getting Started 使用指南 8 ## Getting Started 使用指南
@@ -69,6 +69,7 @@ class _MyAppState extends State<MyApp> { @@ -69,6 +69,7 @@ class _MyAppState extends State<MyApp> {
69 .enableClick() // 启用点击统计 69 .enableClick() // 启用点击统计
70 .enableDrag() // 启用滑动统计 70 .enableDrag() // 启用滑动统计
71 .enableIgnoreNullKey() // 忽略空的key,如果不忽略,没有配置key的页面或事件会基于一定的规则生成一个随机的key进行上报统计 71 .enableIgnoreNullKey() // 忽略空的key,如果不忽略,没有配置key的页面或事件会基于一定的规则生成一个随机的key进行上报统计
  72 + .enableHttpRequest() // 启用http请求监听
72 .enableLog(); // 启用日志,建议在debug模式下开启,会打印一些埋点相关的日志 73 .enableLog(); // 启用日志,建议在debug模式下开启,会打印一些埋点相关的日志
73 74
74 super.initState(); 75 super.initState();
@@ -2,7 +2,7 @@ library autotrack; @@ -2,7 +2,7 @@ library autotrack;
2 2
3 export './auto_track/index.dart'; 3 export './auto_track/index.dart';
4 export './auto_track/config/config.dart'; 4 export './auto_track/config/config.dart';
5 -export './auto_track/page_view/navigation_observer.dart';  
6 -export './auto_track/click/navigator_key.dart';  
7 -export './auto_track/click/element_key.dart'; 5 +export './auto_track/listener/page_view/navigation_observer.dart';
  6 +export './auto_track/listener/click/navigator_key.dart';
  7 +export './auto_track/listener/click/element_key.dart';
8 export './auto_track/log/logger.dart'; 8 export './auto_track/log/logger.dart';
@@ -29,9 +29,12 @@ class AutoTrackConfig { @@ -29,9 +29,12 @@ class AutoTrackConfig {
29 this.enableClick = true, // 监听点击事件 29 this.enableClick = true, // 监听点击事件
30 this.enableDrag = false, // 监听拖拽事件 30 this.enableDrag = false, // 监听拖拽事件
31 this.enableIgnoreNullKey = false, // 忽略空key事件 31 this.enableIgnoreNullKey = false, // 忽略空key事件
  32 + this.httpRequestConfig,
32 }) { 33 }) {
33 trackId ??= const Uuid().v4().replaceAll('-', ''); 34 trackId ??= const Uuid().v4().replaceAll('-', '');
34 - signature ??= (t) => sha256.convert(utf8.encode('$appKey$t$appSecret')).toString(); 35 + signature ??=
  36 + (t) => sha256.convert(utf8.encode('$appKey$t$appSecret')).toString();
  37 + httpRequestConfig ??= HttpRequestConfig();
35 } 38 }
36 39
37 String? host; 40 String? host;
@@ -76,18 +79,68 @@ class AutoTrackConfig { @@ -76,18 +79,68 @@ class AutoTrackConfig {
76 bool enableDrag; 79 bool enableDrag;
77 80
78 bool enableIgnoreNullKey; 81 bool enableIgnoreNullKey;
  82 +
  83 + HttpRequestConfig? httpRequestConfig;
  84 +
  85 + copyWith({
  86 + String? host,
  87 + String? appKey,
  88 + String? appSecret,
  89 + String? trackId,
  90 + String? userId,
  91 + String? uniqueId,
  92 + double? samplingRate,
  93 + int? uploadInterval,
  94 + Function? signature,
  95 + EventHandlerFunc? eventHandler,
  96 + List<AutoTrackPageConfig>? pageConfigs,
  97 + bool? useCustomRoute,
  98 + List<Key>? ignoreElementKeys,
  99 + List<String>? ignoreElementStringKeys,
  100 + bool? enablePageView,
  101 + bool? enablePageLeave,
  102 + bool? enableClick,
  103 + bool? enableUpload,
  104 + bool? enableDrag,
  105 + bool? enableIgnoreNullKey,
  106 + HttpRequestConfig? httpRequestConfig
  107 + }) {
  108 + return AutoTrackConfig(
  109 + host: host ?? this.host,
  110 + appKey: appKey ?? this.appKey,
  111 + appSecret: appSecret ?? this.appSecret,
  112 + trackId: trackId ?? this.trackId,
  113 + userId: userId ?? this.userId,
  114 + uniqueId: uniqueId ?? this.uniqueId,
  115 + samplingRate: samplingRate ?? this.samplingRate,
  116 + uploadInterval: uploadInterval ?? this.uploadInterval,
  117 + signature: signature ?? this.signature,
  118 + eventHandler: eventHandler ?? this.eventHandler,
  119 + pageConfigs: pageConfigs ?? this.pageConfigs,
  120 + useCustomRoute: useCustomRoute ?? this.useCustomRoute,
  121 + ignoreElementKeys: ignoreElementKeys ?? this.ignoreElementKeys,
  122 + ignoreElementStringKeys:
  123 + ignoreElementStringKeys ?? this.ignoreElementStringKeys,
  124 + enablePageView: enablePageView ?? this.enablePageView,
  125 + enablePageLeave: enablePageLeave ?? this.enablePageLeave,
  126 + enableClick: enableClick ?? this.enableClick,
  127 + enableUpload: enableUpload ?? this.enableUpload,
  128 + enableDrag: enableDrag ?? this.enableDrag,
  129 + enableIgnoreNullKey: enableIgnoreNullKey ?? this.enableIgnoreNullKey,
  130 + httpRequestConfig: httpRequestConfig ?? this.httpRequestConfig
  131 + );
  132 + }
79 } 133 }
80 134
81 typedef PageWidgetFunc = bool Function(Widget); 135 typedef PageWidgetFunc = bool Function(Widget);
82 136
83 class AutoTrackPageConfig<T extends Widget> { 137 class AutoTrackPageConfig<T extends Widget> {
84 - AutoTrackPageConfig({  
85 - this.pageID,  
86 - this.pagePath,  
87 - this.ignore = false,  
88 - this.pageTitle,  
89 - this.isPageWidget  
90 - }) { 138 + AutoTrackPageConfig(
  139 + {this.pageID,
  140 + this.pagePath,
  141 + this.ignore = false,
  142 + this.pageTitle,
  143 + this.isPageWidget}) {
91 isPageWidget ??= (pageWidget) => pageWidget is T; 144 isPageWidget ??= (pageWidget) => pageWidget is T;
92 } 145 }
93 146
@@ -97,3 +150,13 @@ class AutoTrackPageConfig<T extends Widget> { @@ -97,3 +150,13 @@ class AutoTrackPageConfig<T extends Widget> {
97 String? pageTitle; 150 String? pageTitle;
98 PageWidgetFunc? isPageWidget; 151 PageWidgetFunc? isPageWidget;
99 } 152 }
  153 +
  154 +class HttpRequestConfig {
  155 + bool ignoreRequestHeader;
  156 + bool ignoreResponseHeader;
  157 +
  158 + HttpRequestConfig({
  159 + this.ignoreRequestHeader = false,
  160 + this.ignoreResponseHeader = false,
  161 + });
  162 +}
@@ -8,6 +8,8 @@ import 'package:package_info_plus/package_info_plus.dart'; @@ -8,6 +8,8 @@ import 'package:package_info_plus/package_info_plus.dart';
8 8
9 import 'config.dart'; 9 import 'config.dart';
10 10
  11 +typedef UpdateConfigFunc = AutoTrackConfig Function(AutoTrackConfig);
  12 +
11 class AutoTrackConfigManager { 13 class AutoTrackConfigManager {
12 static final AutoTrackConfigManager instance = AutoTrackConfigManager._(); 14 static final AutoTrackConfigManager instance = AutoTrackConfigManager._();
13 15
@@ -36,10 +38,16 @@ class AutoTrackConfigManager { @@ -36,10 +38,16 @@ class AutoTrackConfigManager {
36 bool _autoTrackEnable = false; 38 bool _autoTrackEnable = false;
37 bool get autoTrackEnable => _autoTrackEnable; 39 bool get autoTrackEnable => _autoTrackEnable;
38 40
39 - void updateConfig(AutoTrackConfig config) {  
40 - _config = config; 41 + void setConfig(AutoTrackConfig config) {
  42 + updateConfig((old) {
  43 + return config;
  44 + });
41 _updateDeviceId(); 45 _updateDeviceId();
42 - if (config.enableUpload) { 46 + }
  47 +
  48 + void updateConfig(UpdateConfigFunc func) {
  49 + _config = func(_config);
  50 + if (_config.enableUpload) {
43 AutoTrackQueue.instance.start(); 51 AutoTrackQueue.instance.start();
44 } else { 52 } else {
45 AutoTrackQueue.instance.stop(); 53 AutoTrackQueue.instance.stop();
@@ -58,59 +66,10 @@ class AutoTrackConfigManager { @@ -58,59 +66,10 @@ class AutoTrackConfigManager {
58 } 66 }
59 } 67 }
60 68
61 - void updateUserId(String userId) {  
62 - _config.userId = userId;  
63 - }  
64 -  
65 - void updateSampleRate(double rate) {  
66 - _config.samplingRate = rate;  
67 - }  
68 -  
69 - void updatePageConfigs(List<AutoTrackPageConfig> pageConfigs) {  
70 - _config.pageConfigs = pageConfigs;  
71 - }  
72 -  
73 - void updateIgnoreElementKeys(List<Key> ignoreElementKeys) {  
74 - _config.ignoreElementKeys = ignoreElementKeys;  
75 - }  
76 -  
77 - void updateIgnoreElementStringKeys(List<String> ignoreElementStringKeys) {  
78 - _config.ignoreElementStringKeys = ignoreElementStringKeys;  
79 - }  
80 -  
81 - void enablePageView(bool enable) {  
82 - _config.enablePageView = enable;  
83 - }  
84 -  
85 - void enablePageLeave(bool enable) {  
86 - _config.enablePageLeave = enable;  
87 - }  
88 -  
89 - void enableClick(bool enable) {  
90 - _config.enableClick = enable;  
91 - }  
92 -  
93 - void enableDrag(bool enable) {  
94 - _config.enableDrag = enable;  
95 - }  
96 -  
97 void enableAutoTrack(bool enable) { 69 void enableAutoTrack(bool enable) {
98 _autoTrackEnable = enable; 70 _autoTrackEnable = enable;
99 } 71 }
100 72
101 - void enableUpload(bool enable) {  
102 - _config.enableUpload = enable;  
103 - if (enable) {  
104 - AutoTrackQueue.instance.start();  
105 - } else {  
106 - AutoTrackQueue.instance.stop();  
107 - }  
108 - }  
109 -  
110 - void enableIgnoreNullKey(bool enable) {  
111 - _config.enableIgnoreNullKey = enable;  
112 - }  
113 -  
114 List<AutoTrackPageConfig> get pageConfigs => _config.pageConfigs; 73 List<AutoTrackPageConfig> get pageConfigs => _config.pageConfigs;
115 74
116 bool get useCustomRoute => _config.useCustomRoute; 75 bool get useCustomRoute => _config.useCustomRoute;
1 import 'dart:async'; 1 import 'dart:async';
  2 +import 'dart:convert';
  3 +import 'dart:io';
2 import 'dart:math'; 4 import 'dart:math';
3 5
4 import 'package:auto_track/auto_track/config/manager.dart'; 6 import 'package:auto_track/auto_track/config/manager.dart';
5 import 'package:auto_track/auto_track/utils/track_model.dart'; 7 import 'package:auto_track/auto_track/utils/track_model.dart';
6 -import 'package:dio/dio.dart';  
7 8
8 import '../log/logger.dart'; 9 import '../log/logger.dart';
9 10
10 -  
11 -  
12 class AutoTrackQueue { 11 class AutoTrackQueue {
13 static final AutoTrackQueue instance = AutoTrackQueue._(); 12 static final AutoTrackQueue instance = AutoTrackQueue._();
14 - AutoTrackQueue._(); 13 + AutoTrackQueue._() {
  14 + httpClient.badCertificateCallback =
  15 + (X509Certificate cert, String host, int port) => true;
  16 + }
15 17
16 Timer? _timer; 18 Timer? _timer;
17 final List<TrackModel> _queue = []; 19 final List<TrackModel> _queue = [];
18 - final dio = Dio(); 20 + final httpClient = HttpClient();
19 21
20 void appendQueue(TrackModel model) { 22 void appendQueue(TrackModel model) {
21 if (_timer == null) return; 23 if (_timer == null) return;
@@ -24,7 +26,10 @@ class AutoTrackQueue { @@ -24,7 +26,10 @@ class AutoTrackQueue {
24 26
25 void start() { 27 void start() {
26 if (_timer != null) return; 28 if (_timer != null) return;
27 - _timer = Timer.periodic(Duration(seconds: AutoTrackConfigManager.instance.config.uploadInterval ?? 10), (timer) { 29 + _timer = Timer.periodic(
  30 + Duration(
  31 + seconds: AutoTrackConfigManager.instance.config.uploadInterval ??
  32 + 10), (timer) {
28 flush(); 33 flush();
29 }); 34 });
30 } 35 }
@@ -48,21 +53,29 @@ class AutoTrackQueue { @@ -48,21 +53,29 @@ class AutoTrackQueue {
48 } 53 }
49 if (host != null) { 54 if (host != null) {
50 final t = DateTime.now().millisecondsSinceEpoch; 55 final t = DateTime.now().millisecondsSinceEpoch;
51 - dio.post(host, data: {  
52 - 'app_key': config.appKey ?? '',  
53 - 'signature': config.signature!(t),  
54 - 't': t,  
55 - 'user_id': config.userId ?? '',  
56 - 'track_id': config.trackId ?? '',  
57 - 'unique_id': config.uniqueId ?? AutoTrackConfigManager.instance.deviceId,  
58 - 'device_id': AutoTrackConfigManager.instance.deviceId,  
59 - 'data_list': uploadList.map((e) => e.toMap()).toList(),  
60 - 'app_version': AutoTrackConfigManager.instance.appVersion,  
61 - 'device_info': AutoTrackConfigManager.instance.deviceInfo  
62 - }).onError((error, stackTrace) {  
63 - AutoTrackLogger.getInstance().error(error!);  
64 - return Future.value(Response(statusCode: 500, requestOptions: RequestOptions(path: host))); 56 +
  57 + httpClient.postUrl(Uri.parse(host)).then((request) {
  58 + request.headers.contentType = ContentType.json;
  59 + request.write(json.encode({
  60 + 'app_key': config.appKey ?? '',
  61 + 'signature': config.signature!(t),
  62 + 't': t,
  63 + 'user_id': config.userId ?? '',
  64 + 'track_id': config.trackId ?? '',
  65 + 'unique_id':
  66 + config.uniqueId ?? AutoTrackConfigManager.instance.deviceId,
  67 + 'device_id': AutoTrackConfigManager.instance.deviceId,
  68 + 'data_list': uploadList.map((e) => e.toMap()).toList(),
  69 + 'app_version': AutoTrackConfigManager.instance.appVersion,
  70 + 'device_info': AutoTrackConfigManager.instance.deviceInfo
  71 + }));
  72 + return request.close();
  73 + }).then((response) {
  74 + AutoTrackLogger.getInstance()
  75 + .debug('upload status => ${response.statusCode}');
  76 + }).catchError((error) {
  77 + AutoTrackLogger.getInstance().error(error);
65 }); 78 });
66 } 79 }
67 } 80 }
68 -}  
  81 +}
1 -import 'package:auto_track/auto_track/drag/drag_pointer_event_listener.dart'; 1 +import 'dart:io';
  2 +
2 import 'package:flutter/foundation.dart'; 3 import 'package:flutter/foundation.dart';
3 4
4 -import 'click/pointer_event_listener.dart';  
5 import 'config/config.dart'; 5 import 'config/config.dart';
6 import 'config/manager.dart'; 6 import 'config/manager.dart';
  7 +import 'listener/click/pointer_event_listener.dart';
  8 +import 'listener/drag/drag_pointer_event_listener.dart';
  9 +import 'listener/request/request_listener.dart';
7 import 'log/logger.dart'; 10 import 'log/logger.dart';
8 11
9 class AutoTrack { 12 class AutoTrack {
@@ -16,98 +19,132 @@ class AutoTrack { @@ -16,98 +19,132 @@ class AutoTrack {
16 } 19 }
17 20
18 void updateUserId(String id) { 21 void updateUserId(String id) {
19 - AutoTrackConfigManager.instance.updateUserId(id); 22 + AutoTrackConfigManager.instance.updateConfig((config) {
  23 + return config.copyWith(userId: id);
  24 + });
20 } 25 }
21 26
22 void updateSampleRate(double rate) { 27 void updateSampleRate(double rate) {
23 - AutoTrackConfigManager.instance.updateSampleRate(rate); 28 + AutoTrackConfigManager.instance.updateConfig((config) {
  29 + return config.copyWith(samplingRate: rate);
  30 + });
24 } 31 }
25 32
26 AutoTrack config(AutoTrackConfig? config) { 33 AutoTrack config(AutoTrackConfig? config) {
27 if (config != null) { 34 if (config != null) {
28 - AutoTrackConfigManager.instance.updateConfig(config); 35 + AutoTrackConfigManager.instance.setConfig(config);
29 } 36 }
30 return _instance; 37 return _instance;
31 } 38 }
32 39
33 AutoTrack pageConfigs(List<AutoTrackPageConfig>? pageConfigs) { 40 AutoTrack pageConfigs(List<AutoTrackPageConfig>? pageConfigs) {
34 if (pageConfigs != null) { 41 if (pageConfigs != null) {
35 - AutoTrackConfigManager.instance.updatePageConfigs(pageConfigs); 42 + AutoTrackConfigManager.instance.updateConfig((config) {
  43 + return config.copyWith(pageConfigs: pageConfigs);
  44 + });
36 } 45 }
37 return _instance; 46 return _instance;
38 } 47 }
39 48
40 AutoTrack ignoreElementKeys(List<Key>? ignoreElementKeys) { 49 AutoTrack ignoreElementKeys(List<Key>? ignoreElementKeys) {
41 if (ignoreElementKeys != null) { 50 if (ignoreElementKeys != null) {
42 - AutoTrackConfigManager.instance.updateIgnoreElementKeys(ignoreElementKeys); 51 + AutoTrackConfigManager.instance.updateConfig((config) {
  52 + return config.copyWith(ignoreElementKeys: ignoreElementKeys);
  53 + });
43 } 54 }
44 return _instance; 55 return _instance;
45 } 56 }
46 57
47 AutoTrack ignoreElementStringKeys(List<String>? ignoreElementStringKeys) { 58 AutoTrack ignoreElementStringKeys(List<String>? ignoreElementStringKeys) {
48 if (ignoreElementStringKeys != null) { 59 if (ignoreElementStringKeys != null) {
49 - AutoTrackConfigManager.instance.updateIgnoreElementStringKeys(ignoreElementStringKeys); 60 + AutoTrackConfigManager.instance.updateConfig((config) {
  61 + return config.copyWith(ignoreElementStringKeys: ignoreElementStringKeys);
  62 + });
50 } 63 }
51 return _instance; 64 return _instance;
52 } 65 }
53 66
54 AutoTrack enablePageView() { 67 AutoTrack enablePageView() {
55 - AutoTrackConfigManager.instance.enablePageView(true); 68 + AutoTrackConfigManager.instance.updateConfig((config) {
  69 + return config.copyWith(enablePageView: true);
  70 + });
56 return _instance; 71 return _instance;
57 } 72 }
58 73
59 AutoTrack disablePageView() { 74 AutoTrack disablePageView() {
60 - AutoTrackConfigManager.instance.enablePageView(false); 75 + AutoTrackConfigManager.instance.updateConfig((config) {
  76 + return config.copyWith(enablePageView: false);
  77 + });
61 return _instance; 78 return _instance;
62 } 79 }
63 80
64 AutoTrack enablePageLeave() { 81 AutoTrack enablePageLeave() {
65 - AutoTrackConfigManager.instance.enablePageLeave(true); 82 + AutoTrackConfigManager.instance.updateConfig((config) {
  83 + return config.copyWith(enablePageLeave: true);
  84 + });
66 return _instance; 85 return _instance;
67 } 86 }
68 87
69 AutoTrack disablePageLeave() { 88 AutoTrack disablePageLeave() {
70 - AutoTrackConfigManager.instance.enablePageLeave(false); 89 + AutoTrackConfigManager.instance.updateConfig((config) {
  90 + return config.copyWith(enablePageLeave: false);
  91 + });
71 return _instance; 92 return _instance;
72 } 93 }
73 94
74 AutoTrack enableIgnoreNullKey() { 95 AutoTrack enableIgnoreNullKey() {
75 - AutoTrackConfigManager.instance.enableIgnoreNullKey(true); 96 + AutoTrackConfigManager.instance.updateConfig((config) {
  97 + return config.copyWith(enableIgnoreNullKey: true);
  98 + });
76 return _instance; 99 return _instance;
77 } 100 }
78 101
79 AutoTrack disableIgnoreNullKey() { 102 AutoTrack disableIgnoreNullKey() {
80 - AutoTrackConfigManager.instance.enableIgnoreNullKey(false); 103 + AutoTrackConfigManager.instance.updateConfig((config) {
  104 + return config.copyWith(enableIgnoreNullKey: false);
  105 + });
81 return _instance; 106 return _instance;
82 } 107 }
83 108
84 AutoTrack enableUpload() { 109 AutoTrack enableUpload() {
85 - AutoTrackConfigManager.instance.enableUpload(true); 110 + AutoTrackConfigManager.instance.updateConfig((config) {
  111 + return config.copyWith(enableUpload: true);
  112 + });
86 return _instance; 113 return _instance;
87 } 114 }
88 115
89 AutoTrack disableUpload() { 116 AutoTrack disableUpload() {
90 - AutoTrackConfigManager.instance.enableUpload(false); 117 + AutoTrackConfigManager.instance.updateConfig((config) {
  118 + return config.copyWith(enableUpload: false);
  119 + });
91 return _instance; 120 return _instance;
92 } 121 }
93 122
94 AutoTrack enableClick() { 123 AutoTrack enableClick() {
95 - AutoTrackConfigManager.instance.enableClick(true); 124 + AutoTrackConfigManager.instance.updateConfig((config) {
  125 + return config.copyWith(enableClick: true);
  126 + });
96 return _instance; 127 return _instance;
97 } 128 }
98 129
99 - AutoTrack enableDrag() {  
100 - AutoTrackConfigManager.instance.enableDrag(true); 130 + AutoTrack disableClick() {
  131 + AutoTrackConfigManager.instance.updateConfig((config) {
  132 + return config.copyWith(enableClick: false);
  133 + });
101 return _instance; 134 return _instance;
102 } 135 }
103 136
104 - AutoTrack disableDrag() {  
105 - AutoTrackConfigManager.instance.enableDrag(true); 137 + AutoTrack enableDrag() {
  138 + AutoTrackConfigManager.instance.updateConfig((config) {
  139 + return config.copyWith(enableDrag: true);
  140 + });
106 return _instance; 141 return _instance;
107 } 142 }
108 143
109 - AutoTrack disableClick() {  
110 - AutoTrackConfigManager.instance.enableClick(false); 144 + AutoTrack disableDrag() {
  145 + AutoTrackConfigManager.instance.updateConfig((config) {
  146 + return config.copyWith(enableDrag: false);
  147 + });
111 return _instance; 148 return _instance;
112 } 149 }
113 150
@@ -122,6 +159,7 @@ class AutoTrack { @@ -122,6 +159,7 @@ class AutoTrack {
122 AutoTrackConfigManager.instance.enableAutoTrack(false); 159 AutoTrackConfigManager.instance.enableAutoTrack(false);
123 PointerEventListener.instance.stop(); 160 PointerEventListener.instance.stop();
124 DragPointerEventListener.instance.stop(); 161 DragPointerEventListener.instance.stop();
  162 + disableHttpRequest();
125 return _instance; 163 return _instance;
126 } 164 }
127 165
@@ -136,4 +174,14 @@ class AutoTrack { @@ -136,4 +174,14 @@ class AutoTrack {
136 } 174 }
137 return _instance; 175 return _instance;
138 } 176 }
  177 +
  178 + AutoTrack enableHttpRequest() {
  179 + HttpOverrides.global = AutoTrackHttpOverrides(HttpOverrides.current);
  180 + return _instance;
  181 + }
  182 +
  183 + AutoTrack disableHttpRequest() {
  184 + HttpOverrides.global = null;
  185 + return _instance;
  186 + }
139 } 187 }
@@ -3,9 +3,9 @@ import 'dart:convert'; @@ -3,9 +3,9 @@ import 'dart:convert';
3 import 'package:crypto/crypto.dart'; 3 import 'package:crypto/crypto.dart';
4 import 'package:flutter/widgets.dart'; 4 import 'package:flutter/widgets.dart';
5 5
6 -import '../config/manager.dart'; 6 +import '../../config/manager.dart';
7 import '../page_view/page_info.dart'; 7 import '../page_view/page_info.dart';
8 -import '../utils/element_util.dart'; 8 +import '../../utils/element_util.dart';
9 import 'element_key.dart'; 9 import 'element_key.dart';
10 import 'xpath.dart'; 10 import 'xpath.dart';
11 11
@@ -5,11 +5,11 @@ import 'package:flutter/rendering.dart'; @@ -5,11 +5,11 @@ import 'package:flutter/rendering.dart';
5 import 'package:flutter/widgets.dart'; 5 import 'package:flutter/widgets.dart';
6 6
7 7
8 -import '../log/logger.dart'; 8 +import '../../log/logger.dart';
9 import '../page_view/page_info.dart'; 9 import '../page_view/page_info.dart';
10 import '../page_view/page_stack.dart'; 10 import '../page_view/page_stack.dart';
11 -import '../track/track.dart';  
12 -import '../utils/element_util.dart'; 11 +import '../../track/track.dart';
  12 +import '../../utils/element_util.dart';
13 import 'click_info.dart'; 13 import 'click_info.dart';
14 14
15 class PointerEventListener { 15 class PointerEventListener {
1 import 'package:flutter/widgets.dart'; 1 import 'package:flutter/widgets.dart';
2 2
3 -import '../config/manager.dart';  
4 import '../page_view/page_info.dart'; 3 import '../page_view/page_info.dart';
5 4
6 class DragInfo { 5 class DragInfo {
1 -import 'package:auto_track/auto_track/drag/drag_info.dart';  
2 import 'package:auto_track/auto_track/track/track.dart'; 1 import 'package:auto_track/auto_track/track/track.dart';
3 import 'package:flutter/gestures.dart'; 2 import 'package:flutter/gestures.dart';
4 3
5 import '../page_view/page_stack.dart'; 4 import '../page_view/page_stack.dart';
  5 +import 'drag_info.dart';
6 6
7 class DragPointerEventListener { 7 class DragPointerEventListener {
8 static final DragPointerEventListener instance = DragPointerEventListener._(); 8 static final DragPointerEventListener instance = DragPointerEventListener._();
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 import 'package:flutter/scheduler.dart'; 2 import 'package:flutter/scheduler.dart';
3 3
4 -import '../config/config.dart';  
5 -import '../config/manager.dart';  
6 -import '../log/logger.dart';  
7 -import '../utils/element_util.dart'; 4 +import '../../config/config.dart';
  5 +import '../../config/manager.dart';
  6 +import '../../log/logger.dart';
  7 +import '../../utils/element_util.dart';
8 import 'page_stack.dart'; 8 import 'page_stack.dart';
9 9
10 class AutoTrackNavigationObserver extends NavigatorObserver { 10 class AutoTrackNavigationObserver extends NavigatorObserver {
@@ -3,9 +3,9 @@ import 'dart:convert'; @@ -3,9 +3,9 @@ import 'dart:convert';
3 import 'package:crypto/crypto.dart'; 3 import 'package:crypto/crypto.dart';
4 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
5 5
6 -import '../config/config.dart';  
7 -import '../config/manager.dart';  
8 -import '../utils/element_util.dart'; 6 +import '../../config/config.dart';
  7 +import '../../config/manager.dart';
  8 +import '../../utils/element_util.dart';
9 9
10 class PageInfo { 10 class PageInfo {
11 PageInfo._(this.timer); 11 PageInfo._(this.timer);
@@ -3,7 +3,7 @@ import 'dart:core'; @@ -3,7 +3,7 @@ import 'dart:core';
3 3
4 import 'package:flutter/widgets.dart'; 4 import 'package:flutter/widgets.dart';
5 5
6 -import '../track/track.dart'; 6 +import '../../track/track.dart';
7 import 'page_info.dart'; 7 import 'page_info.dart';
8 8
9 class PageStack with WidgetsBindingObserver { 9 class PageStack with WidgetsBindingObserver {
  1 +import 'dart:async';
  2 +import 'dart:convert';
  3 +import 'dart:io';
  4 +
  5 +import 'package:auto_track/auto_track/track/track.dart';
  6 +
  7 +import '../../config/manager.dart';
  8 +import '../../utils/request_model.dart';
  9 +import '../page_view/page_stack.dart';
  10 +
  11 +class HttpClientRequestWithChecker implements HttpClientRequest {
  12 + final HttpClientRequest _realRequest;
  13 + final Stopwatch _stopwatch;
  14 + final Page? pageInfoData;
  15 +
  16 + HttpClientRequestWithChecker(
  17 + this._realRequest, this._stopwatch, this.pageInfoData);
  18 +
  19 + @override
  20 + bool get bufferOutput => _realRequest.bufferOutput;
  21 +
  22 + @override
  23 + int get contentLength => _realRequest.contentLength;
  24 +
  25 + @override
  26 + Encoding get encoding => _realRequest.encoding;
  27 +
  28 + @override
  29 + bool get followRedirects => _realRequest.followRedirects;
  30 +
  31 + @override
  32 + int get maxRedirects => _realRequest.maxRedirects;
  33 +
  34 + @override
  35 + bool get persistentConnection => _realRequest.persistentConnection;
  36 +
  37 + @override
  38 + void add(List<int> data) {
  39 + _realRequest.add(data);
  40 + }
  41 +
  42 + @override
  43 + void addError(Object error, [StackTrace? stackTrace]) {
  44 + _realRequest.addError(error, stackTrace);
  45 + }
  46 +
  47 + @override
  48 + Future addStream(Stream<List<int>> stream) {
  49 + return _realRequest.addStream(stream);
  50 + }
  51 +
  52 + @override
  53 + Future<HttpClientResponse> close() async {
  54 + return _realRequest.close().then((HttpClientResponse response) {
  55 + _checkResponse(_realRequest, response);
  56 + return response;
  57 + }).catchError((dynamic error, dynamic stackTrace) {}, test: (error) {
  58 + _stopwatch.stop();
  59 + String message;
  60 + if (error is HttpException) {
  61 + message = error.message;
  62 + } else {
  63 + message = error.toString();
  64 + }
  65 + Track.instance.reportHttpRequest(RequestModel(
  66 + uri: _realRequest.uri,
  67 + method: method,
  68 + pageId: pageInfoData?.pageInfo?.pageKey ?? "",
  69 + requestHeaders: AutoTrackConfigManager
  70 + .instance.config.httpRequestConfig!.ignoreRequestHeader
  71 + ? null
  72 + : _realRequest.headers,
  73 + message: message,
  74 + status: -1,
  75 + spent: _stopwatch.elapsedMilliseconds));
  76 + return false;
  77 + });
  78 + }
  79 +
  80 + @override
  81 + HttpConnectionInfo? get connectionInfo => _realRequest.connectionInfo;
  82 +
  83 + @override
  84 + List<Cookie> get cookies => _realRequest.cookies;
  85 +
  86 + @override
  87 + Future<HttpClientResponse> get done async {
  88 + return close();
  89 + }
  90 +
  91 + @override
  92 + Future flush() {
  93 + return _realRequest.flush();
  94 + }
  95 +
  96 + @override
  97 + HttpHeaders get headers => _realRequest.headers;
  98 +
  99 + @override
  100 + String get method => _realRequest.method;
  101 +
  102 + @override
  103 + Uri get uri => _realRequest.uri;
  104 +
  105 + @override
  106 + void write(Object? obj) {
  107 + _realRequest.write(obj);
  108 + }
  109 +
  110 + @override
  111 + void writeAll(Iterable objects, [String separator = '']) {
  112 + _realRequest.writeAll(objects, separator);
  113 + }
  114 +
  115 + @override
  116 + void writeCharCode(int charCode) {
  117 + _realRequest.writeCharCode(charCode);
  118 + }
  119 +
  120 + @override
  121 + void writeln([Object? obj = '']) {
  122 + _realRequest.writeln(obj);
  123 + }
  124 +
  125 + void _checkResponse(HttpClientRequest request, HttpClientResponse response) {
  126 + String message = 'status ${response.statusCode}';
  127 + message = '$message: ${response.reasonPhrase}';
  128 +
  129 + _stopwatch.stop();
  130 + final config = AutoTrackConfigManager.instance.config.httpRequestConfig!;
  131 + Track.instance.reportHttpRequest(RequestModel(
  132 + uri: _realRequest.uri,
  133 + method: method,
  134 + pageId: pageInfoData?.pageInfo?.pageKey ?? "",
  135 + requestHeaders: config.ignoreRequestHeader ? null : request.headers,
  136 + responseHeaders: config.ignoreResponseHeader ? null : response.headers,
  137 + message: message,
  138 + status: response.statusCode,
  139 + spent: _stopwatch.elapsedMilliseconds));
  140 + }
  141 +
  142 + @override
  143 + set bufferOutput(bool bufferOutput) {
  144 + _realRequest.bufferOutput = bufferOutput;
  145 + }
  146 +
  147 + @override
  148 + set contentLength(int contentLength) {
  149 + _realRequest.contentLength = contentLength;
  150 + }
  151 +
  152 + @override
  153 + set encoding(Encoding encoding) {
  154 + _realRequest.encoding = encoding;
  155 + }
  156 +
  157 + @override
  158 + set followRedirects(bool followRedirects) {
  159 + _realRequest.followRedirects = followRedirects;
  160 + }
  161 +
  162 + @override
  163 + set maxRedirects(int maxRedirects) {
  164 + _realRequest.maxRedirects = maxRedirects;
  165 + }
  166 +
  167 + @override
  168 + set persistentConnection(bool persistentConnection) {
  169 + _realRequest.persistentConnection = persistentConnection;
  170 + }
  171 +
  172 + @override
  173 + void abort([Object? exception, StackTrace? stackTrace]) {
  174 + _realRequest.abort(exception, stackTrace);
  175 + }
  176 +}
  177 +
  178 +class HttpClientWithChecker implements HttpClient {
  179 + final HttpClient _realClient;
  180 +
  181 + Uri? url;
  182 + String? method;
  183 +
  184 + HttpClientWithChecker(this._realClient);
  185 +
  186 + @override
  187 + set connectionFactory(
  188 + Future<ConnectionTask<Socket>> Function(
  189 + Uri url, String? proxyHost, int? proxyPort)?
  190 + f) {
  191 + // TODO: add impl here
  192 + assert(false);
  193 + }
  194 +
  195 + @override
  196 + set keyLog(Function(String line)? callback) {
  197 + // TODO: add impl here
  198 + assert(false);
  199 + }
  200 +
  201 + @override
  202 + bool get autoUncompress => _realClient.autoUncompress;
  203 +
  204 + @override
  205 + set autoUncompress(bool value) => _realClient.autoUncompress = value;
  206 +
  207 + @override
  208 + Duration? get connectionTimeout => _realClient.connectionTimeout;
  209 +
  210 + @override
  211 + set connectionTimeout(Duration? value) =>
  212 + _realClient.connectionTimeout = value;
  213 +
  214 + @override
  215 + Duration get idleTimeout => _realClient.idleTimeout;
  216 +
  217 + @override
  218 + set idleTimeout(Duration value) => _realClient.idleTimeout = value;
  219 +
  220 + @override
  221 + int? get maxConnectionsPerHost => _realClient.maxConnectionsPerHost;
  222 +
  223 + @override
  224 + set maxConnectionsPerHost(int? value) =>
  225 + _realClient.maxConnectionsPerHost = value;
  226 +
  227 + @override
  228 + String? get userAgent => _realClient.userAgent;
  229 +
  230 + @override
  231 + set userAgent(String? value) => _realClient.userAgent = value;
  232 +
  233 + @override
  234 + void addCredentials(
  235 + Uri url, String realm, HttpClientCredentials credentials) =>
  236 + _realClient.addCredentials(url, realm, credentials);
  237 +
  238 + @override
  239 + void addProxyCredentials(String host, int port, String realm,
  240 + HttpClientCredentials credentials) =>
  241 + _realClient.addProxyCredentials(host, port, realm, credentials);
  242 +
  243 + @override
  244 + set authenticate(
  245 + Future<bool> Function(Uri url, String scheme, String? realm)? f) =>
  246 + _realClient.authenticate = f;
  247 +
  248 + @override
  249 + set authenticateProxy(
  250 + Future<bool> Function(
  251 + String host, int port, String scheme, String? realm)?
  252 + f) =>
  253 + _realClient.authenticateProxy = f;
  254 +
  255 + @override
  256 + set badCertificateCallback(
  257 + bool Function(X509Certificate cert, String host, int port)?
  258 + callback) =>
  259 + _realClient.badCertificateCallback = callback;
  260 +
  261 + @override
  262 + void close({bool force = false}) => _realClient.close(force: force);
  263 +
  264 + @override
  265 + Future<HttpClientRequest> delete(String host, int port, String path) =>
  266 + _realClient.delete(host, port, path);
  267 +
  268 + @override
  269 + Future<HttpClientRequest> deleteUrl(Uri url) => _realClient.deleteUrl(url);
  270 +
  271 + @override
  272 + set findProxy(String Function(Uri url)? f) => _realClient.findProxy = f;
  273 +
  274 + @override
  275 + Future<HttpClientRequest> get(String host, int port, String path) =>
  276 + _realClient.get(host, port, path);
  277 +
  278 + @override
  279 + Future<HttpClientRequest> getUrl(Uri url) =>
  280 + _addCheck(_realClient.getUrl(url), 'get', url);
  281 +
  282 + @override
  283 + Future<HttpClientRequest> head(String host, int port, String path) =>
  284 + _realClient.head(host, port, path);
  285 +
  286 + @override
  287 + Future<HttpClientRequest> headUrl(Uri url) =>
  288 + _addCheck(_realClient.headUrl(url), 'head', url);
  289 +
  290 + @override
  291 + Future<HttpClientRequest> patch(String host, int port, String path) =>
  292 + _realClient.patch(host, port, path);
  293 +
  294 + @override
  295 + Future<HttpClientRequest> patchUrl(Uri url) =>
  296 + _addCheck(_realClient.patchUrl(url), 'patch', url);
  297 +
  298 + @override
  299 + Future<HttpClientRequest> post(String host, int port, String path) =>
  300 + _realClient.post(host, port, path);
  301 +
  302 + @override
  303 + Future<HttpClientRequest> postUrl(Uri url) =>
  304 + _addCheck(_realClient.postUrl(url), 'post', url);
  305 +
  306 + @override
  307 + Future<HttpClientRequest> put(String host, int port, String path) =>
  308 + _realClient.put(host, port, path);
  309 +
  310 + @override
  311 + Future<HttpClientRequest> putUrl(Uri url) =>
  312 + _addCheck(_realClient.putUrl(url), 'put', url);
  313 +
  314 + @override
  315 + Future<HttpClientRequest> open(
  316 + String method, String host, int port, String path) {
  317 + const int hashMark = 0x23;
  318 + const int questionMark = 0x3f;
  319 + int fragmentStart = path.length;
  320 + int queryStart = path.length;
  321 + for (int i = path.length - 1; i >= 0; i--) {
  322 + final char = path.codeUnitAt(i);
  323 + if (char == hashMark) {
  324 + fragmentStart = i;
  325 + queryStart = i;
  326 + } else if (char == questionMark) {
  327 + queryStart = i;
  328 + }
  329 + }
  330 + String? query;
  331 + if (queryStart < fragmentStart) {
  332 + query = path.substring(queryStart + 1, fragmentStart);
  333 + path = path.substring(0, queryStart);
  334 + }
  335 + final Uri uri =
  336 + Uri(scheme: 'http', host: host, port: port, path: path, query: query);
  337 + return _addCheck(_realClient.open(method, host, port, path), method, uri);
  338 + }
  339 +
  340 + @override
  341 + Future<HttpClientRequest> openUrl(String method, Uri url) =>
  342 + _addCheck(_realClient.openUrl(method, url), method, url);
  343 +
  344 + Future<HttpClientRequest> _addCheck(
  345 + Future<HttpClientRequest> request, String method, Uri url) {
  346 + final host = AutoTrackConfigManager.instance.config.host;
  347 + if (host != null) {
  348 + final uploadUrl = Uri.parse(host);
  349 + if (uploadUrl.host == url.host && uploadUrl.path == url.path) {
  350 + return request;
  351 + }
  352 + }
  353 +
  354 + final Stopwatch stopwatch = Stopwatch()..start();
  355 + final Page? pageInfoData = PageStack.instance.getCurrentPage();
  356 + return request
  357 + .then((HttpClientRequest request) =>
  358 + HttpClientRequestWithChecker(request, stopwatch, pageInfoData))
  359 + .catchError((dynamic error, dynamic stackTrace) {}, test: (error) {
  360 + String message = error.toString();
  361 + if (error is SocketException) {
  362 + message = error.message;
  363 + }
  364 + Track.instance.reportHttpRequest(RequestModel(
  365 + uri: url,
  366 + method: method,
  367 + pageId: pageInfoData?.pageInfo?.pageKey ?? "",
  368 + requestHeaders: null,
  369 + message: message,
  370 + status: -1,
  371 + spent: stopwatch.elapsedMilliseconds));
  372 + return false;
  373 + });
  374 + }
  375 +}
  376 +
  377 +class _DefaultHttpOverrides extends HttpOverrides {}
  378 +
  379 +class AutoTrackHttpOverrides extends HttpOverrides {
  380 + HttpOverrides? _currentOverrides;
  381 +
  382 + AutoTrackHttpOverrides(this._currentOverrides) : super() {
  383 + _currentOverrides ??= _DefaultHttpOverrides();
  384 + }
  385 +
  386 + @override
  387 + HttpClient createHttpClient(SecurityContext? context) {
  388 + return HttpClientWithChecker(_currentOverrides!.createHttpClient(context));
  389 + }
  390 +
  391 + @override
  392 + String findProxyFromEnvironment(Uri url, Map<String, String>? environment) {
  393 + return _currentOverrides!.findProxyFromEnvironment(url, environment);
  394 + }
  395 +}
1 -import 'package:dio/dio.dart'; 1 +import 'dart:io';
  2 +
2 import 'package:flutter/widgets.dart'; 3 import 'package:flutter/widgets.dart';
3 4
4 typedef AutoTrackLoggerHandler = void Function(AutoTrackLoggerLevel level, String message); 5 typedef AutoTrackLoggerHandler = void Function(AutoTrackLoggerLevel level, String message);
@@ -26,8 +27,8 @@ class AutoTrackLogger { @@ -26,8 +27,8 @@ class AutoTrackLogger {
26 message = e.message; 27 message = e.message;
27 } else if (e is Error) { 28 } else if (e is Error) {
28 message = e.stackTrace.toString(); 29 message = e.stackTrace.toString();
29 - } else if (e is DioException) {  
30 - message = (e).message ?? 'dio exception with unknown message'; 30 + } else if (e is HttpException) {
  31 + message = e.message;
31 } 32 }
32 _print(AutoTrackLoggerLevel.error, message); 33 _print(AutoTrackLoggerLevel.error, message);
33 } 34 }
1 import 'package:auto_track/auto_track/config/queue.dart'; 1 import 'package:auto_track/auto_track/config/queue.dart';
2 -import 'package:auto_track/auto_track/drag/drag_info.dart';  
3 import 'package:auto_track/auto_track/utils/error_model.dart'; 2 import 'package:auto_track/auto_track/utils/error_model.dart';
  3 +import 'package:auto_track/auto_track/utils/request_model.dart';
4 import 'package:auto_track/auto_track/utils/track_model.dart'; 4 import 'package:auto_track/auto_track/utils/track_model.dart';
5 5
6 -import '../click/click_info.dart';  
7 import '../config/manager.dart'; 6 import '../config/manager.dart';
  7 +import '../listener/click/click_info.dart';
  8 +import '../listener/drag/drag_info.dart';
  9 +import '../listener/page_view/page_info.dart';
8 import '../log/logger.dart'; 10 import '../log/logger.dart';
9 -import '../page_view/page_info.dart';  
10 11
11 class Track { 12 class Track {
12 static final Track instance = Track._(); 13 static final Track instance = Track._();
@@ -104,7 +105,14 @@ class Track { @@ -104,7 +105,14 @@ class Track {
104 } 105 }
105 106
106 void reportError(Object error, StackTrace stack) { 107 void reportError(Object error, StackTrace stack) {
107 - _TrackPlugin.customEvent('error', ErrorModel(error: error, stack: stack).toMap()); 108 + final model = ErrorModel(error: error, stack: stack);
  109 + _TrackPlugin.customEvent('error', model.toMap());
  110 + AutoTrackLogger.getInstance().debug('track error => ${model.toMap()}');
  111 + }
  112 +
  113 + void reportHttpRequest(RequestModel requestModel) {
  114 + _TrackPlugin.customEvent('http', requestModel.toMap(), key: requestModel.uri.path);
  115 + AutoTrackLogger.getInstance().debug('track request => ${requestModel.toMap()}');
108 } 116 }
109 } 117 }
110 118
@@ -128,8 +136,8 @@ class _TrackPlugin { @@ -128,8 +136,8 @@ class _TrackPlugin {
128 AutoTrackQueue.instance.appendQueue(model); 136 AutoTrackQueue.instance.appendQueue(model);
129 } 137 }
130 138
131 - static void customEvent(String type, Map<String, dynamic> params) {  
132 - var model = TrackModel(type, DateTime.now().millisecondsSinceEpoch, params, params['key'] ?? type); 139 + static void customEvent(String type, Map<String, dynamic> params, { String? key }) {
  140 + var model = TrackModel(type, DateTime.now().millisecondsSinceEpoch, params, params['key'] ?? key ?? type);
133 AutoTrackConfigManager.instance.config.eventHandler?.call(model); 141 AutoTrackConfigManager.instance.config.eventHandler?.call(model);
134 AutoTrackQueue.instance.appendQueue(model); 142 AutoTrackQueue.instance.appendQueue(model);
135 } 143 }
  1 +class RequestModel {
  2 + RequestModel({
  3 + required this.uri,
  4 + required this.method,
  5 + required this.message,
  6 + required this.status,
  7 + required this.spent,
  8 + required this.pageId,
  9 + this.requestHeaders,
  10 + this.responseHeaders,
  11 + });
  12 +
  13 + Uri uri;
  14 + String method;
  15 + String message;
  16 + String pageId;
  17 + int status;
  18 + int spent;
  19 + dynamic requestHeaders;
  20 + dynamic responseHeaders;
  21 +
  22 + Map<String, dynamic> toMap() {
  23 + return {
  24 + 'uri': uri.toString(),
  25 + 'method': method,
  26 + 'message': message,
  27 + 'pageId': pageId,
  28 + 'status': status,
  29 + 'spent': spent,
  30 + 'requestHeaders': requestHeaders.toString(),
  31 + 'responseHeaders': responseHeaders.toString(),
  32 + };
  33 + }
  34 +}
1 name: auto_track 1 name: auto_track
2 -description: "Auto Track Plugin"  
3 -version: 0.0.6 2 +description: "Low-intrusion global automatic embedding, automatically recording events such as page entry, exit, clicks, scrolling, and HTTP requests, and supporting custom events."
  3 +version: 0.0.7
4 homepage: https://github.com/epoll-j/auto_track_plugin 4 homepage: https://github.com/epoll-j/auto_track_plugin
5 5
6 environment: 6 environment:
@@ -10,7 +10,6 @@ environment: @@ -10,7 +10,6 @@ environment:
10 dependencies: 10 dependencies:
11 crypto: ^3.0.3 11 crypto: ^3.0.3
12 device_info_plus: ^9.1.2 12 device_info_plus: ^9.1.2
13 - dio: ^5.4.1  
14 flutter: 13 flutter:
15 sdk: flutter 14 sdk: flutter
16 package_info_plus: ^8.0.1 15 package_info_plus: ^8.0.1