diff --git a/.github/workflows/talker_http_logger.yaml b/.github/workflows/talker_http_logger.yaml new file mode 100644 index 000000000..1f70f20ea --- /dev/null +++ b/.github/workflows/talker_http_logger.yaml @@ -0,0 +1,20 @@ +name: talker_http_logger + +on: + push: + paths: + - "packages/talker_http_logger/**" + - ".github/workflows/talker_http_logger.yaml" + pull_request: + paths: + - "packages/talker_http_logger/**" + - ".github/workflows/talker_http_logger.yaml" +permissions: + contents: read + +jobs: + test: + uses: ./.github/workflows/test.yaml + with: + package: talker_http_logger + sdk: dart diff --git a/docs/assets/talker_http_logger/preview.png b/docs/assets/talker_http_logger/preview.png new file mode 100644 index 000000000..2b57ee3aa Binary files /dev/null and b/docs/assets/talker_http_logger/preview.png differ diff --git a/packages/talker_http_logger/CHANGELOG.md b/packages/talker_http_logger/CHANGELOG.md index d992c2bd5..a4aaff268 100644 --- a/packages/talker_http_logger/CHANGELOG.md +++ b/packages/talker_http_logger/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.0.0 +- Complete overhaul of the package + +Thanks to [techouse](https://github.com/techouse) + # 0.1.0-dev.55 - [talker_riverpod_logger] add family arguments to the logs @@ -6,8 +11,6 @@ Thanks to [lucavenir](https://github.com/lucavenir) # 0.1.0-dev.54 - [talker_riverpod_logger] add an error filter -Thanks to [lucavenir](https://github.com/lucavenir) - # 0.1.0-dev.53 - [talker] Add `CustomLog` Documentation - [example] Update plugin com.android.application to v8.10.1 diff --git a/packages/talker_http_logger/Makefile b/packages/talker_http_logger/Makefile new file mode 100644 index 000000000..fc06314a7 --- /dev/null +++ b/packages/talker_http_logger/Makefile @@ -0,0 +1,61 @@ +# Makefile + +help: + @printf "%-20s %s\n" "Target" "Description" + @printf "%-20s %s\n" "------" "-----------" + @make -pqR : 2>/dev/null \ + | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \ + | sort \ + | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ + | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' + +analyze: + @# Help: Analyze the project's Dart code. + dart analyze --fatal-infos + +check_format: + @# Help: Check the formatting of one or more Dart files. + dart format --output=none --set-exit-if-changed . + +check_outdated: + @# Help: Check which of the project's packages are outdated. + dart pub outdated + +check_style: + @# Help: Analyze the project's Dart code and check the formatting one or more Dart files. + make analyze && make check_format + +code_gen: + @# Help: Run the build system for Dart code generation and modular compilation. + dart run build_runner build --delete-conflicting-outputs + +code_gen_watcher: + @# Help: Run the build system for Dart code generation and modular compilation as a watcher. + dart run build_runner watch --delete-conflicting-outputs + +format: + @# Help: Format one or more Dart files. + dart format . + +install: + @# Help: Install all the project's packages + dart pub get + +sure: + @# Help: Analyze the project's Dart code, check the formatting one or more Dart files and run unit tests for the current project. + make check_style && make tests + +show_test_coverage: + @# Help: Run Dart unit tests for the current project and show the coverage. + dart pub global activate coverage && dart pub global run coverage:test_with_coverage + lcov --remove coverage/lcov.info '**.g.dart' '**.mock.dart' -o coverage/lcov_without_generated_code.info --ignore-errors unused + genhtml coverage/lcov_without_generated_code.info -o coverage/html + source tool/makefile_helpers.sh && open_link "coverage/html/index.html" + +tests: + @# Help: Run Dart unit and widget tests for the current project. + dart test + +upgrade: + @# Help: Upgrade all the project's packages. + dart pub upgrade diff --git a/packages/talker_http_logger/README.md b/packages/talker_http_logger/README.md index 6d92b6451..e235a2f81 100644 --- a/packages/talker_http_logger/README.md +++ b/packages/talker_http_logger/README.md @@ -1,5 +1,5 @@ # talker_http_logger -Lightweight and customizable [http](https://pub.dev/packages/http) client logger on [talker](https://pub.dev/packages/talker) base.
+Lightweight and customizable [http_interceptor](https://pub.dev/packages/http_interceptor) client logger on [talker](https://pub.dev/packages/talker) base.
[Talker](https://github.com/Frezyx/talker) - Advanced exception handling and logging for dart/flutter applications 🚀

@@ -15,7 +15,7 @@ Lightweight and customizable [http](https://pub.dev/packages/http) client logger ## Preview This is how the logs of your http requests will look in the console -![](https://github.com/Frezyx/talker/blob/dev/docs/assets/talker_http_logger/preview.png?raw=true) +![](/docs/assets/talker_http_logger/preview.png?raw=true) ## Getting started Follow these steps to use this package @@ -23,11 +23,11 @@ Follow these steps to use this package ### Add dependency ```yaml dependencies: - talker_http_logger: ^0.1.0-dev.55 + talker_http_logger: ^1.0.0 ``` ### Usage -Just add **TalkerHttpLogger** to your **InterceptedClient** instance and it will work +Just add **TalkerHttpLogger** to your [**InterceptedClient**](https://pub.dev/packages/http_interceptor) instance and it will work ```dart import 'package:http_interceptor/http_interceptor.dart'; @@ -35,7 +35,13 @@ import 'package:talker_http_logger/talker_http_logger.dart'; void main() async { final client = InterceptedClient.build(interceptors: [ - TalkerHttpLogger(), + TalkerHttpLogger( + settings: const TalkerHttpLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + printResponseMessage: true, + ), + ), ]); await client.get("https://google.com".toUri()); @@ -52,13 +58,92 @@ import 'package:http_interceptor/http_interceptor.dart'; import 'package:talker_http_logger/talker_http_logger.dart'; void main() async { - final talker = Talker(); - final client = InterceptedClient.build(interceptors: [ - TalkerHttpLogger(talker: talker), - ]); + final _talker = Talker(); + final client = InterceptedClient.build( + /// ... other settings + interceptors: [ + TalkerHttpLogger( + /// ... other Talker HTTP Logger settings + talker: _talker, + ), + ] + ); await client.get("https://google.com".toUri()); -} +``` + +### Print HTTP request curl command + +You can print the curl command for the HTTP request in the console. +This is useful for debugging and testing purposes. + +```dart +final client = InterceptedClient.build( + /// ... other settings + interceptors: [ + TalkerHttpLogger( + talker: _talker, + settings: const TalkerHttpLoggerSettings( + // Print curl command for HTTP request + printRequestCurl: true, + ), + ), + ], +); +``` + +### Hiding sensitive HTTP request headers + +You can hide sensitive HTTP request headers such as `Authorization` or `Cookie` in the console logs. +This is useful for security purposes. + +```dart +final client = InterceptedClient( + /// ... other settings + interceptors: [ + TalkerHttpLogger( + talker: _talker, + settings: const TalkerHttpLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + // Hide sensitive HTTP request headers + hiddenHeaders: { + 'authorization', + 'cookie', + }, + ), + ), + ], +); +``` + +### Change HTTP logs colors + +Customize your HTTP log colors by defining specific colors for requests, responses, and errors in +[TalkerHttpLoggerSettings](lib/talker_http_logger_settings.dart) + +```dart +TalkerHttpLoggerSettings( + // Blue HTTP requests logs in console + requestPen: AnsiPen()..blue(), + // Green HTTP responses logs in console + responsePen: AnsiPen()..green(), + // Error HTTP logs in console + errorPen: AnsiPen()..red(), +); +``` + +### Filter HTTP logs + +For instance, if your app includes private functionality that you prefer not to log with talker, you can apply filters. + +```dart +TalkerHttpLoggerSettings( + // All http requests without "/secure" in path will be printed in console + requestFilter: (Request request) => !request.url.path.contains('/secure'), + // All http responses with status codes different than 301 will be printed in console + responseFilter: (Response response) => response.statusCode != 301, +) ``` ## Additional information diff --git a/packages/talker_http_logger/example/.gitignore b/packages/talker_http_logger/example/.gitignore new file mode 100644 index 000000000..3d3d8995a --- /dev/null +++ b/packages/talker_http_logger/example/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +pubspec.lock + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/talker_http_logger/example/.metadata b/packages/talker_http_logger/example/.metadata new file mode 100644 index 000000000..79f857910 --- /dev/null +++ b/packages/talker_http_logger/example/.metadata @@ -0,0 +1,36 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ea121f8859e4b13e47a8f845e4586164519588bc" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: android + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: ios + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: web + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/talker_http_logger/example/README.md b/packages/talker_http_logger/example/README.md new file mode 100644 index 000000000..08a48ac8a --- /dev/null +++ b/packages/talker_http_logger/example/README.md @@ -0,0 +1,34 @@ +# Example Project + +This example demonstrates the usage of the talker_http_logger package for logging HTTP requests, responses, and +errors. + +## Setup + +Install dependencies: + +```shell +flutter pub get +``` + +## Running the Example + +To run the example application: + +```shell +flutter run +``` + +## Directory Structure + +Within the example directory you will typically find: + +- A main entry point that demonstrates the logging functionality. +- Supporting files or configurations that illustrate how logging is customized using the package's settings. + +## Customization + +- Adjust logging settings directly within the example code to explore options such as: + - Printing request headers and data. + - Displaying cURL commands to replicate HTTP requests. + - Customizing the output format of errors, responses, and request logs. diff --git a/packages/talker_http_logger/example/analysis_options.yaml b/packages/talker_http_logger/example/analysis_options.yaml new file mode 100644 index 000000000..0bda3e79b --- /dev/null +++ b/packages/talker_http_logger/example/analysis_options.yaml @@ -0,0 +1,11 @@ +analyzer: + exclude: + - "lib/generated_plugin_registrant.dart" + - "**.mocks.dart" + - "**.gen.dart" + +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - require_trailing_commas diff --git a/packages/talker_http_logger/example/android/.gitignore b/packages/talker_http_logger/example/android/.gitignore new file mode 100644 index 000000000..be3943c96 --- /dev/null +++ b/packages/talker_http_logger/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/packages/talker_http_logger/example/android/app/build.gradle.kts b/packages/talker_http_logger/example/android/app/build.gradle.kts new file mode 100644 index 000000000..d77dd1aa0 --- /dev/null +++ b/packages/talker_http_logger/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = "27.0.12077973" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/talker_http_logger/example/android/app/src/debug/AndroidManifest.xml b/packages/talker_http_logger/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/talker_http_logger/example/android/app/src/main/AndroidManifest.xml b/packages/talker_http_logger/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..74a78b939 --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/talker_http_logger/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/talker_http_logger/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..ac81bae64 --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/talker_http_logger/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/talker_http_logger/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/talker_http_logger/example/android/app/src/main/res/drawable/launch_background.xml b/packages/talker_http_logger/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/talker_http_logger/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/talker_http_logger/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/talker_http_logger/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/talker_http_logger/example/android/app/src/main/res/values-night/styles.xml b/packages/talker_http_logger/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/talker_http_logger/example/android/app/src/main/res/values/styles.xml b/packages/talker_http_logger/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/talker_http_logger/example/android/app/src/profile/AndroidManifest.xml b/packages/talker_http_logger/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/talker_http_logger/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/talker_http_logger/example/android/build.gradle.kts b/packages/talker_http_logger/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/talker_http_logger/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/talker_http_logger/example/android/gradle.properties b/packages/talker_http_logger/example/android/gradle.properties new file mode 100644 index 000000000..f018a6181 --- /dev/null +++ b/packages/talker_http_logger/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/talker_http_logger/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/talker_http_logger/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..afa1e8eb0 --- /dev/null +++ b/packages/talker_http_logger/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/talker_http_logger/example/android/settings.gradle.kts b/packages/talker_http_logger/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/talker_http_logger/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/talker_http_logger/example/ios/.gitignore b/packages/talker_http_logger/example/ios/.gitignore new file mode 100644 index 000000000..993c497d7 --- /dev/null +++ b/packages/talker_http_logger/example/ios/.gitignore @@ -0,0 +1,36 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 + +Podfile.lock diff --git a/packages/talker_http_logger/example/ios/Flutter/AppFrameworkInfo.plist b/packages/talker_http_logger/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..7c5696400 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/packages/talker_http_logger/example/ios/Flutter/Debug.xcconfig b/packages/talker_http_logger/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/talker_http_logger/example/ios/Flutter/Release.xcconfig b/packages/talker_http_logger/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/talker_http_logger/example/ios/Podfile b/packages/talker_http_logger/example/ios/Podfile new file mode 100644 index 000000000..e549ee22f --- /dev/null +++ b/packages/talker_http_logger/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.pbxproj b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..166d90387 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,731 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0349975BD96070230C2ECB21 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EDD2E4EED1632F8AE49946CE /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5B8111CFC21719E178446F90 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57157F71654DDC088CF06FBE /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 57157F71654DDC088CF06FBE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 58D6CBE4547B3036C6252C06 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 5F637F1C7C8541B999CB5454 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 63282D6269B8F68246D76081 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 73F049D3BB980382AD75683A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 89B6B8A988902DB1E2E01E1F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E2DB985429F6CC0766642994 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + EDD2E4EED1632F8AE49946CE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0349975BD96070230C2ECB21 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBAE7A6ED3AD0C8F29996905 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B8111CFC21719E178446F90 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 590A0B4D6205CA39BE159923 /* Pods */ = { + isa = PBXGroup; + children = ( + E2DB985429F6CC0766642994 /* Pods-Runner.debug.xcconfig */, + 73F049D3BB980382AD75683A /* Pods-Runner.release.xcconfig */, + 89B6B8A988902DB1E2E01E1F /* Pods-Runner.profile.xcconfig */, + 5F637F1C7C8541B999CB5454 /* Pods-RunnerTests.debug.xcconfig */, + 58D6CBE4547B3036C6252C06 /* Pods-RunnerTests.release.xcconfig */, + 63282D6269B8F68246D76081 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 6A79C0DF6F3EB005401B55E4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + EDD2E4EED1632F8AE49946CE /* Pods_Runner.framework */, + 57157F71654DDC088CF06FBE /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 590A0B4D6205CA39BE159923 /* Pods */, + 6A79C0DF6F3EB005401B55E4 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + F2EBB17EDE09C97A3C31A953 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + FBAE7A6ED3AD0C8F29996905 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + A93AFDABF02DCF34B8C34490 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 929F1C8D4C242F96E65A93DF /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 929F1C8D4C242F96E65A93DF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + A93AFDABF02DCF34B8C34490 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F2EBB17EDE09C97A3C31A953 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6LYC36B94Q; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5F637F1C7C8541B999CB5454 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 58D6CBE4547B3036C6252C06 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63282D6269B8F68246D76081 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6LYC36B94Q; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 6LYC36B94Q; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/talker_http_logger/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/talker_http_logger/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..15cada483 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/talker_http_logger/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/talker_http_logger/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/talker_http_logger/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/talker_http_logger/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/talker_http_logger/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/talker_http_logger/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/talker_http_logger/example/ios/Runner/AppDelegate.swift b/packages/talker_http_logger/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..626664468 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..7353c41ec Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..6ed2d933e Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cd7b0099 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..fe730945a Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..321773cd8 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..502f463a9 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..e9f5fea27 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..84ac32ae7 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..8953cba09 Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..0467bf12a Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/talker_http_logger/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/talker_http_logger/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/talker_http_logger/example/ios/Runner/Base.lproj/Main.storyboard b/packages/talker_http_logger/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/talker_http_logger/example/ios/Runner/Info.plist b/packages/talker_http_logger/example/ios/Runner/Info.plist new file mode 100644 index 000000000..5458fc418 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/talker_http_logger/example/ios/Runner/Runner-Bridging-Header.h b/packages/talker_http_logger/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/talker_http_logger/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/talker_http_logger/example/ios/RunnerTests/RunnerTests.swift b/packages/talker_http_logger/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/talker_http_logger/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/talker_http_logger/example/lib/main.dart b/packages/talker_http_logger/example/lib/main.dart new file mode 100644 index 000000000..beff9b4f3 --- /dev/null +++ b/packages/talker_http_logger/example/lib/main.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:http_interceptor/http/intercepted_client.dart'; +import 'package:talker_flutter/talker_flutter.dart'; +import 'package:talker_http_logger/talker_http_logger_interceptor.dart'; +import 'package:talker_http_logger/talker_http_logger_settings.dart'; + +void main() { + runApp(MaterialApp(home: const MyApp())); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + final Talker _talker = Talker(); + + late final InterceptedClient client = InterceptedClient.build( + interceptors: [ + TalkerHttpLogger( + talker: _talker, + settings: TalkerHttpLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + hiddenHeaders: {'authorization'}, + ), + ), + ], + ); + + late final List<({String title, VoidCallback onPressed})> _buttons = [ + ( + title: 'GET todo request', + onPressed: () => client.get( + Uri.https("jsonplaceholder.typicode.com", "/todos/1"), + headers: { + "firstHeader": "firstHeaderValue", + "authorization": "bearer super_secret_token", + "lastHeader": "lastHeaderValue", + }, + ), + ), + ( + title: 'GET todos list request', + onPressed: () => + client.get(Uri.https("jsonplaceholder.typicode.com", "/todos")), + ), + ( + title: 'POST todo request', + onPressed: () => client.post( + Uri.https("jsonplaceholder.typicode.com", "/todos"), + body: { + "userId": '1', + "id": '1', + "title": "delectus aut autem", + "completed": 'false', + }, + ), + ), + ( + title: 'PUT todo request', + onPressed: () => client.put( + Uri.https("jsonplaceholder.typicode.com", "/todos/1"), + body: { + "userId": '1', + "id": '1', + "title": "delectus aut autem", + "completed": 'false', + }, + ), + ), + ( + title: 'DELETE todo request', + onPressed: () => + client.delete(Uri.https("jsonplaceholder.typicode.com", "/todos/1")), + ), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text('Talker + Http Example'), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: ListView( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 24, + ), + children: [ + const Text( + 'Check result in console or open Talker Screen', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + for (final ({String title, VoidCallback onPressed}) button + in _buttons) + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(48), + ), + onPressed: button.onPressed, + child: Text(button.title), + ), + ), + const SizedBox(height: 24), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.amber, + foregroundColor: Colors.black, + minimumSize: const Size.fromHeight(48), + ), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen( + talker: _talker, + isLogsExpanded: true, + isLogOrderReversed: true, + ), + ), + ); + }, + child: Text('Go to Talker Screen'), + ), + ], + ), + ); + } +} diff --git a/packages/talker_http_logger/example/pubspec.yaml b/packages/talker_http_logger/example/pubspec.yaml new file mode 100644 index 000000000..0465abf2c --- /dev/null +++ b/packages/talker_http_logger/example/pubspec.yaml @@ -0,0 +1,27 @@ +name: example +description: "Talker + Http Logger Example" +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ^3.0.0 + +dependencies: + flutter: + sdk: flutter + http_interceptor: ^2.0.0 + talker_http_logger: + talker_flutter: + equatable: ^2.0.7 + +dev_dependencies: + flutter_lints: ^5.0.0 + +dependency_overrides: + talker_http_logger: + path: .. + talker_flutter: + path: ../../talker_flutter + +flutter: + uses-material-design: true diff --git a/packages/talker_http_logger/example/talker_http_logger_example.dart b/packages/talker_http_logger/example/talker_http_logger_example.dart deleted file mode 100644 index 75ff2dd3c..000000000 --- a/packages/talker_http_logger/example/talker_http_logger_example.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:http_interceptor/http_interceptor.dart'; -import 'package:talker_http_logger/talker_http_logger.dart'; -import 'package:talker_http_logger/talker_http_logger_settings.dart'; - -void main(List args) async { - final client = InterceptedClient.build(interceptors: [ - TalkerHttpLogger( - settings: TalkerHttpLoggerSettings( - hiddenHeaders: {'Authorization'})), - ]); - - await client.get("https://google.com".toUri(), headers: { - "firstHeader": "firstHeaderValue", - "authorization": "bearer super_secret_token", - "lastHeader": "lastHeaderValue", - }); -} diff --git a/packages/talker_http_logger/example/web/favicon.png b/packages/talker_http_logger/example/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/packages/talker_http_logger/example/web/favicon.png differ diff --git a/packages/talker_http_logger/example/web/icons/Icon-192.png b/packages/talker_http_logger/example/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/packages/talker_http_logger/example/web/icons/Icon-192.png differ diff --git a/packages/talker_http_logger/example/web/icons/Icon-512.png b/packages/talker_http_logger/example/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/packages/talker_http_logger/example/web/icons/Icon-512.png differ diff --git a/packages/talker_http_logger/example/web/icons/Icon-maskable-192.png b/packages/talker_http_logger/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/talker_http_logger/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/talker_http_logger/example/web/icons/Icon-maskable-512.png b/packages/talker_http_logger/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/talker_http_logger/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/talker_http_logger/example/web/index.html b/packages/talker_http_logger/example/web/index.html new file mode 100644 index 000000000..29b58086b --- /dev/null +++ b/packages/talker_http_logger/example/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + diff --git a/packages/talker_http_logger/example/web/manifest.json b/packages/talker_http_logger/example/web/manifest.json new file mode 100644 index 000000000..096edf8fe --- /dev/null +++ b/packages/talker_http_logger/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/talker_http_logger/lib/curl_request.dart b/packages/talker_http_logger/lib/curl_request.dart new file mode 100644 index 000000000..323055921 --- /dev/null +++ b/packages/talker_http_logger/lib/curl_request.dart @@ -0,0 +1,43 @@ +import 'dart:convert' show JsonEncoder, jsonDecode; + +import 'package:http_interceptor/http_interceptor.dart'; + +extension CurlRequest on BaseRequest { + static const JsonEncoder _encoder = JsonEncoder(); + + /// Converts the request to a curl command string. + String toCurl({Map? headers}) { + final List curl = ['curl', '-v', '-X', method]; + + curl.addAll((headers ?? this.headers) + .entries + .map((e) => "-H ${_q('${e.key}: ${e.value}')}")); + + switch (this) { + case Request req when req.body.isNotEmpty: + try { + curl.addAll(['-d', _q(_encoder.convert(jsonDecode(req.body)))]); + } on FormatException { + curl.addAll(['-d', _q(req.body)]); + } + break; + case MultipartRequest req: + for (final MapEntry field in req.fields.entries) { + curl.addAll(['-F', _q('${field.key}=${field.value}')]); + } + for (final MultipartFile file in req.files) { + curl.addAll(['-F', _q('${file.field}=@${file.filename}')]); + } + break; + default: + break; + } + + curl.add(_q(url.toString())); + + return curl.join(' '); + } + + /// single-quoting with sane escaping of `'` + String _q(String s) => "'${s.replaceAll("'", r"'\''")}'"; +} diff --git a/packages/talker_http_logger/lib/http_error_log.dart b/packages/talker_http_logger/lib/http_error_log.dart new file mode 100644 index 000000000..180066246 --- /dev/null +++ b/packages/talker_http_logger/lib/http_error_log.dart @@ -0,0 +1,112 @@ +import 'dart:convert' show JsonEncoder, jsonDecode; + +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:meta/meta.dart'; +import 'package:talker/talker.dart'; +import 'package:talker_http_logger/response_time.dart'; +import 'package:talker_http_logger/talker_http_logger_settings.dart'; + +class HttpErrorLog extends TalkerLog with ResponseTime { + HttpErrorLog( + super.title, { + this.request, + this.response, + required this.settings, + super.exception, + super.stackTrace, + }); + + final BaseRequest? request; + final BaseResponse? response; + final TalkerHttpLoggerSettings settings; + + static const JsonEncoder _encoder = JsonEncoder.withIndent(' '); + + @override + AnsiPen get pen => settings.errorPen ?? (AnsiPen()..red()); + + @override + String get key => TalkerLogType.httpError.key; + + @override + LogLevel get logLevel => LogLevel.error; + + @visibleForTesting + String convert(Object? object) => _encoder.convert(object); + + @override + String generateTextMessage({ + TimeFormat timeFormat = TimeFormat.timeAndSeconds, + }) { + final StringBuffer msg = StringBuffer(); + + msg.write('[$title]'); + + if (response?.request?.method != null || request?.method != null) { + msg.write(' [${response?.request?.method ?? request?.method}]'); + } + + if (response?.request != null || request != null) { + msg.writeln(' ${response?.request?.url ?? request?.url}'); + } else if (exception is ClientException) { + msg.writeln(' ${(exception as ClientException).uri}'); + } else { + msg.writeln(); + } + + if (response?.statusCode != null) { + msg.writeln('Status: ${response?.statusCode}'); + } + + final String? responseMessage = switch (exception) { + ClientException ex => ex.message, + Exception ex => ex.toString(), + _ => null, + }; + + if (settings.printResponseTime) { + final int? responseTime = getResponseTime(response?.headers) ?? + getResponseTime(response?.request?.headers); + + if (responseTime != null) { + msg.writeln('Time: $responseTime ms'); + } + } + + if (settings.printErrorMessage && (responseMessage?.isNotEmpty ?? false)) { + msg.writeln('Message: $responseMessage'); + } + + if (settings.printErrorHeaders && (response?.headers.isNotEmpty ?? false)) { + msg.writeln('Headers: ${convert(response?.headers)}'); + } + + final String? data = switch (response) { + Response res => res.body, + _ => null, + }; + + if (settings.printErrorData && (data?.isNotEmpty ?? false)) { + late final dynamic jsonData; + try { + jsonData = jsonDecode(data!); + } catch (_) { + jsonData = null; + } + + try { + if (jsonData != null) { + msg.writeln('Data: ${convert(jsonData)}'); + } else { + msg.writeln('Data: ${convert(data)}'); + } + } catch (error, stackTrace) { + msg.writeln( + 'Data: ', + ); + } + } + + return msg.toString().trimRight(); + } +} diff --git a/packages/talker_http_logger/lib/http_request_log.dart b/packages/talker_http_logger/lib/http_request_log.dart new file mode 100644 index 000000000..b425e1f88 --- /dev/null +++ b/packages/talker_http_logger/lib/http_request_log.dart @@ -0,0 +1,117 @@ +import 'dart:convert' show JsonEncoder, jsonDecode; + +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:meta/meta.dart'; +import 'package:talker/talker.dart'; +import 'package:talker_http_logger/curl_request.dart'; +import 'package:talker_http_logger/talker_http_logger_settings.dart'; + +class HttpRequestLog extends TalkerLog { + HttpRequestLog( + super.title, { + required this.request, + required this.settings, + }); + + final BaseRequest request; + final TalkerHttpLoggerSettings settings; + + static const JsonEncoder _encoder = JsonEncoder.withIndent(' '); + static const String _hiddenValue = '*****'; + + @override + AnsiPen get pen => settings.requestPen ?? (AnsiPen()..xterm(219)); + + @override + String get key => TalkerLogType.httpRequest.key; + + @override + LogLevel get logLevel => settings.logLevel; + + @visibleForTesting + String convert(Object? object) => _encoder.convert(object); + + @override + String generateTextMessage({ + TimeFormat timeFormat = TimeFormat.timeAndSeconds, + }) { + final StringBuffer msg = StringBuffer(); + msg.write('[$title]'); + msg.write(' [${request.method}]'); + msg.writeln(' $message'); + + final Map headers = {...request.headers}; + + // HTTP headers are case-insensitive by standard + _replaceHiddenHeaders(headers); + + if (settings.printRequestCurl) { + msg.writeln('[cURL] ${request.toCurl(headers: headers)}'); + } + + if (settings.printRequestHeaders && headers.isNotEmpty) { + try { + msg.writeln('Headers: ${convert(headers)}'); + } catch (error, stackTrace) { + msg.writeln( + 'Headers: ', + ); + } + } + + if (settings.printRequestData) { + switch (request) { + case Request req when req.body.isNotEmpty: + late final dynamic jsonData; + try { + jsonData = jsonDecode(req.body); + } catch (_) { + jsonData = null; + } + + try { + if (jsonData != null) { + msg.writeln('Data: ${convert(jsonData)}'); + break; + } else { + msg.writeln('Data: ${req.body}'); + break; + } + } catch (error, stackTrace) { + msg.writeln( + 'Data: ', + ); + } + break; + case MultipartRequest req + when req.fields.isNotEmpty || req.files.isNotEmpty: + msg.writeln('Data: ${convert( + { + ...req.fields, + for (final MultipartFile file in req.files) + file.field: file.filename ?? '' + }, + )}'); + break; + default: + break; + } + } + + return msg.toString().trimRight(); + } + + void _replaceHiddenHeaders(Map headers) { + if (settings.hiddenHeaders.isEmpty || headers.isEmpty) { + return; + } + + // HTTP headers are case-insensitive by standard + final Set hiddenLower = settings.hiddenHeaders + .map((String header) => header.toLowerCase()) + .toSet(); + + headers.updateAll((String k, String v) => + hiddenLower.contains(k.toLowerCase()) ? _hiddenValue : v); + } +} diff --git a/packages/talker_http_logger/lib/http_response_log.dart b/packages/talker_http_logger/lib/http_response_log.dart new file mode 100644 index 000000000..ea4bdea78 --- /dev/null +++ b/packages/talker_http_logger/lib/http_response_log.dart @@ -0,0 +1,90 @@ +import 'dart:convert' show JsonEncoder, jsonDecode; + +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:meta/meta.dart'; +import 'package:talker/talker.dart'; +import 'package:talker_http_logger/response_time.dart'; +import 'package:talker_http_logger/talker_http_logger_settings.dart'; + +class HttpResponseLog extends TalkerLog with ResponseTime { + HttpResponseLog( + super.title, { + required this.response, + required this.settings, + }); + + final BaseResponse response; + final TalkerHttpLoggerSettings settings; + + static const JsonEncoder _encoder = JsonEncoder.withIndent(' '); + + @override + AnsiPen get pen => settings.responsePen ?? (AnsiPen()..xterm(46)); + + @override + String get key => TalkerLogType.httpResponse.key; + + @override + LogLevel get logLevel => settings.logLevel; + + @visibleForTesting + String convert(Object? object) => _encoder.convert(object); + + @override + String generateTextMessage({ + TimeFormat timeFormat = TimeFormat.timeAndSeconds, + }) { + final StringBuffer msg = StringBuffer(); + msg.write('[$title]'); + msg.write(' [${response.request?.method}]'); + msg.writeln(' $message'); + + final String? data = switch (response) { + Response res => res.body, + _ => null, + }; + + msg.writeln('Status: ${response.statusCode}'); + + if (settings.printResponseTime) { + final int? responseTime = getResponseTime(response.headers) ?? + getResponseTime(response.request?.headers); + + if (responseTime != null) { + msg.writeln('Time: $responseTime ms'); + } + } + + if (settings.printResponseMessage && response.reasonPhrase != null) { + msg.writeln('Message: ${response.reasonPhrase}'); + } + + if (settings.printResponseHeaders && response.headers.isNotEmpty) { + try { + msg.writeln('Headers: ${convert(response.headers)}'); + } catch (error, stackTrace) { + msg.writeln( + 'Headers: ', + ); + } + } + + if (settings.printResponseRedirects && response.isRedirect) { + msg.writeln('Redirect: ${response.isRedirect}'); + } + + if (settings.printResponseData && (data?.isNotEmpty ?? false)) { + try { + msg.writeln('Data: ${convert(jsonDecode(data!))}'); + } on FormatException { + msg.writeln('Data: ${convert(data)}'); + } catch (error, stackTrace) { + msg.writeln( + 'Data: ', + ); + } + } + + return msg.toString().trimRight(); + } +} diff --git a/packages/talker_http_logger/lib/response_time.dart b/packages/talker_http_logger/lib/response_time.dart new file mode 100644 index 000000000..b9acfd3e8 --- /dev/null +++ b/packages/talker_http_logger/lib/response_time.dart @@ -0,0 +1,15 @@ +import 'package:talker/talker.dart'; +import 'package:talker_http_logger/talker_http_logger_interceptor.dart'; + +mixin ResponseTime on TalkerLog { + int? getResponseTime(Map? headers) { + final int? triggerTime = + int.tryParse(headers?[TalkerHttpLogger.kLogsTimeStamp] ?? ''); + + if (triggerTime != null && triggerTime > 0) { + return DateTime.timestamp().millisecondsSinceEpoch - triggerTime; + } + + return null; + } +} diff --git a/packages/talker_http_logger/lib/talker_http_logger.dart b/packages/talker_http_logger/lib/talker_http_logger.dart index dcc5a20d8..118e24256 100644 --- a/packages/talker_http_logger/lib/talker_http_logger.dart +++ b/packages/talker_http_logger/lib/talker_http_logger.dart @@ -1,168 +1,4 @@ -library talker_http_logger; +library; -import 'dart:convert'; - -import 'package:http_interceptor/http_interceptor.dart'; -import 'package:talker/talker.dart'; -import 'package:talker_http_logger/talker_http_logger_settings.dart'; - -class TalkerHttpLogger extends InterceptorContract { - TalkerHttpLogger( - {Talker? talker, this.settings = const TalkerHttpLoggerSettings()}) { - _talker = talker ?? Talker(); - } - - late Talker _talker; - - /// [TalkerHttpLogger] settings and customization - TalkerHttpLoggerSettings settings; - - @override - Future interceptRequest({ - required BaseRequest request, - }) async { - final message = '${request.url}'; - _talker.logCustom( - HttpRequestLog(message, request: request, settings: settings)); - return request; - } - - @override - Future interceptResponse({ - required BaseResponse response, - }) async { - final message = '${response.request?.url}'; - - if (response.statusCode >= 400 && response.statusCode < 600) { - _talker.logCustom(HttpErrorLog(message, response: response)); - } else { - _talker.logCustom(HttpResponseLog(message, response: response)); - } - - return response; - } -} - -const encoder = JsonEncoder.withIndent(' '); -const _hiddenValue = '*****'; - -class HttpRequestLog extends TalkerLog { - HttpRequestLog( - String title, { - required this.request, - this.settings = const TalkerHttpLoggerSettings(), - }) : super(title); - - final BaseRequest request; - - final TalkerHttpLoggerSettings settings; - - @override - AnsiPen get pen => (AnsiPen()..xterm(219)); - - @override - String get key => TalkerLogType.httpRequest.key; - - @override - String generateTextMessage( - {TimeFormat timeFormat = TimeFormat.timeAndSeconds}) { - var msg = '[$title] [${request.method}] $message'; - - final headers = Map.from(request.headers); - - try { - if (headers.isNotEmpty) { - if (settings.hiddenHeaders.isNotEmpty) { - headers.updateAll((key, value) { - return settings.hiddenHeaders - .map((v) => v.toLowerCase()) - .contains(key.toLowerCase()) - ? _hiddenValue - : value; - }); - } - final prettyHeaders = encoder.convert(headers); - msg += '\nHeaders: $prettyHeaders'; - } - } catch (_) { - // TODO: add handling can`t convert - } - return msg; - } -} - -class HttpResponseLog extends TalkerLog { - HttpResponseLog( - String title, { - required this.response, - this.settings = const TalkerHttpLoggerSettings(), - }) : super(title); - - final BaseResponse response; - - final TalkerHttpLoggerSettings settings; - - @override - AnsiPen get pen => (AnsiPen()..xterm(46)); - - @override - String get key => TalkerLogType.httpResponse.key; - - @override - String generateTextMessage( - {TimeFormat timeFormat = TimeFormat.timeAndSeconds}) { - var msg = '[$title] [${response.request?.method}] $message'; - - final headers = response.request?.headers; - - msg += '\nStatus: ${response.statusCode}'; - - try { - if (headers?.isNotEmpty ?? false) { - final prettyHeaders = encoder.convert(headers); - msg += '\nHeaders: $prettyHeaders'; - } - } catch (_) { - // TODO: add handling can`t convert - } - return msg; - } -} - -class HttpErrorLog extends TalkerLog { - HttpErrorLog( - String title, { - required this.response, - this.settings = const TalkerHttpLoggerSettings(), - }) : super(title); - - final BaseResponse response; - - final TalkerHttpLoggerSettings settings; - - @override - AnsiPen get pen => AnsiPen()..red(); - - @override - String get key => TalkerLogType.httpError.key; - - @override - String generateTextMessage( - {TimeFormat timeFormat = TimeFormat.timeAndSeconds}) { - var msg = '[$title] [${response.request?.method}] $message'; - - final headers = response.request?.headers; - - msg += '\nStatus: ${response.statusCode}'; - - try { - if (headers?.isNotEmpty ?? false) { - final prettyHeaders = encoder.convert(headers); - msg += '\nHeaders: $prettyHeaders'; - } - } catch (_) { - // TODO: add handling can`t convert - } - return msg; - } -} +export 'talker_http_logger_interceptor.dart'; +export 'talker_http_logger_settings.dart'; diff --git a/packages/talker_http_logger/lib/talker_http_logger_interceptor.dart b/packages/talker_http_logger/lib/talker_http_logger_interceptor.dart new file mode 100644 index 000000000..0c5b9dc16 --- /dev/null +++ b/packages/talker_http_logger/lib/talker_http_logger_interceptor.dart @@ -0,0 +1,127 @@ +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:talker/talker.dart'; +import 'package:talker_http_logger/http_error_log.dart'; +import 'package:talker_http_logger/http_request_log.dart'; +import 'package:talker_http_logger/http_response_log.dart'; +import 'package:talker_http_logger/talker_http_logger_settings.dart'; + +class TalkerHttpLogger extends InterceptorContract { + TalkerHttpLogger({ + Talker? talker, + this.settings = const TalkerHttpLoggerSettings(), + this.addonId, + }) { + _talker = talker ?? Talker(); + } + + static const String kLogsTimeStamp = 'x-talker-http-logger-ts'; + + late final Talker _talker; + + /// [TalkerHttpLogger] settings and customization + TalkerHttpLoggerSettings settings; + + /// Talker addon functionality addon id for creating a lot of addons + final String? addonId; + + /// Method to update [settings] of [TalkerHttpLogger] + void configure({ + bool? enabled, + LogLevel? logLevel, + bool? printResponseData, + bool? printResponseHeaders, + bool? printResponseMessage, + bool? printResponseTime, + bool? printErrorData, + bool? printErrorHeaders, + bool? printErrorMessage, + bool? printRequestData, + bool? printRequestHeaders, + bool? printRequestCurl, + AnsiPen? requestPen, + AnsiPen? responsePen, + AnsiPen? errorPen, + RequestFilter? requestFilter, + ResponseFilter? responseFilter, + ResponseFilter? errorFilter, + Set? hiddenHeaders, + }) => + settings = settings.copyWith( + enabled: enabled, + logLevel: logLevel, + printRequestData: printRequestData, + printRequestHeaders: printRequestHeaders, + printRequestCurl: printRequestCurl, + printResponseData: printResponseData, + printErrorData: printErrorData, + printErrorHeaders: printErrorHeaders, + printErrorMessage: printErrorMessage, + printResponseHeaders: printResponseHeaders, + printResponseMessage: printResponseMessage, + requestPen: requestPen, + responsePen: responsePen, + errorPen: errorPen, + requestFilter: requestFilter, + responseFilter: responseFilter, + errorFilter: errorFilter, + hiddenHeaders: hiddenHeaders, + printResponseTime: printResponseTime, + ); + + @override + Future interceptRequest({ + required BaseRequest request, + }) async { + if (settings.enabled && (settings.requestFilter?.call(request) ?? true)) { + if (settings.printResponseTime) { + request.headers[kLogsTimeStamp] = + DateTime.timestamp().millisecondsSinceEpoch.toString(); + } + + _talker.logCustom( + HttpRequestLog( + request.url.toString(), + request: request, + settings: settings, + ), + ); + } + + return request; + } + + @override + Future interceptResponse({ + required BaseResponse response, + }) async { + final String message = '${response.request?.url}'; + + switch (response.statusCode) { + case int statusCode when settings.enabled && statusCode < 400: + if (settings.responseFilter?.call(response) ?? true) { + _talker.logCustom( + HttpResponseLog( + message, + response: response, + settings: settings, + ), + ); + } + break; + case _ when settings.enabled: + if (settings.errorFilter?.call(response) ?? true) { + _talker.logCustom( + HttpErrorLog( + message, + request: response.request, + response: response, + settings: settings, + ), + ); + } + break; + } + + return response; + } +} diff --git a/packages/talker_http_logger/lib/talker_http_logger_settings.dart b/packages/talker_http_logger/lib/talker_http_logger_settings.dart index 62c70382f..04ae698d0 100644 --- a/packages/talker_http_logger/lib/talker_http_logger_settings.dart +++ b/packages/talker_http_logger/lib/talker_http_logger_settings.dart @@ -1,9 +1,186 @@ -class TalkerHttpLoggerSettings { +import 'package:equatable/equatable.dart'; +import 'package:http_interceptor/http_interceptor.dart' + show BaseRequest, BaseResponse; +import 'package:talker/talker.dart' show AnsiPen, LogLevel; +import 'package:talker/talker.dart'; + +typedef RequestFilter = bool Function(BaseRequest request); +typedef ResponseFilter = bool Function(BaseResponse response); + +class TalkerHttpLoggerSettings with EquatableMixin { const TalkerHttpLoggerSettings({ + this.enabled = true, + this.logLevel = LogLevel.debug, + this.printResponseData = true, + this.printResponseHeaders = false, + this.printResponseMessage = true, + this.printResponseRedirects = false, + this.printResponseTime = false, + this.printErrorData = true, + this.printErrorHeaders = true, + this.printErrorMessage = true, + this.printRequestData = true, + this.printRequestHeaders = false, + this.printRequestCurl = false, this.hiddenHeaders = const {}, + this.requestPen, + this.responsePen, + this.errorPen, + this.requestFilter, + this.responseFilter, + this.errorFilter, }); + /// Print Http logger if true + final bool enabled; + + // LogLevel, default is debug + final LogLevel logLevel; + + /// Print [response.data] if true + final bool printResponseData; + + /// Print [response.headers] if true + final bool printResponseHeaders; + + /// Print [response.statusMessage] if true + final bool printResponseMessage; + + /// Print [response.redirects] if true + final bool printResponseRedirects; + + /// Print response time if true + final bool printResponseTime; + + /// Print [error.response.data] if true + final bool printErrorData; + + /// Print [error.response.headers] if true + final bool printErrorHeaders; + + /// Print [error.message] if true + final bool printErrorMessage; + + /// Print [request.data] if true + final bool printRequestData; + + /// Print [request.headers] if true + final bool printRequestHeaders; + + /// Prints a curl request equivalent of the network call if true + final bool printRequestCurl; + + /// Field to set custom http request console logs color + ///``` + ///// Red color + ///final redPen = AnsiPen()..red(); + /// + ///// Blue color + ///final redPen = AnsiPen()..blue(); + ///``` + /// More details in [AnsiPen] docs + final AnsiPen? requestPen; + + /// Field to set custom http response console logs color + ///``` + ///// Red color + ///final redPen = AnsiPen()..red(); + /// + ///// Blue color + ///final redPen = AnsiPen()..blue(); + ///``` + /// More details in [AnsiPen] docs + final AnsiPen? responsePen; + + /// Field to set custom http error console logs color + ///``` + ///// Red color + ///final redPen = AnsiPen()..red(); + /// + ///// Blue color + ///final redPen = AnsiPen()..blue(); + ///``` + /// More details in [AnsiPen] docs + final AnsiPen? errorPen; + + /// For request filtering. + /// You can add your custom logic to log only specific HTTP requests [Request]. + final RequestFilter? requestFilter; + + /// For response filtering. + /// You can add your custom logic to log only specific HTTP responses [Response]. + final ResponseFilter? responseFilter; + + /// For error filtering. + /// You can add your custom logic to log only specific HTTP error [Response]. + final ResponseFilter? errorFilter; + /// Header values for the specified keys in the Set will be replaced with *****. /// Case insensitive final Set hiddenHeaders; + + TalkerHttpLoggerSettings copyWith({ + bool? enabled, + LogLevel? logLevel, + bool? printResponseData, + bool? printResponseHeaders, + bool? printResponseMessage, + bool? printResponseTime, + bool? printErrorData, + bool? printErrorHeaders, + bool? printErrorMessage, + bool? printRequestData, + bool? printRequestHeaders, + bool? printRequestCurl, + AnsiPen? requestPen, + AnsiPen? responsePen, + AnsiPen? errorPen, + RequestFilter? requestFilter, + ResponseFilter? responseFilter, + ResponseFilter? errorFilter, + Set? hiddenHeaders, + }) => + TalkerHttpLoggerSettings( + enabled: enabled ?? this.enabled, + logLevel: logLevel ?? this.logLevel, + printResponseData: printResponseData ?? this.printResponseData, + printResponseHeaders: printResponseHeaders ?? this.printResponseHeaders, + printResponseMessage: printResponseMessage ?? this.printResponseMessage, + printResponseTime: printResponseTime ?? this.printResponseTime, + printErrorData: printErrorData ?? this.printErrorData, + printErrorHeaders: printErrorHeaders ?? this.printErrorHeaders, + printErrorMessage: printErrorMessage ?? this.printErrorMessage, + printRequestData: printRequestData ?? this.printRequestData, + printRequestHeaders: printRequestHeaders ?? this.printRequestHeaders, + printRequestCurl: printRequestCurl ?? this.printRequestCurl, + requestPen: requestPen ?? this.requestPen, + responsePen: responsePen ?? this.responsePen, + errorPen: errorPen ?? this.errorPen, + requestFilter: requestFilter ?? this.requestFilter, + responseFilter: responseFilter ?? this.responseFilter, + errorFilter: errorFilter ?? this.errorFilter, + hiddenHeaders: hiddenHeaders ?? this.hiddenHeaders, + ); + + @override + List get props => [ + enabled, + printResponseData, + printResponseHeaders, + printResponseMessage, + printResponseTime, + printErrorData, + printErrorHeaders, + printErrorMessage, + printRequestData, + printRequestHeaders, + printRequestCurl, + requestPen, + responsePen, + errorPen, + requestFilter, + responseFilter, + errorFilter, + hiddenHeaders, + ]; } diff --git a/packages/talker_http_logger/pubspec.yaml b/packages/talker_http_logger/pubspec.yaml index 09f5b9e46..0fcb4cdb6 100644 --- a/packages/talker_http_logger/pubspec.yaml +++ b/packages/talker_http_logger/pubspec.yaml @@ -1,15 +1,19 @@ name: talker_http_logger description: Lightweight and customizable http client logger on talker base -version: 0.1.0-dev.55 +version: 1.0.0 homepage: https://github.com/Frezyx/talker environment: sdk: ^3.0.3 dependencies: + equatable: ^2.0.7 http_interceptor: ^2.0.0 + meta: ^1.9.1 talker: ^4.8.4 dev_dependencies: - lints: ^2.0.0 - test: ^1.21.0 + http_parser: ^4.1.2 + qs_dart: ^1.3.7+1 + lints: ^5.1.1 + test: ^1.25.15 diff --git a/packages/talker_http_logger/test/helpers/multipart_request_extension.dart b/packages/talker_http_logger/test/helpers/multipart_request_extension.dart new file mode 100644 index 000000000..a5763f7ca --- /dev/null +++ b/packages/talker_http_logger/test/helpers/multipart_request_extension.dart @@ -0,0 +1,29 @@ +import 'package:http_interceptor/http_interceptor.dart'; + +extension MultipartRequestExtension on Request { + MultipartRequest toMultipartRequest( + Iterable<({String name, dynamic value})> parts, + ) { + final MultipartRequest request = MultipartRequest(method, url) + ..headers.addAll(headers); + + for (final ({String name, dynamic value}) part in parts) { + if (part.value == null) continue; + + if (part.value is MultipartFile) { + request.files.add(part.value); + } else if (part.value is Iterable) { + request.files.addAll(part.value); + } else if (part.value is Iterable) { + request.fields.addAll({ + for (int i = 0; i < part.value.length; i++) + '${part.name}[$i]': part.value.elementAt(i).toString(), + }); + } else { + request.fields[part.name] = part.value.toString(); + } + } + + return request; + } +} diff --git a/packages/talker_http_logger/test/logger_test.dart b/packages/talker_http_logger/test/logger_test.dart new file mode 100644 index 000000000..6e3a36125 --- /dev/null +++ b/packages/talker_http_logger/test/logger_test.dart @@ -0,0 +1,426 @@ +import 'dart:convert'; + +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:qs_dart/qs_dart.dart' as qs; +import 'package:talker/talker.dart'; +import 'package:talker_http_logger/http_error_log.dart'; +import 'package:talker_http_logger/http_request_log.dart'; +import 'package:talker_http_logger/http_response_log.dart'; +import 'package:talker_http_logger/talker_http_logger_interceptor.dart'; +import 'package:test/test.dart'; + +void main() { + group('TalkerHttpLogger tests', () { + late TalkerHttpLogger logger; + late Talker talker; + late Request fakeRequest; + + setUp(() { + talker = Talker(settings: TalkerSettings(useConsoleLogs: false)); + logger = TalkerHttpLogger(talker: talker); + fakeRequest = Request(HttpMethod.GET.name, Uri.parse('/test')); + }); + + test('configure method should update logger settings', () { + logger.configure(printRequestData: true); + expect(logger.settings.printRequestData, true); + }); + + test('interceptRequest method should log http GET request', () async { + logger.configure(printRequestHeaders: true); + + await logger.interceptRequest(request: fakeRequest); + + expect(talker.history.firstOrNull?.message, fakeRequest.url.toString()); + expect(talker.history.firstOrNull, isA()); + expect( + talker.history.firstOrNull?.generateTextMessage(), + '[http-request] [GET] /test', + ); + }); + + test('interceptRequest method should log http POST request', () async { + logger.configure(printRequestHeaders: true); + + final Request postRequest = fakeRequest.copyWith( + method: HttpMethod.POST, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ); + + await logger.interceptRequest(request: postRequest); + + expect(talker.history.firstOrNull?.message, postRequest.url.toString()); + expect(talker.history.firstOrNull, isA()); + expect( + talker.history.firstOrNull?.generateTextMessage(), + '''[http-request] [POST] /test +Headers: { + "content-type": "application/json; charset=utf-8" +} +Data: { + "foo": "bar", + "baz": "qux" +}''', + ); + }); + + test('interceptRequest method should log http POST request form data', + () async { + logger.configure(printRequestHeaders: true); + + final Request postRequest = fakeRequest.copyWith( + method: HttpMethod.POST, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + }, + body: qs.encode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ); + + await logger.interceptRequest(request: postRequest); + + expect(talker.history.firstOrNull?.message, postRequest.url.toString()); + expect(talker.history.firstOrNull, isA()); + expect( + talker.history.firstOrNull?.generateTextMessage(), + '''[http-request] [POST] /test +Headers: { + "content-type": "application/x-www-form-urlencoded; charset=utf-8" +} +Data: foo=bar&baz=qux''', + ); + }); + + test('interceptRequest method should log http PUT request', () async { + logger.configure(printRequestHeaders: true); + + final Request putRequest = fakeRequest.copyWith( + method: HttpMethod.PUT, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ); + + await logger.interceptRequest(request: putRequest); + + expect(talker.history.firstOrNull?.message, putRequest.url.toString()); + expect(talker.history.firstOrNull, isA()); + expect( + talker.history.firstOrNull?.generateTextMessage(), + '''[http-request] [PUT] /test +Headers: { + "content-type": "application/json; charset=utf-8" +} +Data: { + "foo": "bar", + "baz": "qux" +}''', + ); + }); + + test('interceptRequest method should log http PATCH request', () async { + logger.configure(printRequestHeaders: true); + + final Request putRequest = fakeRequest.copyWith( + method: HttpMethod.PATCH, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ); + + await logger.interceptRequest(request: putRequest); + + expect(talker.history.firstOrNull?.message, putRequest.url.toString()); + expect(talker.history.firstOrNull, isA()); + expect( + talker.history.firstOrNull?.generateTextMessage(), + '''[http-request] [PATCH] /test +Headers: { + "content-type": "application/json; charset=utf-8" +} +Data: { + "foo": "bar", + "baz": "qux" +}''', + ); + }); + + test('interceptRequest method should log http DELETE request', () async { + logger.configure(printRequestHeaders: true); + + final Request deleteRequest = + fakeRequest.copyWith(method: HttpMethod.DELETE); + + await logger.interceptRequest(request: deleteRequest); + + expect(talker.history.firstOrNull?.message, deleteRequest.url.toString()); + expect(talker.history.firstOrNull, isA()); + expect( + talker.history.firstOrNull?.generateTextMessage(), + '''[http-request] [DELETE] /test +Headers: { + "content-type": "text/plain; charset=utf-8" +}''', + ); + }); + + test('interceptRequest method should log http HEAD request', () async { + logger.configure(printRequestHeaders: true); + + final Request headRequest = fakeRequest.copyWith(method: HttpMethod.HEAD); + + await logger.interceptRequest(request: headRequest); + + expect(talker.history.firstOrNull?.message, headRequest.url.toString()); + expect(talker.history.firstOrNull, isA()); + expect( + talker.history.firstOrNull?.generateTextMessage(), + '''[http-request] [HEAD] /test +Headers: { + "content-type": "text/plain; charset=utf-8" +}''', + ); + }); + + test('interceptResponse method should log http response', () async { + final Response fakeResponse = + Response('responseBody', 200, request: fakeRequest); + + final String logMessage = + fakeResponse.request?.url.toString() ?? fakeRequest.url.toString(); + + await logger.interceptResponse(response: fakeResponse); + + expect(talker.history.lastOrNull?.message, logMessage); + expect(talker.history.lastOrNull, isA()); + expect( + talker.history.lastOrNull?.generateTextMessage(), + '''[http-response] [GET] /test +Status: 200 +Data: "responseBody"''', + ); + }); + + test('interceptResponse method should log http 4xx response error', + () async { + final Response fakeResponse = + Response('responseErrorBody', 400, request: fakeRequest); + + await logger.interceptResponse(response: fakeResponse); + + expect(talker.history, isNotEmpty); + expect(talker.history.lastOrNull, isA()); + expect( + talker.history.lastOrNull?.generateTextMessage(), + '''[http-error] [GET] /test +Status: 400 +Data: "responseErrorBody"''', + ); + }); + + test('interceptResponse method should log http 5xx response error', + () async { + final Response fakeResponse = + Response('responseErrorBody', 500, request: fakeRequest); + + await logger.interceptResponse(response: fakeResponse); + + expect(talker.history, isNotEmpty); + expect(talker.history.lastOrNull, isA()); + expect( + talker.history.lastOrNull?.generateTextMessage(), + '''[http-error] [GET] /test +Status: 500 +Data: "responseErrorBody"''', + ); + }); + + test('interceptResponse method should log http response headers', () async { + logger.configure(printResponseHeaders: true); + + final Response fakeResponse = Response( + '', + 200, + headers: {'HEADER': 'VALUE'}, + request: fakeRequest, + ); + + await logger.interceptResponse(response: fakeResponse); + + expect( + talker.history.lastOrNull?.generateTextMessage(), + '''[http-response] [GET] /test +Status: 200 +Headers: { + "HEADER": "VALUE" +}''', + ); + }); + + test( + 'interceptRequest method should hide specific header values in logging', + () async { + logger.configure( + printRequestHeaders: true, + hiddenHeaders: {'Authorization'}, + ); + + await logger.interceptRequest( + request: fakeRequest.copyWith( + headers: { + "firstHeader": "firstHeaderValue", + "authorization": "bearer super_secret_token", + "lastHeader": "lastHeaderValue", + }, + ), + ); + + expect( + talker.history.firstOrNull?.generateTextMessage(), + '''[http-request] [GET] /test +Headers: { + "content-type": "text/plain; charset=utf-8", + "firstHeader": "firstHeaderValue", + "authorization": "*****", + "lastHeader": "lastHeaderValue" +}''', + ); + }); + + test('interceptRequest should add timestamp to request headers', () async { + logger.configure(printResponseTime: true); + + await logger.interceptRequest(request: fakeRequest); + + expect(fakeRequest.headers[TalkerHttpLogger.kLogsTimeStamp], isNotNull); + expect( + fakeRequest.headers[TalkerHttpLogger.kLogsTimeStamp], + isA(), + ); + expect( + fakeRequest.headers[TalkerHttpLogger.kLogsTimeStamp], + matches(RegExp(r'^\d+$')), + ); + final int? ts = int.tryParse( + fakeRequest.headers[TalkerHttpLogger.kLogsTimeStamp] ?? '', + ); + expect(ts, isNotNull); + expect(ts, isNonNegative); + expect(ts, greaterThan(0)); + expect( + ts, + lessThanOrEqualTo(DateTime.timestamp().millisecondsSinceEpoch), + ); + + final String? log = talker.history.firstOrNull?.generateTextMessage(); + expect(log, isNotNull); + expect(log, isA()); + expect(log, contains('[http-request] [GET] /test')); + }); + + test('interceptResponse should show response time when requested', + () async { + logger.configure(printResponseTime: true); + + // Simulate a request with a timestamp + await logger.interceptRequest(request: fakeRequest); + + final Response fakeResponse = Response( + 'responseBody', + 200, + request: fakeRequest, + ); + + await logger.interceptResponse(response: fakeResponse); + + final String? log = talker.history.lastOrNull?.generateTextMessage(); + expect(log, isNotNull); + expect(log, isA()); + expect(log, contains('[http-response] [GET] /test')); + expect(log, contains('Status: 200')); + expect(log, matches(RegExp(r'Time: \d+ ms'))); + expect(log, contains('Data: "responseBody"')); + }); + + test( + 'interceptResponse should show response time in error when requested', + () async { + logger.configure(printResponseTime: true); + + // Simulate a request with a timestamp + await logger.interceptRequest(request: fakeRequest); + + final Response fakeResponse = + Response('responseErrorBody', 400, request: fakeRequest); + + await logger.interceptResponse(response: fakeResponse); + + final String? log = talker.history.lastOrNull?.generateTextMessage(); + expect(log, isNotNull); + expect(log, isA()); + expect(log, contains('[http-error] [GET] /test')); + expect(log, contains('Status: 400')); + expect(log, matches(RegExp(r'Time: \d+ ms'))); + expect(log, contains('Data: "responseErrorBody"')); + }, + ); + + test('interceptRequest should not log if logger is disabled', () async { + logger.configure(enabled: false); + + await logger.interceptRequest(request: fakeRequest); + + expect(talker.history, isEmpty); + }); + + test( + 'interceptRequest should not log if request and response are filtered', + () async { + logger.configure( + requestFilter: (_) => false, + responseFilter: (_) => false, + ); + + await logger.interceptRequest(request: fakeRequest); + + expect(talker.history, isEmpty); + }, + ); + + test( + 'interceptResponse should not log if error is filtered', + () async { + logger.configure( + requestFilter: (_) => false, + errorFilter: (_) => false, + ); + + await logger.interceptResponse( + response: Response( + 'responseErrorBodyBase', + 400, + request: fakeRequest, + ), + ); + + expect(talker.history, isEmpty); + }, + ); + }); +} diff --git a/packages/talker_http_logger/test/logs_test.dart b/packages/talker_http_logger/test/logs_test.dart new file mode 100644 index 000000000..1305d3016 --- /dev/null +++ b/packages/talker_http_logger/test/logs_test.dart @@ -0,0 +1,1033 @@ +import 'dart:convert'; + +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:qs_dart/qs_dart.dart' as qs; +import 'package:talker/talker.dart'; +import 'package:talker_http_logger/curl_request.dart'; +import 'package:talker_http_logger/http_error_log.dart'; +import 'package:talker_http_logger/http_request_log.dart'; +import 'package:talker_http_logger/http_response_log.dart'; +import 'package:talker_http_logger/talker_http_logger.dart'; +import 'package:test/test.dart'; + +import 'helpers/multipart_request_extension.dart'; + +class _MockHttpRequestLog extends HttpRequestLog { + _MockHttpRequestLog( + super.title, { + required super.request, + required super.settings, + }); + + @override + String convert(Object? object) => throw Exception('forced error'); +} + +class _MockHttpResponseLog extends HttpResponseLog { + _MockHttpResponseLog( + super.title, { + required super.response, + required super.settings, + }); + + @override + String convert(Object? object) => throw Exception('forced error'); +} + +class _MockHttpErrorLog extends HttpErrorLog { + _MockHttpErrorLog( + super.title, { + // ignore: unused_element_parameter + super.request, + super.response, + required super.settings, + super.exception, + // ignore: unused_element_parameter + super.stackTrace, + }); + + @override + String convert(Object? object) => throw Exception('forced error'); +} + +void main() { + late Request request; + + setUp(() { + request = Request(HttpMethod.GET.name, Uri.parse('/test')); + }); + + group('HttpRequestLog', () { + test('generateTextMessage should include method and message', () { + final HttpRequestLog log = HttpRequestLog( + 'Test message', + request: request, + settings: TalkerHttpLoggerSettings( + requestPen: AnsiPen()..blue(), + ), + ); + + expect( + log.generateTextMessage(), + contains('[GET] Test message'), + ); + }); + + test( + 'generateTextMessage should include data if printRequestData is true', + () { + final HttpRequestLog log = HttpRequestLog( + null, + request: request.copyWith( + method: HttpMethod.POST, + body: jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ), + settings: const TalkerHttpLoggerSettings(printRequestData: true), + ); + + expect( + log.generateTextMessage(), + contains( + '''Data: { + "foo": "bar", + "baz": "qux" +}''', + ), + ); + }, + ); + + test( + 'generateTextMessage with multipart request should include form data if printRequestData is true', + () { + final BaseRequest multiPartRequest = + request.copyWith(method: HttpMethod.POST).toMultipartRequest([ + (name: '1', value: {'foo': 'bar'}), + (name: '2', value: {'baz': 'qux'}), + ]); + + final HttpRequestLog log = HttpRequestLog( + null, + request: multiPartRequest, + settings: const TalkerHttpLoggerSettings(printRequestData: true), + ); + + expect( + log.generateTextMessage(), + contains( + '''Data: { + "1": "{foo: bar}", + "2": "{baz: qux}" +}''', + ), + ); + }, + ); + + test( + 'generateTextMessage with multipart file request should include form data if printRequestData is true', + () { + final MultipartFile file = MultipartFile.fromBytes( + 'foo_bar', + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + filename: 'baz_qux', + contentType: MediaType.parse('application/octet-stream'), + ); + + final BaseRequest multiPartRequest = request + .copyWith(method: HttpMethod.POST) + .toMultipartRequest([(name: 'file', value: file)]); + + final HttpRequestLog log = HttpRequestLog( + null, + request: multiPartRequest, + settings: const TalkerHttpLoggerSettings(printRequestData: true), + ); + + expect( + log.generateTextMessage(), + contains('Data: {\n "foo_bar": "baz_qux"\n}'), + ); + }, + ); + + test( + 'generateTextMessage with BaseRequest should include form data if printRequestData is true', + () { + final HttpRequestLog log = HttpRequestLog( + null, + request: request.copyWith( + method: HttpMethod.POST, + body: jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ), + settings: const TalkerHttpLoggerSettings(printRequestData: true), + ); + + expect( + log.generateTextMessage(), + contains( + '''Data: { + "foo": "bar", + "baz": "qux" +}''', + ), + ); + }, + ); + + test( + 'generateTextMessage should include headers if printRequestHeaders is true', + () { + final HttpRequestLog log = HttpRequestLog( + null, + request: request.copyWith( + headers: {'Authorization': 'Bearer Token'}, + ), + settings: const TalkerHttpLoggerSettings(printRequestHeaders: true), + ); + + expect( + log.generateTextMessage(), + contains('"Authorization": "Bearer Token"'), + ); + }, + ); + + test( + 'generateTextMessage should include redirect if printResponseRedirects is true', + () { + final HttpResponseLog log = HttpResponseLog( + '', + response: Response( + '', + 200, + request: request, + isRedirect: true, + ), + settings: const TalkerHttpLoggerSettings( + printResponseRedirects: true, + ), + ); + + expect(log.generateTextMessage(), contains('Redirect: true')); + }, + ); + + test( + 'generateTextMessage should not include redirects if printResponseRedirects is false', + () { + final HttpResponseLog log = HttpResponseLog( + '', + response: Response( + '', + 200, + request: request, + isRedirect: true, + ), + settings: const TalkerHttpLoggerSettings( + printResponseRedirects: false, + ), + ); + + final result = log.generateTextMessage(); + expect(result.contains('Redirect:'), isFalse); + }, + ); + + test( + 'generateTextMessage should include curl command if printRequestCurl is true', + () { + final HttpRequestLog log = HttpRequestLog( + null, + request: request, + settings: const TalkerHttpLoggerSettings(printRequestCurl: true), + ); + + final String out = log.generateTextMessage(); + + expect(out, contains('[cURL]')); + expect(out, contains('curl -v')); + expect(out, contains('-X ${request.method}')); + expect(out, contains('${request.url}')); + expect(out, contains('[cURL] ${request.toCurl()}')); + }, + ); + + test( + 'generateTextMessage should include curl command with headers if printRequestCurl is true', + () { + final Request requestWithHeaders = request.copyWith( + headers: { + 'foo': 'bar', + 'baz': 'qux', + }, + ); + + final HttpRequestLog log = HttpRequestLog( + null, + request: requestWithHeaders, + settings: const TalkerHttpLoggerSettings( + printRequestCurl: true, + hiddenHeaders: {'baz'}, + ), + ); + + final String out = log.generateTextMessage(); + + expect(out, contains('[cURL]')); + expect(out, contains('curl -v')); + expect(out, contains('-X ${requestWithHeaders.method}')); + expect(out, contains("-H 'foo: bar'")); + expect(out, isNot(contains("-H 'baz: qux'"))); + expect(out, contains("-H 'baz: *****'")); + expect(out, contains('${requestWithHeaders.url}')); + expect( + out, + contains( + '[cURL] ${requestWithHeaders.copyWith(headers: { + ...requestWithHeaders.headers, + 'baz': '*****', + }).toCurl()}', + ), + ); + }, + ); + + test( + 'generateTextMessage should include curl command with body if printRequestCurl is true', + () { + final Request postRequest = request.copyWith( + method: HttpMethod.POST, + body: jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ); + + final HttpRequestLog log = HttpRequestLog( + null, + request: postRequest, + settings: const TalkerHttpLoggerSettings(printRequestCurl: true), + ); + + final String out = log.generateTextMessage(); + + expect(out, contains('[cURL]')); + expect(out, contains('curl -v')); + expect(out, contains('-X ${postRequest.method}')); + expect(out, contains(r"""-d '{"foo":"bar","baz":"qux"}'""")); + expect(out, contains('${postRequest.url}')); + expect(out, contains('[cURL] ${postRequest.toCurl()}')); + }, + ); + + test( + 'generateTextMessage should include curl command with form body if printRequestCurl is true', + () { + final Request postRequest = request.copyWith( + method: HttpMethod.POST, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + }, + body: qs.encode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ); + + final HttpRequestLog log = HttpRequestLog( + null, + request: postRequest, + settings: const TalkerHttpLoggerSettings(printRequestCurl: true), + ); + + final String out = log.generateTextMessage(); + + expect(out, contains('[cURL]')); + expect(out, contains('curl -v')); + expect(out, contains('-X ${postRequest.method}')); + expect(out, contains(r"""-d 'foo=bar&baz=qux'""")); + expect(out, contains('${postRequest.url}')); + expect(out, contains('[cURL] ${postRequest.toCurl()}')); + }, + ); + + test( + 'generateTextMessage with baseRequest should include curl command with form data if printRequestCurl is true', + () { + final BaseRequest postRequest = request.copyWith( + method: HttpMethod.POST, + headers: { + 'content-type': 'application/json; charset=utf-8', + }, + body: jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + ); + + final HttpRequestLog log = HttpRequestLog( + null, + request: postRequest, + settings: const TalkerHttpLoggerSettings(printRequestCurl: true), + ); + + final String out = log.generateTextMessage(); + + expect(out, contains('[cURL]')); + expect(out, contains('curl -v')); + expect(out, contains('-X ${postRequest.method}')); + expect( + out, + contains( + "-H 'content-type: application/json; charset=utf-8'", + ), + ); + expect(out, contains("""-d '{"foo":"bar","baz":"qux"}'""")); + expect(out, contains('${postRequest.url}')); + expect(out, contains('[cURL] ${postRequest.toCurl()}')); + }, + ); + + test( + 'generateTextMessage with multipart baseRequest should include curl command with form data if printRequestCurl is true', + () { + final List<({String name, Map value})> parts = [ + (name: '1', value: {'foo': 'bar'}), + (name: '2', value: {'baz': 'qux'}), + ]; + + final BaseRequest multiPartRequest = + request.copyWith(method: HttpMethod.POST).toMultipartRequest(parts); + + final HttpRequestLog log = HttpRequestLog( + null, + request: multiPartRequest, + settings: const TalkerHttpLoggerSettings(printRequestCurl: true), + ); + + final String out = log.generateTextMessage(); + + expect(out, contains('[cURL]')); + expect(out, contains('curl -v')); + expect(out, contains('-X ${multiPartRequest.method}')); + for (final ({String name, Map value}) part in parts) { + expect(out, contains("-F '${part.name}=${part.value}'")); + } + expect(out, contains('${multiPartRequest.url}')); + expect(out, contains('[cURL] ${multiPartRequest.toCurl()}')); + }, + ); + + test( + 'generateTextMessage with multipart file request should include curl command with form data if printRequestCurl is true', + () async { + final MultipartFile file = MultipartFile.fromBytes( + 'foo_bar', + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + filename: 'baz_qux', + contentType: MediaType.parse('application/octet-stream'), + ); + + final List<({String name, MultipartFile value})> parts = [ + (name: 'file', value: file), + ]; + + final BaseRequest multiPartRequest = + request.copyWith(method: HttpMethod.POST).toMultipartRequest(parts); + + final HttpRequestLog log = HttpRequestLog( + null, + request: multiPartRequest, + settings: const TalkerHttpLoggerSettings(printRequestCurl: true), + ); + + final String out = log.generateTextMessage(); + + expect(out, contains('[cURL]')); + expect(out, contains('curl -v')); + expect(out, contains('-X ${multiPartRequest.method}')); + for (final ({String name, MultipartFile value}) part in parts) { + expect( + out, + contains("-F '${part.value.field}=@${part.value.filename}'"), + ); + } + expect(out, contains('${multiPartRequest.url}')); + expect(out, contains('[cURL] ${multiPartRequest.toCurl()}')); + }, + ); + + test('logLevel should return settings logLevel', () { + final HttpRequestLog log = HttpRequestLog( + 'Test message', + request: request, + settings: const TalkerHttpLoggerSettings(logLevel: LogLevel.info), + ); + + expect(log.logLevel, equals(LogLevel.info)); + }); + + test('force throw exception on convert headers', () { + final HttpRequestLog log = _MockHttpRequestLog( + 'Test message', + request: request.copyWith(headers: { + 'foo': 'bar', + 'baz': 'qux', + }), + settings: const TalkerHttpLoggerSettings(printRequestHeaders: true), + ); + + expect(() => log.convert('foo'), throwsA(isA())); + + expect(() => log.generateTextMessage(), returnsNormally); + + final String result = log.generateTextMessage(); + expect(result, contains('[GET] Test message')); + expect(result, contains('Headers: log.convert('foo'), throwsA(isA())); + + expect(() => log.generateTextMessage(), returnsNormally); + + final String result = log.generateTextMessage(); + expect(result, contains('[POST] Test message')); + expect(result, contains('Data: log.convert('foo'), throwsA(isA())); + + expect(() => log.generateTextMessage(), returnsNormally); + + final String result = log.generateTextMessage(); + expect(result, contains('[POST] Test message')); + expect(result, contains('Data: log.convert('foo'), throwsA(isA())); + + expect(() => log.generateTextMessage(), returnsNormally); + + final String result = log.generateTextMessage(); + expect(result, contains('[POST] Test message')); + expect(result, contains('Data: { + 'content-type': 'application/json', + }, + ), + settings: TalkerHttpLoggerSettings( + responsePen: AnsiPen()..blue(), + ), + ); + + final String result = log.generateTextMessage(); + + expect(log.pen, isNotNull); + expect(result, contains('[GET] Test message')); + expect(result, contains('Status: 200')); + expect(result, contains('Data: {\n "key": "value"\n}')); + }); + + test( + 'generateTextMessage should include message if printResponseMessage is true', + () { + final HttpResponseLog log = HttpResponseLog( + 'Test message', + response: Response( + 'responseBodyBase', + 200, + request: request, + reasonPhrase: 'OK', + ), + settings: const TalkerHttpLoggerSettings(printResponseMessage: true), + ); + + expect(log.generateTextMessage(), contains('Message: OK')); + }, + ); + + test( + 'generateTextMessage should include response time if printResponseTime is true', + () { + final HttpResponseLog log = HttpResponseLog( + 'Test message', + response: Response( + 'responseBodyBase', + 200, + request: request.copyWith( + headers: { + TalkerHttpLogger.kLogsTimeStamp: + (DateTime.timestamp().millisecondsSinceEpoch - 123) + .toString(), + }, + ), + reasonPhrase: 'OK', + ), + settings: const TalkerHttpLoggerSettings(printResponseTime: true), + ); + + expect(log.generateTextMessage(), matches(RegExp(r'Time: \d+ ms'))); + }, + ); + + test( + 'generateTextMessage should not include response time if printResponseTime is false', + () { + final HttpResponseLog log = HttpResponseLog( + 'Test message', + response: Response( + 'responseBodyBase', + 200, + request: request.copyWith( + headers: { + TalkerHttpLogger.kLogsTimeStamp: + (DateTime.timestamp().millisecondsSinceEpoch - 123) + .toString(), + }, + ), + reasonPhrase: 'OK', + ), + settings: const TalkerHttpLoggerSettings(printResponseTime: false), + ); + + expect(log.generateTextMessage(), isNot(contains('Time:'))); + }, + ); + + test( + 'generateTextMessage error should include include response time if printResponseTime is true', + () { + final HttpResponseLog log = HttpResponseLog( + 'Error title', + response: Response( + 'responseErrorBodyBase', + 404, + request: request.copyWith( + headers: { + TalkerHttpLogger.kLogsTimeStamp: + (DateTime.timestamp().millisecondsSinceEpoch - 123) + .toString(), + }, + ), + reasonPhrase: 'Error message', + ), + settings: const TalkerHttpLoggerSettings(printResponseTime: true), + ); + + expect(log.generateTextMessage(), matches(RegExp(r'Time: \d+ ms'))); + }); + + test('logLevel should return settings logLevel', () { + final HttpResponseLog log = HttpResponseLog( + 'Test message', + response: Response( + 'responseBodyBase', + 200, + request: request, + reasonPhrase: 'OK', + ), + settings: const TalkerHttpLoggerSettings(logLevel: LogLevel.warning), + ); + + expect(log.logLevel, equals(LogLevel.warning)); + }); + + test('force throw exception on convert Response headers', () { + final HttpResponseLog log = _MockHttpResponseLog( + 'Test message', + response: Response( + jsonEncode({ + 'foo': 'bar', + 'baz': 'qux', + }), + 200, + request: request, + headers: { + 'content-type': 'application/json', + }, + reasonPhrase: 'OK', + ), + settings: const TalkerHttpLoggerSettings(printResponseHeaders: true), + ); + + expect(() => log.convert('foo'), throwsA(isA())); + + expect(() => log.generateTextMessage(), returnsNormally); + + final String result = log.generateTextMessage(); + expect(result, contains('[GET] Test message')); + expect(result, contains('Headers: log.convert('foo'), throwsA(isA())); + + expect(() => log.generateTextMessage(), returnsNormally); + + final String result = log.generateTextMessage(); + expect(result, contains('[GET] Test message')); + expect(result, contains('Data: log.convert('foo'), throwsA(isA())); + + expect(() => log.generateTextMessage(), returnsNormally); + + final String result = log.generateTextMessage(); + expect(result, contains('[GET] /test')); + expect(result, contains('Status: 500')); + expect(result, contains('Message: Error message')); + expect(result, contains('Data: request.url.path == '/allowed', + ); + + final Request allowedRequestOptions = Request( + HttpMethod.GET.name, + Uri.parse('/allowed'), + ); + + final Request disallowedRequestOptions = Request( + HttpMethod.GET.name, + Uri.parse('/disallowed'), + ); + + expect(settings.requestFilter?.call(allowedRequestOptions), isTrue); + expect(settings.requestFilter?.call(disallowedRequestOptions), isFalse); + }); + + test('responseFilter should return true for successful responses', () { + final TalkerHttpLoggerSettings settings = TalkerHttpLoggerSettings( + responseFilter: (BaseResponse response) => + response.statusCode == 200); + + final Response successfulResponse = + Response('responseBodyBase', 200, request: fakeRequest); + + final Response unsuccessfulResponse = + Response('responseErrorBodyBase', 404, request: fakeRequest); + + expect(settings.responseFilter?.call(successfulResponse), isTrue); + expect(settings.responseFilter?.call(unsuccessfulResponse), isFalse); + }); + + test( + 'copyWith should create a new instance with updated values for all fields', + () { + final TalkerHttpLoggerSettings originalSettings = + TalkerHttpLoggerSettings( + printResponseData: true, + printResponseHeaders: false, + printResponseMessage: true, + printRequestData: true, + printRequestHeaders: false, + printErrorHeaders: false, + printErrorData: true, + requestPen: AnsiPen()..green(), + responsePen: AnsiPen()..cyan(), + errorPen: AnsiPen()..red(), + ); + + final TalkerHttpLoggerSettings updatedSettings = + originalSettings.copyWith( + printResponseData: false, + printRequestHeaders: true, + printErrorHeaders: true, + printErrorData: false, + requestPen: AnsiPen()..yellow(), + ); + + expect(updatedSettings.printResponseData, isFalse); + expect(updatedSettings.printResponseHeaders, isFalse); + expect(updatedSettings.printResponseMessage, isTrue); + expect(updatedSettings.printRequestData, isTrue); + expect(updatedSettings.printErrorHeaders, isTrue); + expect(updatedSettings.printErrorData, isFalse); + expect(updatedSettings.printRequestHeaders, isTrue); + expect( + updatedSettings.requestPen, + isNot(same(originalSettings.requestPen)), + ); + expect( + updatedSettings.responsePen, + equals(originalSettings.responsePen), + ); + expect( + updatedSettings.errorPen, + equals(originalSettings.errorPen), + ); + }, + ); + + test('default logLevel should be debug', () { + final TalkerHttpLoggerSettings settings = + const TalkerHttpLoggerSettings(); + expect(settings.logLevel, equals(LogLevel.debug)); + }); + + test('copyWith should preserve logLevel if not specified', () { + final TalkerHttpLoggerSettings originalSettings = + const TalkerHttpLoggerSettings(logLevel: LogLevel.warning); + final TalkerHttpLoggerSettings updatedSettings = + originalSettings.copyWith(printResponseData: false); + + expect(updatedSettings.logLevel, equals(LogLevel.warning)); + }); + + test('copyWith should update logLevel when specified', () { + final TalkerHttpLoggerSettings originalSettings = + const TalkerHttpLoggerSettings(logLevel: LogLevel.debug); + final TalkerHttpLoggerSettings updatedSettings = + originalSettings.copyWith(logLevel: LogLevel.error); + + expect(updatedSettings.logLevel, equals(LogLevel.error)); + }); + }); +} diff --git a/packages/talker_http_logger/test/talker_http_logger_test.dart b/packages/talker_http_logger/test/talker_http_logger_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/packages/talker_http_logger/test/talker_http_logger_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/talker_http_logger/tool/makefile_helpers.sh b/packages/talker_http_logger/tool/makefile_helpers.sh new file mode 100644 index 000000000..003ff2e41 --- /dev/null +++ b/packages/talker_http_logger/tool/makefile_helpers.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Opens a link with the default browser of OS (It works cross-platform) +# +## You can call it like `open_link balad.ir` to open balad website on your default browser +open_link() { + case "$(uname -s)" in + Darwin) + # macOS + open "$1" + ;; + + Linux) + # Linux: + xdg-open "$1" + ;; + + CYGWIN* | MINGW32* | MSYS* | MINGW*) + # Windows + start "$1" + ;; + + *) + echo 'Not supported OS' + ;; + esac +}