diff --git a/.github/workflows/ci-dio-interceptor.yml b/.github/workflows/ci-dio-interceptor.yml
new file mode 100644
index 00000000..f62e139b
--- /dev/null
+++ b/.github/workflows/ci-dio-interceptor.yml
@@ -0,0 +1,43 @@
+name: CI - Dio Interceptor
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - "packages/posthog_dio_interceptor/**"
+ - ".github/workflows/ci-dio-interceptor.yml"
+ pull_request:
+ paths:
+ - "packages/posthog_dio_interceptor/**"
+ - ".github/workflows/ci-dio-interceptor.yml"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: dart-lang/setup-dart@v1
+ with:
+ sdk: stable
+
+ - name: Install dependencies
+ run: |
+ dart pub get
+ working-directory: ./packages/posthog_dio_interceptor
+
+ - name: Check format
+ run: |
+ dart format --set-exit-if-changed ./
+ working-directory: ./packages/posthog_dio_interceptor
+
+ - name: Analyze
+ run: |
+ dart analyze .
+ working-directory: ./packages/posthog_dio_interceptor
+
+ - name: Run tests
+ run: |
+ dart test
+ working-directory: ./packages/posthog_dio_interceptor
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b903a59a..3fd8b8b7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,9 +3,17 @@ on:
push:
branches:
- main
+ paths:
+ - "packages/posthog_flutter/**"
+ - ".github/workflows/ci.yml"
+ - "Makefile"
+ - "ktlint-baseline.xml"
pull_request:
- paths-ignore:
- - "**/*.md"
+ paths:
+ - "packages/posthog_flutter/**"
+ - ".github/workflows/ci.yml"
+ - "Makefile"
+ - "ktlint-baseline.xml"
# Publish using custom workflow
jobs:
@@ -35,6 +43,7 @@ jobs:
flutter pub get
cd example
flutter pub get
+ working-directory: ./packages/posthog_flutter
- name: SDK format check
run: |
@@ -43,22 +52,25 @@ jobs:
make analyzeDart
make formatKotlin
make formatSwift
+ working-directory: ./packages/posthog_flutter
- name: Test
- run: flutter test
+ run: |
+ flutter test
+ working-directory: ./packages/posthog_flutter
- name: Build iOS
- working-directory: ./example
+ working-directory: ./packages/posthog_flutter/example
run: flutter build ios --simulator --no-codesign
- name: Build macOS
- working-directory: ./example
+ working-directory: ./packages/posthog_flutter/example
run: flutter build macos
- name: Build Android
- working-directory: ./example
+ working-directory: ./packages/posthog_flutter/example
run: flutter build apk
- name: Build Web
- working-directory: ./example
+ working-directory: ./packages/posthog_flutter/example
run: flutter build web
diff --git a/.github/workflows/publish-dio-interceptor.yml b/.github/workflows/publish-dio-interceptor.yml
new file mode 100644
index 00000000..60083914
--- /dev/null
+++ b/.github/workflows/publish-dio-interceptor.yml
@@ -0,0 +1,28 @@
+name: Publish Dio Interceptor to pub.dev
+
+on:
+ push:
+ tags:
+ - 'dio-v[0-9]+.[0-9]+.[0-9]+*' # tag pattern for dio interceptor: eg 'dio-v1.0.0'
+
+jobs:
+ publish:
+ permissions:
+ id-token: write # Required for authentication using OIDC
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: dart-lang/setup-dart@v1
+ with:
+ sdk: stable
+
+ - name: Install dependencies
+ run: |
+ dart pub get
+ working-directory: ./packages/posthog_dio_interceptor
+
+ - name: Publish
+ run: |
+ dart pub publish --force
+ working-directory: ./packages/posthog_dio_interceptor
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 36a5826a..43c2d584 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -20,6 +20,10 @@ jobs:
with:
channel: 'stable'
- name: Install dependencies
- run: flutter pub get
+ run: |
+ flutter pub get
+ working-directory: ./packages/posthog_flutter
- name: Publish
- run: flutter pub publish --force
+ run: |
+ flutter pub publish --force
+ working-directory: ./packages/posthog_flutter
diff --git a/.gitignore b/.gitignore
index c41c2fd2..94bb00ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -461,7 +461,7 @@ PublishScripts/
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
+**/Packages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
diff --git a/Makefile b/Makefile
index 5a0475c8..766a1f88 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ formatKotlin:
# swiftlint ios/Classes --fix conflicts with swiftformat
formatSwift:
- swiftformat ios/Classes --swiftversion 5.3
+ swiftformat packages/posthog_flutter/ios/Classes --swiftversion 5.3
formatDart:
dart format .
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
deleted file mode 100644
index 399f6981..00000000
--- a/example/android/app/src/profile/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/packages/posthog_dio_interceptor/.gitignore b/packages/posthog_dio_interceptor/.gitignore
new file mode 100644
index 00000000..3cceda55
--- /dev/null
+++ b/packages/posthog_dio_interceptor/.gitignore
@@ -0,0 +1,7 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
+
+# Avoid committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
diff --git a/packages/posthog_dio_interceptor/CHANGELOG.md b/packages/posthog_dio_interceptor/CHANGELOG.md
new file mode 100644
index 00000000..effe43c8
--- /dev/null
+++ b/packages/posthog_dio_interceptor/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/packages/posthog_dio_interceptor/README.md b/packages/posthog_dio_interceptor/README.md
new file mode 100644
index 00000000..8831761b
--- /dev/null
+++ b/packages/posthog_dio_interceptor/README.md
@@ -0,0 +1,39 @@
+
+
+TODO: Put a short description of the package here that helps potential users
+know whether this package might be useful for them.
+
+## Features
+
+TODO: List what your package can do. Maybe include images, gifs, or videos.
+
+## Getting started
+
+TODO: List prerequisites and provide or point to information on how to
+start using the package.
+
+## Usage
+
+TODO: Include short and useful examples for package users. Add longer examples
+to `/example` folder.
+
+```dart
+const like = 'sample';
+```
+
+## Additional information
+
+TODO: Tell users more about the package: where to find more information, how to
+contribute to the package, how to file issues, what response they can expect
+from the package authors, and more.
diff --git a/packages/posthog_dio_interceptor/analysis_options.yaml b/packages/posthog_dio_interceptor/analysis_options.yaml
new file mode 100644
index 00000000..71204100
--- /dev/null
+++ b/packages/posthog_dio_interceptor/analysis_options.yaml
@@ -0,0 +1,18 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+linter:
+ rules:
+ unnecessary_library_name: false
\ No newline at end of file
diff --git a/packages/posthog_dio_interceptor/lib/posthog_dio_interceptor.dart b/packages/posthog_dio_interceptor/lib/posthog_dio_interceptor.dart
new file mode 100644
index 00000000..4b2fc60f
--- /dev/null
+++ b/packages/posthog_dio_interceptor/lib/posthog_dio_interceptor.dart
@@ -0,0 +1,6 @@
+/// Support for doing something awesome.
+///
+/// More dartdocs go here.
+library;
+
+export 'src/dio_interceptor.dart';
diff --git a/packages/posthog_dio_interceptor/lib/src/dio_interceptor.dart b/packages/posthog_dio_interceptor/lib/src/dio_interceptor.dart
new file mode 100644
index 00000000..8eb20973
--- /dev/null
+++ b/packages/posthog_dio_interceptor/lib/src/dio_interceptor.dart
@@ -0,0 +1,200 @@
+import 'dart:convert';
+
+import 'package:dio/dio.dart';
+import 'package:flutter/foundation.dart';
+import 'package:posthog_flutter/posthog_flutter.dart';
+
+/// A Dio interceptor that captures network events and sends them to PostHog.
+class PostHogDioInterceptor extends Interceptor {
+ final Posthog _posthog = Posthog();
+ final bool attachPayloads;
+
+ static const int _oneMbInBytes = 1024 * 1024;
+
+ PostHogDioInterceptor({
+ this.attachPayloads = false,
+ });
+
+ @override
+ Future onResponse(
+ Response response,
+ ResponseInterceptorHandler handler,
+ ) async {
+ super.onResponse(response, handler);
+
+ final isSessionReplayActive = await _posthog.isSessionReplayActive();
+ if (isSessionReplayActive) {
+ await _captureNetworkEvent(
+ response: response,
+ );
+ }
+ }
+
+ @override
+ Future onError(
+ DioException err,
+ ErrorInterceptorHandler handler,
+ ) async {
+ super.onError(err, handler);
+
+ final isSessionReplayActive = await _posthog.isSessionReplayActive();
+ if (isSessionReplayActive) {
+ final Response? response = err.response;
+ if (response != null) {
+ await _captureNetworkEvent(response: response);
+ }
+ }
+ }
+
+ Future _captureNetworkEvent({
+ required Response response,
+ }) async {
+ final String url = response.requestOptions.uri.toString();
+ final String method = response.requestOptions.method;
+ final int statusCode = response.statusCode ?? 0;
+ final [
+ (Object? publishableRequest, int requestSizeLimit),
+ (Object? publishableResponse, int responseSizeLimit),
+ ] = attachPayloads
+ ? await Future.wait([
+ _tryTransformDataToPublishableObject(
+ data: response.requestOptions.data,
+ ).then(
+ (value) async {
+ final sizeLimit = await _calculateSizeLimit(
+ data: response.requestOptions.data,
+ header: response.requestOptions.headers,
+ );
+ return (
+ value,
+ sizeLimit,
+ );
+ },
+ ),
+ _tryTransformDataToPublishableObject(
+ data: response.data,
+ ).then(
+ (value) async {
+ final sizeLimit = await _calculateSizeLimit(
+ data: response.data,
+ header: response.headers.map,
+ );
+ return (
+ value,
+ sizeLimit,
+ );
+ },
+ ),
+ ])
+ : [
+ (null, 0),
+ (null, 0),
+ ];
+
+ final Map snapshotData = {
+ 'type': 6,
+ 'data': {
+ 'plugin': 'rrweb/network@1',
+ 'payload': {
+ 'url': url,
+ 'method': method,
+ 'status_code': statusCode,
+ if (requestSizeLimit + responseSizeLimit <= _oneMbInBytes) ...{
+ if (publishableRequest != null) 'request': publishableRequest,
+ if (publishableResponse != null) 'response': publishableResponse,
+ }
+ },
+ },
+ 'timestamp': DateTime.now().millisecondsSinceEpoch,
+ };
+ Posthog().capture(
+ eventName: r'$snapshot',
+ properties: {
+ r'$snapshot_source': 'mobile',
+ r'$snapshot_data': snapshotData,
+ },
+ );
+ }
+
+ Future _calculateSizeLimit({
+ required dynamic data,
+ required Map header,
+ }) async {
+ final contentLengthHeader = header['content-length'];
+ final contentLength = _deriveContentLength(contentLengthHeader);
+ if (contentLength != null) {
+ return contentLength;
+ }
+
+ if (data == null) {
+ return 0;
+ }
+
+ if (data is bool) {
+ return 4;
+ }
+
+ if (data is num) {
+ return 8;
+ }
+
+ try {
+ final encodedData =
+ await compute((data) => utf8.encode(jsonEncode(data)), data);
+ return encodedData.length;
+ } catch (e) {
+ // Since we couldn't serialize the data, assume it exceeds the limit.
+ return _oneMbInBytes + 1;
+ }
+ }
+
+ int? _deriveContentLength(dynamic contentLengthHeader) {
+ if (contentLengthHeader == null) {
+ return null;
+ }
+
+ if (contentLengthHeader is Iterable) {
+ return int.tryParse(contentLengthHeader.first);
+ }
+
+ return int.tryParse(contentLengthHeader.toString());
+ }
+
+ Future