From 9db6b69ae9a27d8e81c1c0c85e5967b32b8ea3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melih=20=C3=87elik?= Date: Wed, 24 Jun 2026 17:04:33 +0300 Subject: [PATCH 1/3] feat: update fk_user_agent to version 3.0.0 with improved user agent retrieval - Updated podspec version to 3.0.0 and improved summary and description. - Added Package.swift for Swift Package Manager support. - Implemented FkUserAgentPlugin in Objective-C to retrieve device user agents. - Enhanced user agent retrieval logic to include WebView user agents. - Updated Dart API to support async property fetching and improved initialization logic. - Updated pubspec.yaml and pubspec.lock for Dart SDK and Flutter version compatibility. - Added comprehensive tests for user agent properties and initialization behavior. --- .gitignore | 2 + .idea/libraries/Dart_SDK.xml | 37 +++-- .idea/libraries/Dart_SDK.xml.bak | 19 +++ .pubignore | 6 + CHANGELOG.md | 7 +- README.md | 6 +- example/ios/Flutter/AppFrameworkInfo.plist | 2 - example/ios/Flutter/Debug.xcconfig | 1 - example/ios/Flutter/Release.xcconfig | 1 - .../Package.swift | 30 ++++ .../FlutterGeneratedPluginSwiftPackage.swift | 3 + .../Flutter/ephemeral/flutter_lldb_helper.py | 32 ++++ .../ios/Flutter/ephemeral/flutter_lldbinit | 5 + .../ephemeral/flutter_native_integration.env | 13 ++ example/ios/Podfile | 38 ----- example/ios/Podfile.lock | 22 --- example/ios/Runner.xcodeproj/project.pbxproj | 106 ++++++------- .../xcshareddata/xcschemes/Runner.xcscheme | 23 ++- .../contents.xcworkspacedata | 3 - example/ios/Runner/AppDelegate.h | 2 +- example/ios/Runner/AppDelegate.m | 5 +- example/ios/Runner/Info.plist | 25 +++ example/lib/main.dart | 10 +- example/pubspec.lock | 145 ++++++++++++------ example/pubspec.yaml | 2 +- firebase-debug.log | 48 ++++++ fk_user_agent.iml | 21 +-- ios/Assets/.gitkeep | 0 ios/fk_user_agent.podspec | 16 +- ios/fk_user_agent/Package.swift | 27 ++++ .../fk_user_agent}/FkUserAgentPlugin.m | 2 +- .../fk_user_agent}/FkUserAgentPlugin.h | 0 lib/fk_user_agent.dart | 4 +- pubspec.lock | 138 +++++++++++------ pubspec.yaml | 6 +- test/fk_user_agent_test.dart | 66 +++++++- 36 files changed, 594 insertions(+), 279 deletions(-) create mode 100644 .idea/libraries/Dart_SDK.xml.bak create mode 100644 .pubignore create mode 100644 example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift create mode 100644 example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift create mode 100644 example/ios/Flutter/ephemeral/flutter_lldb_helper.py create mode 100644 example/ios/Flutter/ephemeral/flutter_lldbinit create mode 100644 example/ios/Flutter/ephemeral/flutter_native_integration.env delete mode 100644 example/ios/Podfile delete mode 100644 example/ios/Podfile.lock create mode 100644 firebase-debug.log delete mode 100644 ios/Assets/.gitkeep create mode 100644 ios/fk_user_agent/Package.swift rename ios/{Classes => fk_user_agent/Sources/fk_user_agent}/FkUserAgentPlugin.m (99%) rename ios/{Classes => fk_user_agent/Sources/fk_user_agent/include/fk_user_agent}/FkUserAgentPlugin.h (100%) diff --git a/.gitignore b/.gitignore index e9dc58d..a9673bf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ .pub/ build/ +.build/ +.swiftpm/ diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml index 7d5efcd..e23b6e8 100644 --- a/.idea/libraries/Dart_SDK.xml +++ b/.idea/libraries/Dart_SDK.xml @@ -1,19 +1,30 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + + \ No newline at end of file diff --git a/.idea/libraries/Dart_SDK.xml.bak b/.idea/libraries/Dart_SDK.xml.bak new file mode 100644 index 0000000..7d5efcd --- /dev/null +++ b/.idea/libraries/Dart_SDK.xml.bak @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.pubignore b/.pubignore new file mode 100644 index 0000000..9eef9a1 --- /dev/null +++ b/.pubignore @@ -0,0 +1,6 @@ +build/ +.idea/ +*.iml +*.bak +firebase-debug.log +example/ios/Flutter/ephemeral/ diff --git a/CHANGELOG.md b/CHANGELOG.md index dbfd18c..b25ebdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -# 2.1.0 +# 3.0.0 +* breaking: add Swift Package Manager support for iOS plugins +* breaking: raise minimum support to Dart 3.11+, Flutter 3.41+, and iOS 13+ +* support: keep CocoaPods compatibility while adding Swift Package Manager packaging + +## 2.1.0 * fix: #4 ## 2.0.1 diff --git a/README.md b/README.md index d31300c..5abf5ad 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Retrieve Android/iOS device user agents in Flutter. +iOS now supports both Swift Package Manager and CocoaPods. + +This release raises the minimum supported toolchain to Dart 3.11+, Flutter 3.41+, and iOS 13+. + ### Example user-agents: | System | User-Agent | WebView User-Agent | @@ -42,4 +46,4 @@ Android version returns: ### Credits 👍 -Based of https://github.com/j0j00/flutter_user_agent \ No newline at end of file +Based of https://github.com/j0j00/flutter_user_agent diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9367d48..391a902 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 8.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index ec97fc6..592ceee 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index c4855bf..592ceee 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift new file mode 100644 index 0000000..5e4e1ce --- /dev/null +++ b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"]) + ], + dependencies: [ + .package(name: "fk_user_agent", path: "../.packages/fk_user_agent"), + .package(name: "FlutterFramework", path: "../.packages/FlutterFramework") + ], + targets: [ + .target( + name: "FlutterGeneratedPluginSwiftPackage", + dependencies: [ + .product(name: "fk-user-agent", package: "fk_user_agent"), + .product(name: "FlutterFramework", package: "FlutterFramework") + ] + ) + ] +) diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift new file mode 100644 index 0000000..a30d95a --- /dev/null +++ b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift @@ -0,0 +1,3 @@ +// +// Generated file. Do not edit. +// diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/example/ios/Flutter/ephemeral/flutter_native_integration.env b/example/ios/Flutter/ephemeral/flutter_native_integration.env new file mode 100644 index 0000000..272e4a4 --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_native_integration.env @@ -0,0 +1,13 @@ +FLUTTER_ROOT=/Users/melihcelik/.puro/envs/3.44.0/flutter +FLUTTER_APPLICATION_PATH=/Users/melihcelik/Desktop/fk_user_agent/example +FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/melihcelik/Desktop/fk_user_agent/example/ios/Flutter/ephemeral/Packages/.packages/FlutterFramework +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuNDQuMA==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NTU5ZmZhM2Y3NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NGM1MjVkYWM1ZQ==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMi4w +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=false +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=/Users/melihcelik/Desktop/fk_user_agent/example/.dart_tool/package_config.json diff --git a/example/ios/Podfile b/example/ios/Podfile deleted file mode 100644 index f7d6a5e..0000000 --- a/example/ios/Podfile +++ /dev/null @@ -1,38 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '9.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 - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock deleted file mode 100644 index bd94aa0..0000000 --- a/example/ios/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - fk_user_agent (2.0.0): - - Flutter - - Flutter (1.0.0) - -DEPENDENCIES: - - fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`) - - Flutter (from `Flutter`) - -EXTERNAL SOURCES: - fk_user_agent: - :path: ".symlinks/plugins/fk_user_agent/ios" - Flutter: - :path: Flutter - -SPEC CHECKSUMS: - fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545 - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c - -PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d - -COCOAPODS: 1.10.1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 9c68879..95f271d 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,13 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 85A618B28BC7FABA93E3AB01 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 73E6B311D8DE1E2206C57141 /* libPods-Runner.a */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -34,9 +34,9 @@ 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 48C0D7608BE73E326DE5C79E /* 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 = ""; }; - 560661FA8E080455ADDEE653 /* 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 = ""; }; - 73E6B311D8DE1E2206C57141 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; }; + 78DABEA22ED26510000E7860 /* fk_user_agent */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = fk_user_agent; path = ../../ios/fk_user_agent; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -48,7 +48,6 @@ 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 = ""; }; - DD7B8A60E2A7402BA6E6098E /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,35 +55,19 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 85A618B28BC7FABA93E3AB01 /* libPods-Runner.a in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 17E06F9FD7C8897F04046A74 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 73E6B311D8DE1E2206C57141 /* libPods-Runner.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 79916955F1AA8E1535843181 /* Pods */ = { - isa = PBXGroup; - children = ( - DD7B8A60E2A7402BA6E6098E /* Pods-Runner.debug.xcconfig */, - 48C0D7608BE73E326DE5C79E /* Pods-Runner.release.xcconfig */, - 560661FA8E080455ADDEE653 /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78DABEA22ED26510000E7860 /* fk_user_agent */, + 784666492D4C4C64000A1A5F /* FlutterFramework */, + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -99,8 +82,6 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 79916955F1AA8E1535843181 /* Pods */, - 17E06F9FD7C8897F04046A74 /* Frameworks */, ); sourceTree = ""; }; @@ -143,7 +124,6 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 8A7B39775DC5546369E54F7A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -156,6 +136,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -166,7 +149,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -183,6 +166,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -209,10 +195,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -221,30 +209,9 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 8A7B39775DC5546369E54F7A /* [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; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -333,7 +300,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -350,7 +317,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = io.flutterfastkit.fkUserAgentExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; @@ -404,7 +374,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -453,7 +423,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -470,7 +440,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = io.flutterfastkit.fkUserAgentExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; @@ -485,7 +458,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = io.flutterfastkit.fkUserAgentExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; @@ -516,6 +492,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..0bd6d42 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -45,11 +64,13 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc1..1d526a1 100644 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,7 +4,4 @@ - - diff --git a/example/ios/Runner/AppDelegate.h b/example/ios/Runner/AppDelegate.h index 36e21bb..01e6e1d 100644 --- a/example/ios/Runner/AppDelegate.h +++ b/example/ios/Runner/AppDelegate.h @@ -1,6 +1,6 @@ #import #import -@interface AppDelegate : FlutterAppDelegate +@interface AppDelegate : FlutterAppDelegate @end diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m index 70e8393..cebafc9 100644 --- a/example/ios/Runner/AppDelegate.m +++ b/example/ios/Runner/AppDelegate.m @@ -5,9 +5,12 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } +- (void)didInitializeImplicitFlutterEngine:(NSObject*)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + @end diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 9336ef5..f76e30d 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -22,6 +24,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/example/lib/main.dart b/example/lib/main.dart index 003e17c..0641787 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,7 +19,7 @@ class _MyAppState extends State { @override void initState() { super.initState(); - WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await FkUserAgent.init(); initPlatformState(); }); @@ -50,12 +50,8 @@ class _MyAppState extends State { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: Text('Running on: $_platformVersion\n'), - ), + appBar: AppBar(title: const Text('Plugin example app')), + body: Center(child: Text('Running on: $_platformVersion\n')), ), ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 6dd4be9..63aadb0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,65 +5,65 @@ packages: dependency: transitive description: name: async - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" characters: dependency: transitive description: name: characters - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" collection: dependency: transitive description: name: collection - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.flutter-io.cn" + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.9" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.3" fk_user_agent: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.0.0" + version: "3.0.0" flutter: dependency: "direct main" description: flutter @@ -74,88 +74,131 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" matcher: dependency: transitive description: name: matcher - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.12.10" + version: "0.13.0" meta: dependency: transitive description: name: meta - url: "https://pub.flutter-io.cn" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.18.0" path: dependency: transitive description: name: path - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - url: "https://pub.flutter-io.cn" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" source: hosted - version: "0.2.19" - typed_data: + version: "0.7.11" + vector_math: dependency: transitive description: - name: typed_data - url: "https://pub.flutter-io.cn" + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "1.3.0" - vector_math: + version: "2.2.0" + vm_service: dependency: transitive description: - name: vector_math - url: "https://pub.flutter-io.cn" + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "15.2.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.41.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d4a032d..8a3568c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the fk_user_agent plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.11.0 dependencies: flutter: diff --git a/firebase-debug.log b/firebase-debug.log new file mode 100644 index 0000000..e73c8e5 --- /dev/null +++ b/firebase-debug.log @@ -0,0 +1,48 @@ +[debug] [2026-06-24T13:45:54.858Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:45:54.860Z] > authorizing via signed-in user (melih.celik@hubx.co) +[debug] [2026-06-24T13:45:54.872Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:45:54.872Z] > authorizing via signed-in user (melih.celik@hubx.co) +[debug] [2026-06-24T13:45:54.886Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:45:54.886Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:45:54.886Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] +[debug] [2026-06-24T13:45:54.886Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= +[debug] [2026-06-24T13:45:54.900Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:45:54.900Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:45:54.900Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] +[debug] [2026-06-24T13:45:54.900Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= +[debug] [2026-06-24T13:45:56.652Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 +[debug] [2026-06-24T13:45:56.652Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] +[debug] [2026-06-24T13:45:56.655Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:45:56.655Z] > authorizing via signed-in user (melih.celik@hubx.co) +[debug] [2026-06-24T13:45:56.655Z] >>> [apiv2][query] POST https://developerknowledge.googleapis.com/mcp [none] +[debug] [2026-06-24T13:45:56.655Z] >>> [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"method":"tools/list","jsonrpc":"2.0","id":1} +[debug] [2026-06-24T13:45:56.737Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 +[debug] [2026-06-24T13:45:56.737Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] +[debug] [2026-06-24T13:45:57.025Z] <<< [apiv2][status] POST https://developerknowledge.googleapis.com/mcp 200 +[debug] [2026-06-24T13:45:57.025Z] <<< [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"id":1,"jsonrpc":"2.0","result":{"tools":[{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to find documentation about Google developer products. The documents contain official APIs, code snippets, release notes, best practices, guides, debugging info, and more. It covers the following products and domains:\n\n\n* ADK: adk.dev\n\n* Android: developer.android.com\n\n* Apigee: docs.apigee.com\n\n* Chrome: developer.chrome.com\n\n* Dart: dart.dev\n\n* Firebase: firebase.google.com\n\n* Flutter: docs.flutter.dev\n\n* Fuchsia: fuchsia.dev\n\n* Gemini CLI: geminicli.com\n\n* Go: go.dev\n\n* Google AI: ai.google.dev\n\n* Google Antigravity: antigravity.google\n\n* Google Cloud: cloud.google.com & docs.cloud.google.com\n\n* Google Developers, Ads, Search, Google Maps, Youtube: developers.google.com\n\n* Google Home: developers.home.google.com\n\n* Google Maps Platform: mapsplatform.google.com\n\n* TensorFlow: www.tensorflow.org\n\n* Web: web.dev\n\n\nThis tool returns chunks of text, names, and URLs for matching documents. If the returned chunks are not detailed enough to answer the user's question, use `get_documents` with the `parent` from this tool's output to retrieve the full document content.\n","inputSchema":{"description":"Request schema for search_documents. Use the query field to search for related Google developer documentation.","properties":{"query":{"description":"Required. The raw query string provided by the user, such as \"How to create a Cloud Storage bucket?\".","type":"string"}},"required":["query"],"type":"object"},"name":"search_documents","outputSchema":{"$defs":{"DocumentChunk":{"description":"A DocumentChunk represents a piece of content from a Document in the DeveloperKnowledge corpus. To fetch the entire document content, pass the `parent` to get_document or batch_get_documents.","properties":{"content":{"description":"Output only. The content of the document chunk.","readOnly":true,"type":"string"},"id":{"description":"Output only. The ID of this chunk within the document. The chunk ID is unique within a document, but not globally unique across documents. The chunk ID is not stable and may change over time.","readOnly":true,"type":"string"},"parent":{"description":"Output only. The resource name of the document this chunk is from. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for search_documents.","properties":{"results":{"description":"The search results for the given query. Each Document in this list contains a snippet of content relevant to the search query. Use the DocumentChunk.name field of each result with get_documents to retrieve the full document content.","items":{"$ref":"#/$defs/DocumentChunk"},"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use answer_query to get a grounded answer to a query about Google developer products. This tool has limited quota. This tool will synthesize information from the corpus to generate an answer to the query. answer_query grounds answers using the same corpus as search_documents. \nThis tool returns the generated answer_text and a list of document names (references) used to generate the answer. Use get_documents with the document names to fetch the entire document content if needed.\n\nIf you get a 429 out of quota error, use search_documents instead.\n","inputSchema":{"description":"Request message for AnswerQuery.","properties":{"query":{"description":"Required. The query to answer.","type":"string"}},"required":["query"],"type":"object"},"name":"answer_query","outputSchema":{"description":"Response message for AnswerQuery.","properties":{"answerText":{"description":"The answer to the query.","type":"string"},"references":{"description":"Output only. The resource names of the documents used to generate the answer.","items":{"type":"string"},"readOnly":true,"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to retrieve the full content of a single document or up to 20 documents in a single call. The document names should be obtained from the `parent` field of results from a call to the `search_documents` tool. Set the `names` parameter to a list of document names.","inputSchema":{"description":"Request schema for get_documents.","properties":{"names":{"description":"Required. The names of the documents to retrieve, as returned by search_documents. A maximum of 20 documents can be retrieved in one call. The documents are returned in the same order as the `names` in the request. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","items":{"type":"string"},"type":"array"}},"required":["names"],"type":"object"},"name":"get_documents","outputSchema":{"$defs":{"Document":{"description":"A Document represents a piece of content from the Developer Knowledge corpus.","properties":{"content":{"description":"Output only. The content of the document in Markdown format.","readOnly":true,"type":"string"},"description":{"description":"Output only. A description of the document.","readOnly":true,"type":"string"},"name":{"description":"Identifier. The resource name of the document. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","type":"string","x-google-identifier":true},"title":{"description":"Output only. The title of the document.","readOnly":true,"type":"string"},"uri":{"description":"Output only. The URI of the content, such as `https://cloud.google.com/storage/docs/creating-buckets`.","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for get_documents.","properties":{"documents":{"description":"Documents requested.","items":{"$ref":"#/$defs/Document"},"type":"array"}},"type":"object"}}]}} +[debug] [2026-06-24T13:45:57.026Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:45:57.026Z] > authorizing via signed-in user (melih.celik@hubx.co) +[debug] [2026-06-24T13:46:12.218Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:46:12.219Z] > authorizing via signed-in user (melih.celik@hubx.co) +[debug] [2026-06-24T13:46:12.229Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:46:12.229Z] > authorizing via signed-in user (melih.celik@hubx.co) +[debug] [2026-06-24T13:46:12.243Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:46:12.243Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:46:12.243Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] +[debug] [2026-06-24T13:46:12.243Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= +[debug] [2026-06-24T13:46:12.256Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:46:12.256Z] Checked if tokens are valid: true, expires at: 1782310830938 +[debug] [2026-06-24T13:46:12.256Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] +[debug] [2026-06-24T13:46:12.256Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= +[debug] [2026-06-24T13:46:13.178Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 +[debug] [2026-06-24T13:46:13.178Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] +[debug] [2026-06-24T13:46:13.179Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:46:13.179Z] > authorizing via signed-in user (melih.celik@hubx.co) +[debug] [2026-06-24T13:46:13.180Z] >>> [apiv2][query] POST https://developerknowledge.googleapis.com/mcp [none] +[debug] [2026-06-24T13:46:13.180Z] >>> [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"method":"tools/list","jsonrpc":"2.0","id":1} +[debug] [2026-06-24T13:46:13.189Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 +[debug] [2026-06-24T13:46:13.189Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] +[debug] [2026-06-24T13:46:13.529Z] <<< [apiv2][status] POST https://developerknowledge.googleapis.com/mcp 200 +[debug] [2026-06-24T13:46:13.529Z] <<< [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"id":1,"jsonrpc":"2.0","result":{"tools":[{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to find documentation about Google developer products. The documents contain official APIs, code snippets, release notes, best practices, guides, debugging info, and more. It covers the following products and domains:\n\n\n* ADK: adk.dev\n\n* Android: developer.android.com\n\n* Apigee: docs.apigee.com\n\n* Chrome: developer.chrome.com\n\n* Dart: dart.dev\n\n* Firebase: firebase.google.com\n\n* Flutter: docs.flutter.dev\n\n* Fuchsia: fuchsia.dev\n\n* Gemini CLI: geminicli.com\n\n* Go: go.dev\n\n* Google AI: ai.google.dev\n\n* Google Antigravity: antigravity.google\n\n* Google Cloud: cloud.google.com & docs.cloud.google.com\n\n* Google Developers, Ads, Search, Google Maps, Youtube: developers.google.com\n\n* Google Home: developers.home.google.com\n\n* Google Maps Platform: mapsplatform.google.com\n\n* TensorFlow: www.tensorflow.org\n\n* Web: web.dev\n\n\nThis tool returns chunks of text, names, and URLs for matching documents. If the returned chunks are not detailed enough to answer the user's question, use `get_documents` with the `parent` from this tool's output to retrieve the full document content.\n","inputSchema":{"description":"Request schema for search_documents. Use the query field to search for related Google developer documentation.","properties":{"query":{"description":"Required. The raw query string provided by the user, such as \"How to create a Cloud Storage bucket?\".","type":"string"}},"required":["query"],"type":"object"},"name":"search_documents","outputSchema":{"$defs":{"DocumentChunk":{"description":"A DocumentChunk represents a piece of content from a Document in the DeveloperKnowledge corpus. To fetch the entire document content, pass the `parent` to get_document or batch_get_documents.","properties":{"content":{"description":"Output only. The content of the document chunk.","readOnly":true,"type":"string"},"id":{"description":"Output only. The ID of this chunk within the document. The chunk ID is unique within a document, but not globally unique across documents. The chunk ID is not stable and may change over time.","readOnly":true,"type":"string"},"parent":{"description":"Output only. The resource name of the document this chunk is from. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for search_documents.","properties":{"results":{"description":"The search results for the given query. Each Document in this list contains a snippet of content relevant to the search query. Use the DocumentChunk.name field of each result with get_documents to retrieve the full document content.","items":{"$ref":"#/$defs/DocumentChunk"},"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use answer_query to get a grounded answer to a query about Google developer products. This tool has limited quota. This tool will synthesize information from the corpus to generate an answer to the query. answer_query grounds answers using the same corpus as search_documents. \nThis tool returns the generated answer_text and a list of document names (references) used to generate the answer. Use get_documents with the document names to fetch the entire document content if needed.\n\nIf you get a 429 out of quota error, use search_documents instead.\n","inputSchema":{"description":"Request message for AnswerQuery.","properties":{"query":{"description":"Required. The query to answer.","type":"string"}},"required":["query"],"type":"object"},"name":"answer_query","outputSchema":{"description":"Response message for AnswerQuery.","properties":{"answerText":{"description":"The answer to the query.","type":"string"},"references":{"description":"Output only. The resource names of the documents used to generate the answer.","items":{"type":"string"},"readOnly":true,"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to retrieve the full content of a single document or up to 20 documents in a single call. The document names should be obtained from the `parent` field of results from a call to the `search_documents` tool. Set the `names` parameter to a list of document names.","inputSchema":{"description":"Request schema for get_documents.","properties":{"names":{"description":"Required. The names of the documents to retrieve, as returned by search_documents. A maximum of 20 documents can be retrieved in one call. The documents are returned in the same order as the `names` in the request. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","items":{"type":"string"},"type":"array"}},"required":["names"],"type":"object"},"name":"get_documents","outputSchema":{"$defs":{"Document":{"description":"A Document represents a piece of content from the Developer Knowledge corpus.","properties":{"content":{"description":"Output only. The content of the document in Markdown format.","readOnly":true,"type":"string"},"description":{"description":"Output only. A description of the document.","readOnly":true,"type":"string"},"name":{"description":"Identifier. The resource name of the document. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","type":"string","x-google-identifier":true},"title":{"description":"Output only. The title of the document.","readOnly":true,"type":"string"},"uri":{"description":"Output only. The URI of the content, such as `https://cloud.google.com/storage/docs/creating-buckets`.","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for get_documents.","properties":{"documents":{"description":"Documents requested.","items":{"$ref":"#/$defs/Document"},"type":"array"}},"type":"object"}}]}} +[debug] [2026-06-24T13:46:13.530Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2026-06-24T13:46:13.530Z] > authorizing via signed-in user (melih.celik@hubx.co) diff --git a/fk_user_agent.iml b/fk_user_agent.iml index be36c1d..3c78e7e 100644 --- a/fk_user_agent.iml +++ b/fk_user_agent.iml @@ -1,17 +1,18 @@ - + - - - - - - + + + + + + - - - + + + + \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ios/fk_user_agent.podspec b/ios/fk_user_agent.podspec index 60cbe9e..29b590e 100644 --- a/ios/fk_user_agent.podspec +++ b/ios/fk_user_agent.podspec @@ -4,19 +4,19 @@ # Pod::Spec.new do |s| s.name = 'fk_user_agent' - s.version = '2.0.0' - s.summary = 'A new Flutter project.' + s.version = '3.0.0' + s.summary = 'Flutter plugin for retrieving device user agents.' s.description = <<-DESC -Retrieve Android/iOS device user agents in Flutter. +Retrieve Android and iOS device user agents, including WebView user agents, from Flutter. DESC s.homepage = 'https://github.com/flutter-fast-kit/fk_user_agent' - s.license = { :file => '../LICENSE' } + s.license = { :type => 'MIT', :file => '../LICENSE' } s.author = { 'lunnnnul' => 'lunnnnul@gmail.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' + s.source = { :git => 'https://github.com/flutter-fast-kit/fk_user_agent.git', :tag => s.version.to_s } + s.source_files = 'fk_user_agent/Sources/fk_user_agent/**/*.{h,m}' + s.public_header_files = 'fk_user_agent/Sources/fk_user_agent/include/**/*.h' s.dependency 'Flutter' - s.platform = :ios, '8.0' + s.platform = :ios, '13.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/ios/fk_user_agent/Package.swift b/ios/fk_user_agent/Package.swift new file mode 100644 index 0000000..0f417a1 --- /dev/null +++ b/ios/fk_user_agent/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "fk_user_agent", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "fk-user-agent", targets: ["fk_user_agent"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework") + ], + targets: [ + .target( + name: "fk_user_agent", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework") + ], + cSettings: [ + .headerSearchPath("include/fk_user_agent") + ] + ) + ] +) diff --git a/ios/Classes/FkUserAgentPlugin.m b/ios/fk_user_agent/Sources/fk_user_agent/FkUserAgentPlugin.m similarity index 99% rename from ios/Classes/FkUserAgentPlugin.m rename to ios/fk_user_agent/Sources/fk_user_agent/FkUserAgentPlugin.m index 2bea706..bd57e3a 100644 --- a/ios/Classes/FkUserAgentPlugin.m +++ b/ios/fk_user_agent/Sources/fk_user_agent/FkUserAgentPlugin.m @@ -1,4 +1,4 @@ -#import "FkUserAgentPlugin.h" +#import "./include/fk_user_agent/FkUserAgentPlugin.h" @implementation FkUserAgentPlugin diff --git a/ios/Classes/FkUserAgentPlugin.h b/ios/fk_user_agent/Sources/fk_user_agent/include/fk_user_agent/FkUserAgentPlugin.h similarity index 100% rename from ios/Classes/FkUserAgentPlugin.h rename to ios/fk_user_agent/Sources/fk_user_agent/include/fk_user_agent/FkUserAgentPlugin.h diff --git a/lib/fk_user_agent.dart b/lib/fk_user_agent.dart index 22c8183..4fda37b 100644 --- a/lib/fk_user_agent.dart +++ b/lib/fk_user_agent.dart @@ -13,7 +13,7 @@ class FkUserAgent { /// /// Set [force] to true if you want to refetch the user agent properties from /// the native platform. - static Future init({force: false}) async { + static Future init({bool force = false}) async { if (_properties == null || force) { _properties = Map.unmodifiable(await (_channel.invokeMethod('getProperties'))); } @@ -41,7 +41,7 @@ class FkUserAgent { } /// Fetch a [property] asynchronously that can be used to build your own user agent string. - static dynamic getPropertyAsync(String property) async { + static Future getPropertyAsync(String property) async { await init(); return _properties![property]; } diff --git a/pubspec.lock b/pubspec.lock index 6c77e39..b3f9beb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,51 +5,50 @@ packages: dependency: transitive description: name: async - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" characters: dependency: transitive description: name: characters - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" collection: dependency: transitive description: name: collection - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -60,88 +59,131 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" matcher: dependency: transitive description: name: matcher - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.12.10" + version: "0.13.0" meta: dependency: transitive description: name: meta - url: "https://pub.flutter-io.cn" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.18.0" path: dependency: transitive description: name: path - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - url: "https://pub.flutter-io.cn" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" source: hosted - version: "0.2.19" - typed_data: + version: "0.7.11" + vector_math: dependency: transitive description: - name: typed_data - url: "https://pub.flutter-io.cn" + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "1.3.0" - vector_math: + version: "2.2.0" + vm_service: dependency: transitive description: - name: vector_math - url: "https://pub.flutter-io.cn" + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "15.2.0" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.41.0" diff --git a/pubspec.yaml b/pubspec.yaml index f3e54f6..b25fd68 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: fk_user_agent description: Retrieve Android/iOS device user agents in Flutter. -version: 2.1.0 +version: 3.0.0 homepage: https://github.com/flutter-fast-kit/fk_user_agent environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" + sdk: ^3.11.0 + flutter: ">=3.41.0" dependencies: flutter: diff --git a/test/fk_user_agent_test.dart b/test/fk_user_agent_test.dart index ea86cf1..ae2cc29 100644 --- a/test/fk_user_agent_test.dart +++ b/test/fk_user_agent_test.dart @@ -1,3 +1,4 @@ +import 'package:fk_user_agent/fk_user_agent.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -6,17 +7,72 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); + late int invocationCount; + + Map responseForCall(int callNumber) { + return { + 'userAgent': 'agent-$callNumber', + 'webViewUserAgent': 'web-$callNumber', + 'buildNumber': '$callNumber', + }; + } + setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; + invocationCount = 0; + FkUserAgent.release(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(methodCall.method, 'getProperties'); + invocationCount += 1; + return responseForCall(invocationCount); }); }); tearDown(() { - channel.setMockMethodCallHandler(null); + FkUserAgent.release(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + test('init caches properties and populates getters', () async { + await FkUserAgent.init(); + await FkUserAgent.init(); + + expect(invocationCount, 1); + expect(FkUserAgent.userAgent, 'agent-1'); + expect(FkUserAgent.webViewUserAgent, 'web-1'); + expect(FkUserAgent.getProperty('buildNumber'), '1'); + }); + + test('force init refetches native properties', () async { + await FkUserAgent.init(); + await FkUserAgent.init(force: true); + + expect(invocationCount, 2); + expect(FkUserAgent.userAgent, 'agent-2'); }); - test('getPlatformVersion', () async { - // expect(await FkUserAgent.platformVersion, '42'); + test('release clears cached properties', () async { + await FkUserAgent.init(); + + FkUserAgent.release(); + + expect(FkUserAgent.properties, isNull); + }); + + test('getPropertyAsync initializes lazily', () async { + final dynamic buildNumber = await FkUserAgent.getPropertyAsync('buildNumber'); + + expect(invocationCount, 1); + expect(buildNumber, '1'); + }); + + test('properties map is unmodifiable', () async { + await FkUserAgent.init(); + + expect( + () => FkUserAgent.properties!['anotherKey'] = 'value', + throwsUnsupportedError, + ); }); } From 8c937f7e278fae65594ff718d2a438770866e610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melih=20=C3=87elik?= Date: Mon, 6 Jul 2026 02:26:09 +0300 Subject: [PATCH 2/3] security: remove ephemeral/debug files and harden .gitignore - Remove firebase-debug.log (contained developer email and OAuth metadata) - Remove .idea/libraries/Dart_SDK.xml.bak (stray backup with local SDK paths) - Remove example/ios/Flutter/ephemeral/ entirely: flutter_native_integration.env (absolute local paths in DART_DEFINES) flutter_lldb_helper.py (Flutter-generated LLDB tooling) flutter_lldbinit (LLDB init file) Packages/FlutterGeneratedPluginSwiftPackage/* (generated SwiftPM package) - Add firebase-debug.log, *.bak, .idea/, and example/ios/Flutter/ephemeral/ to root .gitignore - Mirror ephemeral/ and firebase-debug.log patterns in example/.gitignore These files were already listed in .pubignore but were missing from .gitignore, allowing them to persist in the git index. --- .gitignore | 10 ++++ .idea/libraries/Dart_SDK.xml.bak | 19 -------- example/.gitignore | 7 +++ .../Package.swift | 30 ------------ .../FlutterGeneratedPluginSwiftPackage.swift | 3 -- .../Flutter/ephemeral/flutter_lldb_helper.py | 32 ------------- .../ios/Flutter/ephemeral/flutter_lldbinit | 5 -- .../ephemeral/flutter_native_integration.env | 13 ----- firebase-debug.log | 48 ------------------- 9 files changed, 17 insertions(+), 150 deletions(-) delete mode 100644 .idea/libraries/Dart_SDK.xml.bak delete mode 100644 example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift delete mode 100644 example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift delete mode 100644 example/ios/Flutter/ephemeral/flutter_lldb_helper.py delete mode 100644 example/ios/Flutter/ephemeral/flutter_lldbinit delete mode 100644 example/ios/Flutter/ephemeral/flutter_native_integration.env delete mode 100644 firebase-debug.log diff --git a/.gitignore b/.gitignore index a9673bf..9d3e9eb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,13 @@ build/ .build/ .swiftpm/ + +# Firebase — contains developer email, OAuth metadata, internal endpoints +firebase-debug.log + +# IntelliJ/IDE backup and generated files +*.bak +.idea/ + +# Flutter iOS ephemeral (generated per-build, may contain absolute local paths) +example/ios/Flutter/ephemeral/ diff --git a/.idea/libraries/Dart_SDK.xml.bak b/.idea/libraries/Dart_SDK.xml.bak deleted file mode 100644 index 7d5efcd..0000000 --- a/.idea/libraries/Dart_SDK.xml.bak +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore index 0fa6b67..caaf16a 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -44,3 +44,10 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Flutter iOS — generated per-build, may contain absolute local paths / OAuth metadata +ios/Flutter/ephemeral/ +ios/Flutter/flutter_export_environment.sh + +# Firebase debug log — contains developer email and OAuth metadata +firebase-debug.log diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift deleted file mode 100644 index 5e4e1ce..0000000 --- a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift +++ /dev/null @@ -1,30 +0,0 @@ -// swift-tools-version: 5.9 -// The swift-tools-version declares the minimum version of Swift required to build this package. -// -// Generated file. Do not edit. -// - -import PackageDescription - -let package = Package( - name: "FlutterGeneratedPluginSwiftPackage", - platforms: [ - .iOS("13.0") - ], - products: [ - .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"]) - ], - dependencies: [ - .package(name: "fk_user_agent", path: "../.packages/fk_user_agent"), - .package(name: "FlutterFramework", path: "../.packages/FlutterFramework") - ], - targets: [ - .target( - name: "FlutterGeneratedPluginSwiftPackage", - dependencies: [ - .product(name: "fk-user-agent", package: "fk_user_agent"), - .product(name: "FlutterFramework", package: "FlutterFramework") - ] - ) - ] -) diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift deleted file mode 100644 index a30d95a..0000000 --- a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift +++ /dev/null @@ -1,3 +0,0 @@ -// -// Generated file. Do not edit. -// diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py deleted file mode 100644 index a88caf9..0000000 --- a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Generated file, do not edit. -# - -import lldb - -def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): - """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" - base = frame.register["x0"].GetValueAsAddress() - page_len = frame.register["x1"].GetValueAsUnsigned() - - # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the - # first page to see if handled it correctly. This makes diagnosing - # misconfiguration (e.g. missing breakpoint) easier. - data = bytearray(page_len) - data[0:8] = b'IHELPED!' - - error = lldb.SBError() - frame.GetThread().GetProcess().WriteMemory(base, data, error) - if not error.Success(): - print(f'Failed to write into {base}[+{page_len}]', error) - return - -def __lldb_init_module(debugger: lldb.SBDebugger, _): - target = debugger.GetDummyTarget() - # Caveat: must use BreakpointCreateByRegEx here and not - # BreakpointCreateByName. For some reasons callback function does not - # get carried over from dummy target for the later. - bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") - bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) - bp.SetAutoContinue(True) - print("-- LLDB integration loaded --") diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit deleted file mode 100644 index e3ba6fb..0000000 --- a/example/ios/Flutter/ephemeral/flutter_lldbinit +++ /dev/null @@ -1,5 +0,0 @@ -# -# Generated file, do not edit. -# - -command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/example/ios/Flutter/ephemeral/flutter_native_integration.env b/example/ios/Flutter/ephemeral/flutter_native_integration.env deleted file mode 100644 index 272e4a4..0000000 --- a/example/ios/Flutter/ephemeral/flutter_native_integration.env +++ /dev/null @@ -1,13 +0,0 @@ -FLUTTER_ROOT=/Users/melihcelik/.puro/envs/3.44.0/flutter -FLUTTER_APPLICATION_PATH=/Users/melihcelik/Desktop/fk_user_agent/example -FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/melihcelik/Desktop/fk_user_agent/example/ios/Flutter/ephemeral/Packages/.packages/FlutterFramework -COCOAPODS_PARALLEL_CODE_SIGN=true -FLUTTER_TARGET=lib/main.dart -FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=1.0.0 -FLUTTER_BUILD_NUMBER=1 -DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuNDQuMA==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NTU5ZmZhM2Y3NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NGM1MjVkYWM1ZQ==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMi4w -DART_OBFUSCATION=false -TRACK_WIDGET_CREATION=false -TREE_SHAKE_ICONS=false -PACKAGE_CONFIG=/Users/melihcelik/Desktop/fk_user_agent/example/.dart_tool/package_config.json diff --git a/firebase-debug.log b/firebase-debug.log deleted file mode 100644 index e73c8e5..0000000 --- a/firebase-debug.log +++ /dev/null @@ -1,48 +0,0 @@ -[debug] [2026-06-24T13:45:54.858Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:45:54.860Z] > authorizing via signed-in user (melih.celik@hubx.co) -[debug] [2026-06-24T13:45:54.872Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:45:54.872Z] > authorizing via signed-in user (melih.celik@hubx.co) -[debug] [2026-06-24T13:45:54.886Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:45:54.886Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:45:54.886Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] -[debug] [2026-06-24T13:45:54.886Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= -[debug] [2026-06-24T13:45:54.900Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:45:54.900Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:45:54.900Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] -[debug] [2026-06-24T13:45:54.900Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= -[debug] [2026-06-24T13:45:56.652Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 -[debug] [2026-06-24T13:45:56.652Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] -[debug] [2026-06-24T13:45:56.655Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:45:56.655Z] > authorizing via signed-in user (melih.celik@hubx.co) -[debug] [2026-06-24T13:45:56.655Z] >>> [apiv2][query] POST https://developerknowledge.googleapis.com/mcp [none] -[debug] [2026-06-24T13:45:56.655Z] >>> [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"method":"tools/list","jsonrpc":"2.0","id":1} -[debug] [2026-06-24T13:45:56.737Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 -[debug] [2026-06-24T13:45:56.737Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] -[debug] [2026-06-24T13:45:57.025Z] <<< [apiv2][status] POST https://developerknowledge.googleapis.com/mcp 200 -[debug] [2026-06-24T13:45:57.025Z] <<< [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"id":1,"jsonrpc":"2.0","result":{"tools":[{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to find documentation about Google developer products. The documents contain official APIs, code snippets, release notes, best practices, guides, debugging info, and more. It covers the following products and domains:\n\n\n* ADK: adk.dev\n\n* Android: developer.android.com\n\n* Apigee: docs.apigee.com\n\n* Chrome: developer.chrome.com\n\n* Dart: dart.dev\n\n* Firebase: firebase.google.com\n\n* Flutter: docs.flutter.dev\n\n* Fuchsia: fuchsia.dev\n\n* Gemini CLI: geminicli.com\n\n* Go: go.dev\n\n* Google AI: ai.google.dev\n\n* Google Antigravity: antigravity.google\n\n* Google Cloud: cloud.google.com & docs.cloud.google.com\n\n* Google Developers, Ads, Search, Google Maps, Youtube: developers.google.com\n\n* Google Home: developers.home.google.com\n\n* Google Maps Platform: mapsplatform.google.com\n\n* TensorFlow: www.tensorflow.org\n\n* Web: web.dev\n\n\nThis tool returns chunks of text, names, and URLs for matching documents. If the returned chunks are not detailed enough to answer the user's question, use `get_documents` with the `parent` from this tool's output to retrieve the full document content.\n","inputSchema":{"description":"Request schema for search_documents. Use the query field to search for related Google developer documentation.","properties":{"query":{"description":"Required. The raw query string provided by the user, such as \"How to create a Cloud Storage bucket?\".","type":"string"}},"required":["query"],"type":"object"},"name":"search_documents","outputSchema":{"$defs":{"DocumentChunk":{"description":"A DocumentChunk represents a piece of content from a Document in the DeveloperKnowledge corpus. To fetch the entire document content, pass the `parent` to get_document or batch_get_documents.","properties":{"content":{"description":"Output only. The content of the document chunk.","readOnly":true,"type":"string"},"id":{"description":"Output only. The ID of this chunk within the document. The chunk ID is unique within a document, but not globally unique across documents. The chunk ID is not stable and may change over time.","readOnly":true,"type":"string"},"parent":{"description":"Output only. The resource name of the document this chunk is from. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for search_documents.","properties":{"results":{"description":"The search results for the given query. Each Document in this list contains a snippet of content relevant to the search query. Use the DocumentChunk.name field of each result with get_documents to retrieve the full document content.","items":{"$ref":"#/$defs/DocumentChunk"},"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use answer_query to get a grounded answer to a query about Google developer products. This tool has limited quota. This tool will synthesize information from the corpus to generate an answer to the query. answer_query grounds answers using the same corpus as search_documents. \nThis tool returns the generated answer_text and a list of document names (references) used to generate the answer. Use get_documents with the document names to fetch the entire document content if needed.\n\nIf you get a 429 out of quota error, use search_documents instead.\n","inputSchema":{"description":"Request message for AnswerQuery.","properties":{"query":{"description":"Required. The query to answer.","type":"string"}},"required":["query"],"type":"object"},"name":"answer_query","outputSchema":{"description":"Response message for AnswerQuery.","properties":{"answerText":{"description":"The answer to the query.","type":"string"},"references":{"description":"Output only. The resource names of the documents used to generate the answer.","items":{"type":"string"},"readOnly":true,"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to retrieve the full content of a single document or up to 20 documents in a single call. The document names should be obtained from the `parent` field of results from a call to the `search_documents` tool. Set the `names` parameter to a list of document names.","inputSchema":{"description":"Request schema for get_documents.","properties":{"names":{"description":"Required. The names of the documents to retrieve, as returned by search_documents. A maximum of 20 documents can be retrieved in one call. The documents are returned in the same order as the `names` in the request. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","items":{"type":"string"},"type":"array"}},"required":["names"],"type":"object"},"name":"get_documents","outputSchema":{"$defs":{"Document":{"description":"A Document represents a piece of content from the Developer Knowledge corpus.","properties":{"content":{"description":"Output only. The content of the document in Markdown format.","readOnly":true,"type":"string"},"description":{"description":"Output only. A description of the document.","readOnly":true,"type":"string"},"name":{"description":"Identifier. The resource name of the document. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","type":"string","x-google-identifier":true},"title":{"description":"Output only. The title of the document.","readOnly":true,"type":"string"},"uri":{"description":"Output only. The URI of the content, such as `https://cloud.google.com/storage/docs/creating-buckets`.","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for get_documents.","properties":{"documents":{"description":"Documents requested.","items":{"$ref":"#/$defs/Document"},"type":"array"}},"type":"object"}}]}} -[debug] [2026-06-24T13:45:57.026Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:45:57.026Z] > authorizing via signed-in user (melih.celik@hubx.co) -[debug] [2026-06-24T13:46:12.218Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:46:12.219Z] > authorizing via signed-in user (melih.celik@hubx.co) -[debug] [2026-06-24T13:46:12.229Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:46:12.229Z] > authorizing via signed-in user (melih.celik@hubx.co) -[debug] [2026-06-24T13:46:12.243Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:46:12.243Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:46:12.243Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] -[debug] [2026-06-24T13:46:12.243Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= -[debug] [2026-06-24T13:46:12.256Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:46:12.256Z] Checked if tokens are valid: true, expires at: 1782310830938 -[debug] [2026-06-24T13:46:12.256Z] >>> [apiv2][query] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [none] -[debug] [2026-06-24T13:46:12.256Z] >>> [apiv2][(partial)header] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com x-goog-user-project= -[debug] [2026-06-24T13:46:13.178Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 -[debug] [2026-06-24T13:46:13.178Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] -[debug] [2026-06-24T13:46:13.179Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:46:13.179Z] > authorizing via signed-in user (melih.celik@hubx.co) -[debug] [2026-06-24T13:46:13.180Z] >>> [apiv2][query] POST https://developerknowledge.googleapis.com/mcp [none] -[debug] [2026-06-24T13:46:13.180Z] >>> [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"method":"tools/list","jsonrpc":"2.0","id":1} -[debug] [2026-06-24T13:46:13.189Z] <<< [apiv2][status] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com 400 -[debug] [2026-06-24T13:46:13.189Z] <<< [apiv2][body] GET https://serviceusage.googleapis.com/v1/projects//services/firebaseappdistribution.googleapis.com [omitted] -[debug] [2026-06-24T13:46:13.529Z] <<< [apiv2][status] POST https://developerknowledge.googleapis.com/mcp 200 -[debug] [2026-06-24T13:46:13.529Z] <<< [apiv2][body] POST https://developerknowledge.googleapis.com/mcp {"id":1,"jsonrpc":"2.0","result":{"tools":[{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to find documentation about Google developer products. The documents contain official APIs, code snippets, release notes, best practices, guides, debugging info, and more. It covers the following products and domains:\n\n\n* ADK: adk.dev\n\n* Android: developer.android.com\n\n* Apigee: docs.apigee.com\n\n* Chrome: developer.chrome.com\n\n* Dart: dart.dev\n\n* Firebase: firebase.google.com\n\n* Flutter: docs.flutter.dev\n\n* Fuchsia: fuchsia.dev\n\n* Gemini CLI: geminicli.com\n\n* Go: go.dev\n\n* Google AI: ai.google.dev\n\n* Google Antigravity: antigravity.google\n\n* Google Cloud: cloud.google.com & docs.cloud.google.com\n\n* Google Developers, Ads, Search, Google Maps, Youtube: developers.google.com\n\n* Google Home: developers.home.google.com\n\n* Google Maps Platform: mapsplatform.google.com\n\n* TensorFlow: www.tensorflow.org\n\n* Web: web.dev\n\n\nThis tool returns chunks of text, names, and URLs for matching documents. If the returned chunks are not detailed enough to answer the user's question, use `get_documents` with the `parent` from this tool's output to retrieve the full document content.\n","inputSchema":{"description":"Request schema for search_documents. Use the query field to search for related Google developer documentation.","properties":{"query":{"description":"Required. The raw query string provided by the user, such as \"How to create a Cloud Storage bucket?\".","type":"string"}},"required":["query"],"type":"object"},"name":"search_documents","outputSchema":{"$defs":{"DocumentChunk":{"description":"A DocumentChunk represents a piece of content from a Document in the DeveloperKnowledge corpus. To fetch the entire document content, pass the `parent` to get_document or batch_get_documents.","properties":{"content":{"description":"Output only. The content of the document chunk.","readOnly":true,"type":"string"},"id":{"description":"Output only. The ID of this chunk within the document. The chunk ID is unique within a document, but not globally unique across documents. The chunk ID is not stable and may change over time.","readOnly":true,"type":"string"},"parent":{"description":"Output only. The resource name of the document this chunk is from. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for search_documents.","properties":{"results":{"description":"The search results for the given query. Each Document in this list contains a snippet of content relevant to the search query. Use the DocumentChunk.name field of each result with get_documents to retrieve the full document content.","items":{"$ref":"#/$defs/DocumentChunk"},"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use answer_query to get a grounded answer to a query about Google developer products. This tool has limited quota. This tool will synthesize information from the corpus to generate an answer to the query. answer_query grounds answers using the same corpus as search_documents. \nThis tool returns the generated answer_text and a list of document names (references) used to generate the answer. Use get_documents with the document names to fetch the entire document content if needed.\n\nIf you get a 429 out of quota error, use search_documents instead.\n","inputSchema":{"description":"Request message for AnswerQuery.","properties":{"query":{"description":"Required. The query to answer.","type":"string"}},"required":["query"],"type":"object"},"name":"answer_query","outputSchema":{"description":"Response message for AnswerQuery.","properties":{"answerText":{"description":"The answer to the query.","type":"string"},"references":{"description":"Output only. The resource names of the documents used to generate the answer.","items":{"type":"string"},"readOnly":true,"type":"array"}},"type":"object"}},{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true},"description":"Use this tool to retrieve the full content of a single document or up to 20 documents in a single call. The document names should be obtained from the `parent` field of results from a call to the `search_documents` tool. Set the `names` parameter to a list of document names.","inputSchema":{"description":"Request schema for get_documents.","properties":{"names":{"description":"Required. The names of the documents to retrieve, as returned by search_documents. A maximum of 20 documents can be retrieved in one call. The documents are returned in the same order as the `names` in the request. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","items":{"type":"string"},"type":"array"}},"required":["names"],"type":"object"},"name":"get_documents","outputSchema":{"$defs":{"Document":{"description":"A Document represents a piece of content from the Developer Knowledge corpus.","properties":{"content":{"description":"Output only. The content of the document in Markdown format.","readOnly":true,"type":"string"},"description":{"description":"Output only. A description of the document.","readOnly":true,"type":"string"},"name":{"description":"Identifier. The resource name of the document. Format: `documents/{uri_without_scheme}` Example: `documents/docs.cloud.google.com/storage/docs/creating-buckets`","type":"string","x-google-identifier":true},"title":{"description":"Output only. The title of the document.","readOnly":true,"type":"string"},"uri":{"description":"Output only. The URI of the content, such as `https://cloud.google.com/storage/docs/creating-buckets`.","readOnly":true,"type":"string"}},"type":"object"}},"description":"Response schema for get_documents.","properties":{"documents":{"description":"Documents requested.","items":{"$ref":"#/$defs/Document"},"type":"array"}},"type":"object"}}]}} -[debug] [2026-06-24T13:46:13.530Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] -[debug] [2026-06-24T13:46:13.530Z] > authorizing via signed-in user (melih.celik@hubx.co) From a0079e028ac6aed54482cf4da1d7be04970f73fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melih=20=C3=87elik?= Date: Tue, 7 Jul 2026 00:26:58 +0300 Subject: [PATCH 3/3] chore: address PR review feedback (gitignore and cached files) --- .gitignore | 1 + .idea/libraries/Dart_SDK.xml | 30 ------------------------------ 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 .idea/libraries/Dart_SDK.xml diff --git a/.gitignore b/.gitignore index 9d3e9eb..911a8de 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ firebase-debug.log # IntelliJ/IDE backup and generated files *.bak +*.iml .idea/ # Flutter iOS ephemeral (generated per-build, may contain absolute local paths) diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml deleted file mode 100644 index e23b6e8..0000000 --- a/.idea/libraries/Dart_SDK.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file