Dubhe
Committed by GitHub

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

Feat/request listener
## 0.0.1
## 0.0.7
* TODO: Describe initial release.
* remove dio dependency
* add http request listener
... ...
... ... @@ -2,7 +2,7 @@
> Flutter全埋点插件,支持 Android 和 iOS
低侵入全局自动埋点,自动记录页面进入、退出,点击、滑动等事件,并支持自定义事件。
低侵入全局自动埋点,自动记录页面进入、退出,点击、滑动、http请求等事件,并支持自定义事件。
## Getting Started 使用指南
... ... @@ -69,6 +69,7 @@ class _MyAppState extends State<MyApp> {
.enableClick() // 启用点击统计
.enableDrag() // 启用滑动统计
.enableIgnoreNullKey() // 忽略空的key,如果不忽略,没有配置key的页面或事件会基于一定的规则生成一个随机的key进行上报统计
.enableHttpRequest() // 启用http请求监听
.enableLog(); // 启用日志,建议在debug模式下开启,会打印一些埋点相关的日志
super.initState();
... ...
... ... @@ -2,7 +2,7 @@ library autotrack;
export './auto_track/index.dart';
export './auto_track/config/config.dart';
export './auto_track/page_view/navigation_observer.dart';
export './auto_track/click/navigator_key.dart';
export './auto_track/click/element_key.dart';
export './auto_track/listener/page_view/navigation_observer.dart';
export './auto_track/listener/click/navigator_key.dart';
export './auto_track/listener/click/element_key.dart';
export './auto_track/log/logger.dart';
\ No newline at end of file
... ...
... ... @@ -29,9 +29,12 @@ class AutoTrackConfig {
this.enableClick = true, // 监听点击事件
this.enableDrag = false, // 监听拖拽事件
this.enableIgnoreNullKey = false, // 忽略空key事件
this.httpRequestConfig,
}) {
trackId ??= const Uuid().v4().replaceAll('-', '');
signature ??= (t) => sha256.convert(utf8.encode('$appKey$t$appSecret')).toString();
signature ??=
(t) => sha256.convert(utf8.encode('$appKey$t$appSecret')).toString();
httpRequestConfig ??= HttpRequestConfig();
}
String? host;
... ... @@ -76,18 +79,68 @@ class AutoTrackConfig {
bool enableDrag;
bool enableIgnoreNullKey;
HttpRequestConfig? httpRequestConfig;
copyWith({
String? host,
String? appKey,
String? appSecret,
String? trackId,
String? userId,
String? uniqueId,
double? samplingRate,
int? uploadInterval,
Function? signature,
EventHandlerFunc? eventHandler,
List<AutoTrackPageConfig>? pageConfigs,
bool? useCustomRoute,
List<Key>? ignoreElementKeys,
List<String>? ignoreElementStringKeys,
bool? enablePageView,
bool? enablePageLeave,
bool? enableClick,
bool? enableUpload,
bool? enableDrag,
bool? enableIgnoreNullKey,
HttpRequestConfig? httpRequestConfig
}) {
return AutoTrackConfig(
host: host ?? this.host,
appKey: appKey ?? this.appKey,
appSecret: appSecret ?? this.appSecret,
trackId: trackId ?? this.trackId,
userId: userId ?? this.userId,
uniqueId: uniqueId ?? this.uniqueId,
samplingRate: samplingRate ?? this.samplingRate,
uploadInterval: uploadInterval ?? this.uploadInterval,
signature: signature ?? this.signature,
eventHandler: eventHandler ?? this.eventHandler,
pageConfigs: pageConfigs ?? this.pageConfigs,
useCustomRoute: useCustomRoute ?? this.useCustomRoute,
ignoreElementKeys: ignoreElementKeys ?? this.ignoreElementKeys,
ignoreElementStringKeys:
ignoreElementStringKeys ?? this.ignoreElementStringKeys,
enablePageView: enablePageView ?? this.enablePageView,
enablePageLeave: enablePageLeave ?? this.enablePageLeave,
enableClick: enableClick ?? this.enableClick,
enableUpload: enableUpload ?? this.enableUpload,
enableDrag: enableDrag ?? this.enableDrag,
enableIgnoreNullKey: enableIgnoreNullKey ?? this.enableIgnoreNullKey,
httpRequestConfig: httpRequestConfig ?? this.httpRequestConfig
);
}
}
typedef PageWidgetFunc = bool Function(Widget);
class AutoTrackPageConfig<T extends Widget> {
AutoTrackPageConfig({
this.pageID,
AutoTrackPageConfig(
{this.pageID,
this.pagePath,
this.ignore = false,
this.pageTitle,
this.isPageWidget
}) {
this.isPageWidget}) {
isPageWidget ??= (pageWidget) => pageWidget is T;
}
... ... @@ -97,3 +150,13 @@ class AutoTrackPageConfig<T extends Widget> {
String? pageTitle;
PageWidgetFunc? isPageWidget;
}
class HttpRequestConfig {
bool ignoreRequestHeader;
bool ignoreResponseHeader;
HttpRequestConfig({
this.ignoreRequestHeader = false,
this.ignoreResponseHeader = false,
});
}
... ...
... ... @@ -8,6 +8,8 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'config.dart';
typedef UpdateConfigFunc = AutoTrackConfig Function(AutoTrackConfig);
class AutoTrackConfigManager {
static final AutoTrackConfigManager instance = AutoTrackConfigManager._();
... ... @@ -36,10 +38,16 @@ class AutoTrackConfigManager {
bool _autoTrackEnable = false;
bool get autoTrackEnable => _autoTrackEnable;
void updateConfig(AutoTrackConfig config) {
_config = config;
void setConfig(AutoTrackConfig config) {
updateConfig((old) {
return config;
});
_updateDeviceId();
if (config.enableUpload) {
}
void updateConfig(UpdateConfigFunc func) {
_config = func(_config);
if (_config.enableUpload) {
AutoTrackQueue.instance.start();
} else {
AutoTrackQueue.instance.stop();
... ... @@ -58,59 +66,10 @@ class AutoTrackConfigManager {
}
}
void updateUserId(String userId) {
_config.userId = userId;
}
void updateSampleRate(double rate) {
_config.samplingRate = rate;
}
void updatePageConfigs(List<AutoTrackPageConfig> pageConfigs) {
_config.pageConfigs = pageConfigs;
}
void updateIgnoreElementKeys(List<Key> ignoreElementKeys) {
_config.ignoreElementKeys = ignoreElementKeys;
}
void updateIgnoreElementStringKeys(List<String> ignoreElementStringKeys) {
_config.ignoreElementStringKeys = ignoreElementStringKeys;
}
void enablePageView(bool enable) {
_config.enablePageView = enable;
}
void enablePageLeave(bool enable) {
_config.enablePageLeave = enable;
}
void enableClick(bool enable) {
_config.enableClick = enable;
}
void enableDrag(bool enable) {
_config.enableDrag = enable;
}
void enableAutoTrack(bool enable) {
_autoTrackEnable = enable;
}
void enableUpload(bool enable) {
_config.enableUpload = enable;
if (enable) {
AutoTrackQueue.instance.start();
} else {
AutoTrackQueue.instance.stop();
}
}
void enableIgnoreNullKey(bool enable) {
_config.enableIgnoreNullKey = enable;
}
List<AutoTrackPageConfig> get pageConfigs => _config.pageConfigs;
bool get useCustomRoute => _config.useCustomRoute;
... ...
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:auto_track/auto_track/config/manager.dart';
import 'package:auto_track/auto_track/utils/track_model.dart';
import 'package:dio/dio.dart';
import '../log/logger.dart';
class AutoTrackQueue {
static final AutoTrackQueue instance = AutoTrackQueue._();
AutoTrackQueue._();
AutoTrackQueue._() {
httpClient.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
Timer? _timer;
final List<TrackModel> _queue = [];
final dio = Dio();
final httpClient = HttpClient();
void appendQueue(TrackModel model) {
if (_timer == null) return;
... ... @@ -24,7 +26,10 @@ class AutoTrackQueue {
void start() {
if (_timer != null) return;
_timer = Timer.periodic(Duration(seconds: AutoTrackConfigManager.instance.config.uploadInterval ?? 10), (timer) {
_timer = Timer.periodic(
Duration(
seconds: AutoTrackConfigManager.instance.config.uploadInterval ??
10), (timer) {
flush();
});
}
... ... @@ -48,20 +53,28 @@ class AutoTrackQueue {
}
if (host != null) {
final t = DateTime.now().millisecondsSinceEpoch;
dio.post(host, data: {
httpClient.postUrl(Uri.parse(host)).then((request) {
request.headers.contentType = ContentType.json;
request.write(json.encode({
'app_key': config.appKey ?? '',
'signature': config.signature!(t),
't': t,
'user_id': config.userId ?? '',
'track_id': config.trackId ?? '',
'unique_id': config.uniqueId ?? AutoTrackConfigManager.instance.deviceId,
'unique_id':
config.uniqueId ?? AutoTrackConfigManager.instance.deviceId,
'device_id': AutoTrackConfigManager.instance.deviceId,
'data_list': uploadList.map((e) => e.toMap()).toList(),
'app_version': AutoTrackConfigManager.instance.appVersion,
'device_info': AutoTrackConfigManager.instance.deviceInfo
}).onError((error, stackTrace) {
AutoTrackLogger.getInstance().error(error!);
return Future.value(Response(statusCode: 500, requestOptions: RequestOptions(path: host)));
}));
return request.close();
}).then((response) {
AutoTrackLogger.getInstance()
.debug('upload status => ${response.statusCode}');
}).catchError((error) {
AutoTrackLogger.getInstance().error(error);
});
}
}
... ...
import 'package:auto_track/auto_track/drag/drag_pointer_event_listener.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'click/pointer_event_listener.dart';
import 'config/config.dart';
import 'config/manager.dart';
import 'listener/click/pointer_event_listener.dart';
import 'listener/drag/drag_pointer_event_listener.dart';
import 'listener/request/request_listener.dart';
import 'log/logger.dart';
class AutoTrack {
... ... @@ -16,98 +19,132 @@ class AutoTrack {
}
void updateUserId(String id) {
AutoTrackConfigManager.instance.updateUserId(id);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(userId: id);
});
}
void updateSampleRate(double rate) {
AutoTrackConfigManager.instance.updateSampleRate(rate);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(samplingRate: rate);
});
}
AutoTrack config(AutoTrackConfig? config) {
if (config != null) {
AutoTrackConfigManager.instance.updateConfig(config);
AutoTrackConfigManager.instance.setConfig(config);
}
return _instance;
}
AutoTrack pageConfigs(List<AutoTrackPageConfig>? pageConfigs) {
if (pageConfigs != null) {
AutoTrackConfigManager.instance.updatePageConfigs(pageConfigs);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(pageConfigs: pageConfigs);
});
}
return _instance;
}
AutoTrack ignoreElementKeys(List<Key>? ignoreElementKeys) {
if (ignoreElementKeys != null) {
AutoTrackConfigManager.instance.updateIgnoreElementKeys(ignoreElementKeys);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(ignoreElementKeys: ignoreElementKeys);
});
}
return _instance;
}
AutoTrack ignoreElementStringKeys(List<String>? ignoreElementStringKeys) {
if (ignoreElementStringKeys != null) {
AutoTrackConfigManager.instance.updateIgnoreElementStringKeys(ignoreElementStringKeys);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(ignoreElementStringKeys: ignoreElementStringKeys);
});
}
return _instance;
}
AutoTrack enablePageView() {
AutoTrackConfigManager.instance.enablePageView(true);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enablePageView: true);
});
return _instance;
}
AutoTrack disablePageView() {
AutoTrackConfigManager.instance.enablePageView(false);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enablePageView: false);
});
return _instance;
}
AutoTrack enablePageLeave() {
AutoTrackConfigManager.instance.enablePageLeave(true);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enablePageLeave: true);
});
return _instance;
}
AutoTrack disablePageLeave() {
AutoTrackConfigManager.instance.enablePageLeave(false);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enablePageLeave: false);
});
return _instance;
}
AutoTrack enableIgnoreNullKey() {
AutoTrackConfigManager.instance.enableIgnoreNullKey(true);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableIgnoreNullKey: true);
});
return _instance;
}
AutoTrack disableIgnoreNullKey() {
AutoTrackConfigManager.instance.enableIgnoreNullKey(false);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableIgnoreNullKey: false);
});
return _instance;
}
AutoTrack enableUpload() {
AutoTrackConfigManager.instance.enableUpload(true);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableUpload: true);
});
return _instance;
}
AutoTrack disableUpload() {
AutoTrackConfigManager.instance.enableUpload(false);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableUpload: false);
});
return _instance;
}
AutoTrack enableClick() {
AutoTrackConfigManager.instance.enableClick(true);
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableClick: true);
});
return _instance;
}
AutoTrack enableDrag() {
AutoTrackConfigManager.instance.enableDrag(true);
AutoTrack disableClick() {
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableClick: false);
});
return _instance;
}
AutoTrack disableDrag() {
AutoTrackConfigManager.instance.enableDrag(true);
AutoTrack enableDrag() {
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableDrag: true);
});
return _instance;
}
AutoTrack disableClick() {
AutoTrackConfigManager.instance.enableClick(false);
AutoTrack disableDrag() {
AutoTrackConfigManager.instance.updateConfig((config) {
return config.copyWith(enableDrag: false);
});
return _instance;
}
... ... @@ -122,6 +159,7 @@ class AutoTrack {
AutoTrackConfigManager.instance.enableAutoTrack(false);
PointerEventListener.instance.stop();
DragPointerEventListener.instance.stop();
disableHttpRequest();
return _instance;
}
... ... @@ -136,4 +174,14 @@ class AutoTrack {
}
return _instance;
}
AutoTrack enableHttpRequest() {
HttpOverrides.global = AutoTrackHttpOverrides(HttpOverrides.current);
return _instance;
}
AutoTrack disableHttpRequest() {
HttpOverrides.global = null;
return _instance;
}
}
\ No newline at end of file
... ...
... ... @@ -3,9 +3,9 @@ import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/widgets.dart';
import '../config/manager.dart';
import '../../config/manager.dart';
import '../page_view/page_info.dart';
import '../utils/element_util.dart';
import '../../utils/element_util.dart';
import 'element_key.dart';
import 'xpath.dart';
... ...
... ... @@ -5,11 +5,11 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../log/logger.dart';
import '../../log/logger.dart';
import '../page_view/page_info.dart';
import '../page_view/page_stack.dart';
import '../track/track.dart';
import '../utils/element_util.dart';
import '../../track/track.dart';
import '../../utils/element_util.dart';
import 'click_info.dart';
class PointerEventListener {
... ...
import 'package:flutter/widgets.dart';
import '../config/manager.dart';
import '../page_view/page_info.dart';
class DragInfo {
... ...
import 'package:auto_track/auto_track/drag/drag_info.dart';
import 'package:auto_track/auto_track/track/track.dart';
import 'package:flutter/gestures.dart';
import '../page_view/page_stack.dart';
import 'drag_info.dart';
class DragPointerEventListener {
static final DragPointerEventListener instance = DragPointerEventListener._();
... ...
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../config/config.dart';
import '../config/manager.dart';
import '../log/logger.dart';
import '../utils/element_util.dart';
import '../../config/config.dart';
import '../../config/manager.dart';
import '../../log/logger.dart';
import '../../utils/element_util.dart';
import 'page_stack.dart';
class AutoTrackNavigationObserver extends NavigatorObserver {
... ...
... ... @@ -3,9 +3,9 @@ import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import '../config/config.dart';
import '../config/manager.dart';
import '../utils/element_util.dart';
import '../../config/config.dart';
import '../../config/manager.dart';
import '../../utils/element_util.dart';
class PageInfo {
PageInfo._(this.timer);
... ...
... ... @@ -3,7 +3,7 @@ import 'dart:core';
import 'package:flutter/widgets.dart';
import '../track/track.dart';
import '../../track/track.dart';
import 'page_info.dart';
class PageStack with WidgetsBindingObserver {
... ...
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:auto_track/auto_track/track/track.dart';
import '../../config/manager.dart';
import '../../utils/request_model.dart';
import '../page_view/page_stack.dart';
class HttpClientRequestWithChecker implements HttpClientRequest {
final HttpClientRequest _realRequest;
final Stopwatch _stopwatch;
final Page? pageInfoData;
HttpClientRequestWithChecker(
this._realRequest, this._stopwatch, this.pageInfoData);
@override
bool get bufferOutput => _realRequest.bufferOutput;
@override
int get contentLength => _realRequest.contentLength;
@override
Encoding get encoding => _realRequest.encoding;
@override
bool get followRedirects => _realRequest.followRedirects;
@override
int get maxRedirects => _realRequest.maxRedirects;
@override
bool get persistentConnection => _realRequest.persistentConnection;
@override
void add(List<int> data) {
_realRequest.add(data);
}
@override
void addError(Object error, [StackTrace? stackTrace]) {
_realRequest.addError(error, stackTrace);
}
@override
Future addStream(Stream<List<int>> stream) {
return _realRequest.addStream(stream);
}
@override
Future<HttpClientResponse> close() async {
return _realRequest.close().then((HttpClientResponse response) {
_checkResponse(_realRequest, response);
return response;
}).catchError((dynamic error, dynamic stackTrace) {}, test: (error) {
_stopwatch.stop();
String message;
if (error is HttpException) {
message = error.message;
} else {
message = error.toString();
}
Track.instance.reportHttpRequest(RequestModel(
uri: _realRequest.uri,
method: method,
pageId: pageInfoData?.pageInfo?.pageKey ?? "",
requestHeaders: AutoTrackConfigManager
.instance.config.httpRequestConfig!.ignoreRequestHeader
? null
: _realRequest.headers,
message: message,
status: -1,
spent: _stopwatch.elapsedMilliseconds));
return false;
});
}
@override
HttpConnectionInfo? get connectionInfo => _realRequest.connectionInfo;
@override
List<Cookie> get cookies => _realRequest.cookies;
@override
Future<HttpClientResponse> get done async {
return close();
}
@override
Future flush() {
return _realRequest.flush();
}
@override
HttpHeaders get headers => _realRequest.headers;
@override
String get method => _realRequest.method;
@override
Uri get uri => _realRequest.uri;
@override
void write(Object? obj) {
_realRequest.write(obj);
}
@override
void writeAll(Iterable objects, [String separator = '']) {
_realRequest.writeAll(objects, separator);
}
@override
void writeCharCode(int charCode) {
_realRequest.writeCharCode(charCode);
}
@override
void writeln([Object? obj = '']) {
_realRequest.writeln(obj);
}
void _checkResponse(HttpClientRequest request, HttpClientResponse response) {
String message = 'status ${response.statusCode}';
message = '$message: ${response.reasonPhrase}';
_stopwatch.stop();
final config = AutoTrackConfigManager.instance.config.httpRequestConfig!;
Track.instance.reportHttpRequest(RequestModel(
uri: _realRequest.uri,
method: method,
pageId: pageInfoData?.pageInfo?.pageKey ?? "",
requestHeaders: config.ignoreRequestHeader ? null : request.headers,
responseHeaders: config.ignoreResponseHeader ? null : response.headers,
message: message,
status: response.statusCode,
spent: _stopwatch.elapsedMilliseconds));
}
@override
set bufferOutput(bool bufferOutput) {
_realRequest.bufferOutput = bufferOutput;
}
@override
set contentLength(int contentLength) {
_realRequest.contentLength = contentLength;
}
@override
set encoding(Encoding encoding) {
_realRequest.encoding = encoding;
}
@override
set followRedirects(bool followRedirects) {
_realRequest.followRedirects = followRedirects;
}
@override
set maxRedirects(int maxRedirects) {
_realRequest.maxRedirects = maxRedirects;
}
@override
set persistentConnection(bool persistentConnection) {
_realRequest.persistentConnection = persistentConnection;
}
@override
void abort([Object? exception, StackTrace? stackTrace]) {
_realRequest.abort(exception, stackTrace);
}
}
class HttpClientWithChecker implements HttpClient {
final HttpClient _realClient;
Uri? url;
String? method;
HttpClientWithChecker(this._realClient);
@override
set connectionFactory(
Future<ConnectionTask<Socket>> Function(
Uri url, String? proxyHost, int? proxyPort)?
f) {
// TODO: add impl here
assert(false);
}
@override
set keyLog(Function(String line)? callback) {
// TODO: add impl here
assert(false);
}
@override
bool get autoUncompress => _realClient.autoUncompress;
@override
set autoUncompress(bool value) => _realClient.autoUncompress = value;
@override
Duration? get connectionTimeout => _realClient.connectionTimeout;
@override
set connectionTimeout(Duration? value) =>
_realClient.connectionTimeout = value;
@override
Duration get idleTimeout => _realClient.idleTimeout;
@override
set idleTimeout(Duration value) => _realClient.idleTimeout = value;
@override
int? get maxConnectionsPerHost => _realClient.maxConnectionsPerHost;
@override
set maxConnectionsPerHost(int? value) =>
_realClient.maxConnectionsPerHost = value;
@override
String? get userAgent => _realClient.userAgent;
@override
set userAgent(String? value) => _realClient.userAgent = value;
@override
void addCredentials(
Uri url, String realm, HttpClientCredentials credentials) =>
_realClient.addCredentials(url, realm, credentials);
@override
void addProxyCredentials(String host, int port, String realm,
HttpClientCredentials credentials) =>
_realClient.addProxyCredentials(host, port, realm, credentials);
@override
set authenticate(
Future<bool> Function(Uri url, String scheme, String? realm)? f) =>
_realClient.authenticate = f;
@override
set authenticateProxy(
Future<bool> Function(
String host, int port, String scheme, String? realm)?
f) =>
_realClient.authenticateProxy = f;
@override
set badCertificateCallback(
bool Function(X509Certificate cert, String host, int port)?
callback) =>
_realClient.badCertificateCallback = callback;
@override
void close({bool force = false}) => _realClient.close(force: force);
@override
Future<HttpClientRequest> delete(String host, int port, String path) =>
_realClient.delete(host, port, path);
@override
Future<HttpClientRequest> deleteUrl(Uri url) => _realClient.deleteUrl(url);
@override
set findProxy(String Function(Uri url)? f) => _realClient.findProxy = f;
@override
Future<HttpClientRequest> get(String host, int port, String path) =>
_realClient.get(host, port, path);
@override
Future<HttpClientRequest> getUrl(Uri url) =>
_addCheck(_realClient.getUrl(url), 'get', url);
@override
Future<HttpClientRequest> head(String host, int port, String path) =>
_realClient.head(host, port, path);
@override
Future<HttpClientRequest> headUrl(Uri url) =>
_addCheck(_realClient.headUrl(url), 'head', url);
@override
Future<HttpClientRequest> patch(String host, int port, String path) =>
_realClient.patch(host, port, path);
@override
Future<HttpClientRequest> patchUrl(Uri url) =>
_addCheck(_realClient.patchUrl(url), 'patch', url);
@override
Future<HttpClientRequest> post(String host, int port, String path) =>
_realClient.post(host, port, path);
@override
Future<HttpClientRequest> postUrl(Uri url) =>
_addCheck(_realClient.postUrl(url), 'post', url);
@override
Future<HttpClientRequest> put(String host, int port, String path) =>
_realClient.put(host, port, path);
@override
Future<HttpClientRequest> putUrl(Uri url) =>
_addCheck(_realClient.putUrl(url), 'put', url);
@override
Future<HttpClientRequest> open(
String method, String host, int port, String path) {
const int hashMark = 0x23;
const int questionMark = 0x3f;
int fragmentStart = path.length;
int queryStart = path.length;
for (int i = path.length - 1; i >= 0; i--) {
final char = path.codeUnitAt(i);
if (char == hashMark) {
fragmentStart = i;
queryStart = i;
} else if (char == questionMark) {
queryStart = i;
}
}
String? query;
if (queryStart < fragmentStart) {
query = path.substring(queryStart + 1, fragmentStart);
path = path.substring(0, queryStart);
}
final Uri uri =
Uri(scheme: 'http', host: host, port: port, path: path, query: query);
return _addCheck(_realClient.open(method, host, port, path), method, uri);
}
@override
Future<HttpClientRequest> openUrl(String method, Uri url) =>
_addCheck(_realClient.openUrl(method, url), method, url);
Future<HttpClientRequest> _addCheck(
Future<HttpClientRequest> request, String method, Uri url) {
final host = AutoTrackConfigManager.instance.config.host;
if (host != null) {
final uploadUrl = Uri.parse(host);
if (uploadUrl.host == url.host && uploadUrl.path == url.path) {
return request;
}
}
final Stopwatch stopwatch = Stopwatch()..start();
final Page? pageInfoData = PageStack.instance.getCurrentPage();
return request
.then((HttpClientRequest request) =>
HttpClientRequestWithChecker(request, stopwatch, pageInfoData))
.catchError((dynamic error, dynamic stackTrace) {}, test: (error) {
String message = error.toString();
if (error is SocketException) {
message = error.message;
}
Track.instance.reportHttpRequest(RequestModel(
uri: url,
method: method,
pageId: pageInfoData?.pageInfo?.pageKey ?? "",
requestHeaders: null,
message: message,
status: -1,
spent: stopwatch.elapsedMilliseconds));
return false;
});
}
}
class _DefaultHttpOverrides extends HttpOverrides {}
class AutoTrackHttpOverrides extends HttpOverrides {
HttpOverrides? _currentOverrides;
AutoTrackHttpOverrides(this._currentOverrides) : super() {
_currentOverrides ??= _DefaultHttpOverrides();
}
@override
HttpClient createHttpClient(SecurityContext? context) {
return HttpClientWithChecker(_currentOverrides!.createHttpClient(context));
}
@override
String findProxyFromEnvironment(Uri url, Map<String, String>? environment) {
return _currentOverrides!.findProxyFromEnvironment(url, environment);
}
}
... ...
import 'package:dio/dio.dart';
import 'dart:io';
import 'package:flutter/widgets.dart';
typedef AutoTrackLoggerHandler = void Function(AutoTrackLoggerLevel level, String message);
... ... @@ -26,8 +27,8 @@ class AutoTrackLogger {
message = e.message;
} else if (e is Error) {
message = e.stackTrace.toString();
} else if (e is DioException) {
message = (e).message ?? 'dio exception with unknown message';
} else if (e is HttpException) {
message = e.message;
}
_print(AutoTrackLoggerLevel.error, message);
}
... ...
import 'package:auto_track/auto_track/config/queue.dart';
import 'package:auto_track/auto_track/drag/drag_info.dart';
import 'package:auto_track/auto_track/utils/error_model.dart';
import 'package:auto_track/auto_track/utils/request_model.dart';
import 'package:auto_track/auto_track/utils/track_model.dart';
import '../click/click_info.dart';
import '../config/manager.dart';
import '../listener/click/click_info.dart';
import '../listener/drag/drag_info.dart';
import '../listener/page_view/page_info.dart';
import '../log/logger.dart';
import '../page_view/page_info.dart';
class Track {
static final Track instance = Track._();
... ... @@ -104,7 +105,14 @@ class Track {
}
void reportError(Object error, StackTrace stack) {
_TrackPlugin.customEvent('error', ErrorModel(error: error, stack: stack).toMap());
final model = ErrorModel(error: error, stack: stack);
_TrackPlugin.customEvent('error', model.toMap());
AutoTrackLogger.getInstance().debug('track error => ${model.toMap()}');
}
void reportHttpRequest(RequestModel requestModel) {
_TrackPlugin.customEvent('http', requestModel.toMap(), key: requestModel.uri.path);
AutoTrackLogger.getInstance().debug('track request => ${requestModel.toMap()}');
}
}
... ... @@ -128,8 +136,8 @@ class _TrackPlugin {
AutoTrackQueue.instance.appendQueue(model);
}
static void customEvent(String type, Map<String, dynamic> params) {
var model = TrackModel(type, DateTime.now().millisecondsSinceEpoch, params, params['key'] ?? type);
static void customEvent(String type, Map<String, dynamic> params, { String? key }) {
var model = TrackModel(type, DateTime.now().millisecondsSinceEpoch, params, params['key'] ?? key ?? type);
AutoTrackConfigManager.instance.config.eventHandler?.call(model);
AutoTrackQueue.instance.appendQueue(model);
}
... ...
class RequestModel {
RequestModel({
required this.uri,
required this.method,
required this.message,
required this.status,
required this.spent,
required this.pageId,
this.requestHeaders,
this.responseHeaders,
});
Uri uri;
String method;
String message;
String pageId;
int status;
int spent;
dynamic requestHeaders;
dynamic responseHeaders;
Map<String, dynamic> toMap() {
return {
'uri': uri.toString(),
'method': method,
'message': message,
'pageId': pageId,
'status': status,
'spent': spent,
'requestHeaders': requestHeaders.toString(),
'responseHeaders': responseHeaders.toString(),
};
}
}
\ No newline at end of file
... ...
name: auto_track
description: "Auto Track Plugin"
version: 0.0.6
description: "Low-intrusion global automatic embedding, automatically recording events such as page entry, exit, clicks, scrolling, and HTTP requests, and supporting custom events."
version: 0.0.7
homepage: https://github.com/epoll-j/auto_track_plugin
environment:
... ... @@ -10,7 +10,6 @@ environment:
dependencies:
crypto: ^3.0.3
device_info_plus: ^9.1.2
dio: ^5.4.1
flutter:
sdk: flutter
package_info_plus: ^8.0.1
... ...