├── src └── echo-chamber-app │ ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ ├── RunnerTests │ │ └── RunnerTests.swift │ ├── .gitignore │ └── Podfile │ ├── assets │ └── icon │ │ └── icon.png │ ├── android │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── kotlin │ │ │ │ │ └── io │ │ │ │ │ │ └── approov │ │ │ │ │ │ └── shapes │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── build.gradle.kts │ ├── settings.gradle.kts │ ├── gradlew.bat │ └── gradlew │ ├── test │ └── widget_test.dart │ ├── analysis_options.yaml │ ├── pubspec.yaml │ ├── lib │ ├── user_auth.dart │ ├── phoenix_channel.dart │ ├── http_service.dart │ └── main.dart │ └── pubspec.lock ├── readme-images ├── download-cert.png └── program-resources.png ├── .gitignore ├── LICENSE ├── README.md └── ECHO-CHAMBER-EXAMPLE.md /src/echo-chamber-app/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /readme-images/download-cert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/readme-images/download-cert.png -------------------------------------------------------------------------------- /readme-images/program-resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/readme-images/program-resources.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /src/echo-chamber-app/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/assets/icon/icon.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/kotlin/io/approov/shapes/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.approov.echo 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/approov/quickstart-flutter-elixir-phoenix-channels/HEAD/src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /src/echo-chamber-app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | src/echo-chamber-app/build/ 33 | src/echo-chamber-app/ios/Podfile.lock 34 | src/echo-chamber-app/ios/Runner.xcworkspace/ 35 | src/echo-chamber-app/ios/build/ 36 | 37 | # Web related 38 | lib/generated_plugin_registrant.dart 39 | 40 | # Symbolication related 41 | app.*.symbols 42 | 43 | # Obfuscation related 44 | app.*.map.json 45 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT Licence 2 | 3 | Copyright (c) 2018-present, Hasura Technologies Private Limited 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/echo-chamber-app/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /src/echo-chamber-app/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id("dev.flutter.flutter-gradle-plugin") 6 | } 7 | 8 | android { 9 | namespace = "io.approov.echo" 10 | compileSdk = flutter.compileSdkVersion 11 | //ndkVersion = flutter.ndkVersion 12 | ndkVersion = "27.0.12077973" 13 | 14 | compileOptions { 15 | sourceCompatibility = JavaVersion.VERSION_11 16 | targetCompatibility = JavaVersion.VERSION_11 17 | } 18 | 19 | kotlinOptions { 20 | jvmTarget = JavaVersion.VERSION_11.toString() 21 | } 22 | 23 | defaultConfig { 24 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 25 | applicationId = "io.approov.echo" 26 | // You can update the following values to match your application needs. 27 | // For more information, see: https://flutter.dev/to/review-gradle-config. 28 | minSdk = 23 29 | targetSdk = flutter.targetSdkVersion 30 | versionCode = flutter.versionCode 31 | versionName = flutter.versionName 32 | } 33 | 34 | buildTypes { 35 | release { 36 | // TODO: Add your own signing config for the release build. 37 | // Signing with the debug keys for now, so `flutter run --release` works. 38 | signingConfig = signingConfigs.getByName("debug") 39 | } 40 | } 41 | } 42 | 43 | flutter { 44 | source = "../.." 45 | } 46 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Echo Chamber 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | Echo 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/echo-chamber-app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: echo 2 | description: Demo example for Approov and Phoenix Channels 3 | 4 | environment: 5 | sdk: '>=3.0.0' 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | http: ^1.2.1 11 | fluttertoast: ^8.0.9 12 | flutter_easyloading: ^3.0.0 13 | 14 | # COMMENT OUT IF APPROOV IS BEING USED 15 | phoenix_wings: ^1.0.2 16 | 17 | # UNCOMMENT THE FOLLOWING LINES IF APPROOV IS BEING USED 18 | #phoenix_wings: 19 | # git: 20 | # url: https://github.com/approov/approov-flutter-packages.git 21 | # path: phoenix_wings 22 | #approov_web_socket: 23 | # git: 24 | # url: https://github.com/approov/approov-flutter-packages.git 25 | # path: approov_web_socket 26 | # git: https://github.com/approov/approov-service-flutter-httpclient.git 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.3 31 | 32 | dev_dependencies: 33 | flutter_test: 34 | sdk: flutter 35 | intl: ^0.17.0 36 | 37 | # For information on the generic Dart part of this file, see the 38 | # following page: https://www.dartlang.org/tools/pub/pubspec 39 | 40 | # The following section is specific to Flutter. 41 | flutter: 42 | 43 | # The following line ensures that the Material Icons font is 44 | # included with your application, so that you can use the icons in 45 | # the material Icons class. 46 | uses-material-design: true 47 | 48 | # To add assets to your application, add an assets section, like this: 49 | # assets: 50 | # - images/a_dot_burr.jpeg 51 | # - images/a_dot_ham.jpeg 52 | 53 | # An image asset can refer to one or more resolution-specific "variants", see 54 | # https://flutter.io/assets-and-images/#resolution-aware. 55 | 56 | # For details regarding adding assets from package dependencies, see 57 | # https://flutter.io/assets-and-images/#from-packages 58 | 59 | # To add custom fonts to your application, add a fonts section here, 60 | # in this "flutter" section. Each entry in this list should have a 61 | # "family" key with the font family name, and a "fonts" key with a 62 | # list giving the asset and other descriptors for the font. For 63 | # example: 64 | # fonts: 65 | # - family: Schyler 66 | # fonts: 67 | # - asset: fonts/Schyler-Regular.ttf 68 | # - asset: fonts/Schyler-Italic.ttf 69 | # style: italic 70 | # - family: Trajan Pro 71 | # fonts: 72 | # - asset: fonts/TrajanPro.ttf 73 | # - asset: fonts/TrajanPro_Bold.ttf 74 | # weight: 700 75 | # 76 | # For details regarding fonts from package dependencies, 77 | # see https://flutter.io/custom-fonts/#from-packages 78 | -------------------------------------------------------------------------------- /src/echo-chamber-app/lib/user_auth.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Approov Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | * Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | import 'dart:convert'; 19 | import 'package:echo/http_service.dart'; 20 | import 'package:flutter/material.dart'; 21 | import 'package:http/http.dart'; 22 | 23 | class UserAuth { 24 | final http = HttpService.httpClient; 25 | 26 | Future login(String username, String password) async { 27 | String _token; 28 | 29 | Map credentials = {"username": username, "password": password}; 30 | 31 | Map headers = {"content-type": "application/json"}; 32 | 33 | Response response = await http 34 | .post(Uri.parse("${HttpService.apiBaseUrl}/auth/login"), headers: headers, body: jsonEncode(credentials)) 35 | .catchError((onError) { 36 | print(onError); 37 | return null; 38 | }); 39 | 40 | if (response == null || response.statusCode != 200) { 41 | return null; 42 | } 43 | 44 | _token = jsonDecode(response.body)["token"]; 45 | return _token; 46 | } 47 | 48 | Future register(String username, String password) async { 49 | bool success = false; 50 | 51 | Map credentials = {"username": username, "password": password}; 52 | 53 | Map headers = {"content-type": "application/json"}; 54 | 55 | Response response = await http 56 | .post(Uri.parse("${HttpService.apiBaseUrl}/auth/register"), headers: headers, body: jsonEncode(credentials)) 57 | .catchError((onError) { 58 | print(onError); 59 | return null; 60 | }); 61 | 62 | if (response == null || response.statusCode != 200) { 63 | return success; 64 | } 65 | 66 | success = jsonDecode(response.body)["id"] != null; 67 | return success; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/echo-chamber-app/lib/phoenix_channel.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Approov Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | * Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | import 'package:echo/http_service.dart'; 19 | import 'package:phoenix_wings/phoenix_wings.dart'; 20 | 21 | class PhoenixChannelSocket { 22 | static late PhoenixSocket _socket; 23 | 24 | static Map _channels = {}; 25 | 26 | static connect({onOpen, onError}) async { 27 | final socket_options = new PhoenixSocketOptions( 28 | params: await HttpService.buildRequestHeaders(), 29 | timeout: 2000, 30 | heartbeatIntervalMs: 3000, 31 | reconnectAfterMs: const [500, 1000, 1500, 3000], 32 | ); 33 | 34 | // To run from the Android the emulator we need to use `10.0.2.2`, because 35 | // if we use `localhost` we just hit the emulator VM, not our computer 36 | // `localhost`. 37 | _socket = PhoenixSocket( 38 | "${HttpService.websocketUrl}/socket/websocket", 39 | // UNCOMMENT IF USING APPROOV 40 | //'', 41 | socketOptions: socket_options, 42 | ); 43 | 44 | _socket.onOpen(onOpen); 45 | _socket.onError(onError); 46 | _socket.connect(); 47 | } 48 | 49 | static Future join(String channelName, {onMessage, onError}) async { 50 | // This check needs to be the first, to be able to resume after app looses 51 | // network and reestablishes the socket connection. 52 | if (_socket != null && !_socket.isConnected) { 53 | print("Phoenix Channel: Trying to reconnect to the socket. Poor Network??? Invalid or missing Approov token???"); 54 | return false; 55 | } 56 | 57 | if (_socket == null) { 58 | print("Phoenix Channel: No socket connection. No network??? Invalid or missing Approov token???"); 59 | return false; 60 | } 61 | 62 | // Only join if not already joined. 63 | if (_channels != null && _channels[channelName] != null) { 64 | return true; 65 | } 66 | 67 | final PhoenixChannel _channel = _socket.channel(channelName, await HttpService.buildRequestHeaders()); 68 | 69 | // Setup listeners for channel events 70 | _channel.on(channelName, onMessage); 71 | _channel.onError(onError); 72 | 73 | // Make the request to the server to join the channel 74 | _channel.join(); 75 | 76 | _channels[channelName] = _channel; 77 | 78 | return true; 79 | } 80 | 81 | static Future push(String message, String channelName) async { 82 | if (_channels == null || _channels[channelName] == null) { 83 | print("Phoenix Channel: Unknown channel ${channelName}"); 84 | return false; 85 | } 86 | 87 | Map payload = await HttpService.buildRequestHeaders(); 88 | payload["message"] = message; 89 | 90 | _channels[channelName]?.push(event: "echo_it", payload: payload); 91 | 92 | return true; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/echo-chamber-app/lib/http_service.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Approov Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | * Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | import 'dart:io'; 19 | 20 | import 'package:http/http.dart' as http; 21 | import 'package:echo/user_auth.dart'; 22 | 23 | // UNCOMMENT TO USE APPROOV 24 | //import 'package:approov_service_flutter_httpclient/approov_service_flutter_httpclient.dart'; 25 | 26 | class HttpService { 27 | static String httpProtocol = "https"; 28 | 29 | static String websocketProtocol = "wss"; 30 | 31 | static String? auth_token; 32 | 33 | static String get localhost { 34 | httpProtocol = "http"; 35 | websocketProtocol = "ws"; 36 | 37 | if (Platform.isAndroid) { 38 | return '10.0.2.2:8002'; 39 | } else { 40 | return 'localhost:8002'; 41 | } 42 | } 43 | 44 | // Choose one of the below endpoints: 45 | // UNCOMMENT IF RUNNING PHOENIX CHANNELS SERVER LOCALLY 46 | // static String apiHost = localhost; 47 | 48 | // UNCOMMENT IF USING THE UNPROTECTED PHOENIX CHANNELS SERVER BEFORE ADDING APPROOV 49 | static String apiHost = 'unprotected.phoenix-channels.demo.approov.io'; 50 | 51 | // UNCOMMENT IF USING THE PROTECTED PHOENIX CHANNELS SERVER WHEN USING APPROOV 52 | //static String apiHost = 'token.phoenix-channels.demo.approov.io'; 53 | 54 | static String get apiBaseUrl { 55 | return "${httpProtocol}://${apiHost}"; 56 | } 57 | 58 | static String get websocketUrl { 59 | return "${websocketProtocol}://${apiHost}"; 60 | } 61 | 62 | // COMMENT LINE BELOW IF USING APPROOV 63 | static final httpClient = new http.Client(); 64 | 65 | // UNCOMMENT LINES BELOW IF USING APPROOV 66 | // static final httpClient = () { 67 | // var approovClient = ApproovClient(''); 68 | // // We use a custom header "X-Approov-Token" rather than just "Approov-Token" 69 | // ApproovService.setApproovHeader("X-Approov-Token", ""); 70 | // return approovClient; 71 | // }(); 72 | 73 | static Future> buildRequestHeaders() async { 74 | final authToken = await _getAuthenticationToken(); 75 | if (authToken == null) { 76 | return {}; 77 | } else { 78 | return { 79 | "Authorization": authToken, 80 | 81 | // UNCOMMENT THE LINE BELOW IF USING APPROOV WITH THE LOCALHOST BACKEND SERVER 82 | // "X-Approov-Token": _getTokenForLocalhostTesting(type: "valid"), 83 | 84 | // UNCOMMENT THE LINE BELOW IF USING APPROOV WITH THE ONLINE BACKEND SERVER 85 | //"X-Approov-Token": await ApproovService.fetchToken(apiHost), 86 | }; 87 | } 88 | } 89 | 90 | static Future _getAuthenticationToken() async { 91 | if (auth_token != null) { 92 | return auth_token; 93 | } 94 | 95 | auth_token = await register_and_login(); 96 | 97 | return auth_token; 98 | } 99 | 100 | static Future register_and_login() async { 101 | // @TODO Add Authentication register screen 102 | await UserAuth().register("me@gmail.com", "very_strong_password"); 103 | 104 | // @TODO Add Authentication login screen 105 | return await UserAuth().login("me@gmail.com", "very_strong_password").then((value) => value); 106 | } 107 | 108 | static String _getTokenForLocalhostTesting({type}) { 109 | switch (type) { 110 | case "valid": 111 | { 112 | return "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjQ3MDg2ODMyMDUuODkxOTEyfQ.c8I4KNndbThAQ7zlgX4_QDtcxCrD9cff1elaCJe9p9U"; 113 | } 114 | 115 | case "expired": 116 | { 117 | return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTAzMDI4MTZ9.1MlmDnPHlgPzKPqHxsPd6HBZ-DYbDB16qGutk7Eheb8"; 118 | } 119 | 120 | case "invalid_signature": 121 | { 122 | return "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NTUwODMzNDkuMzc3NzYyM30.XzZs_ItunAmisfTAuLLHqTytNnQqnwqh0Koh3PPKAoA"; 123 | } 124 | 125 | case "malformed": 126 | { 127 | return "sddsfs.adsad.asdsa"; 128 | } 129 | 130 | case "empty": 131 | { 132 | return ""; 133 | } 134 | default: 135 | { 136 | return ""; 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/echo-chamber-app/lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Approov Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | * permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | * Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | import 'package:flutter/material.dart'; 19 | import 'package:echo/phoenix_channel.dart'; 20 | import 'package:intl/intl.dart'; 21 | import 'package:fluttertoast/fluttertoast.dart'; 22 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 23 | 24 | void main() { 25 | runApp(MyApp()); 26 | } 27 | 28 | class MyApp extends StatelessWidget { 29 | @override 30 | Widget build(BuildContext context) { 31 | return MaterialApp( 32 | title: 'Flutter Demo', 33 | theme: ThemeData(primarySwatch: Colors.blue), 34 | home: MyHomePage('Echo Chamber'), 35 | builder: EasyLoading.init(), 36 | ); 37 | } 38 | } 39 | 40 | class MyHomePage extends StatefulWidget { 41 | //MyHomePage({Key key, this.title}) : super(key: key); 42 | MyHomePage(this.title) : super(); 43 | final String title; 44 | 45 | @override 46 | _MyHomePageState createState() => _MyHomePageState(); 47 | } 48 | 49 | class _MyHomePageState extends State { 50 | String _channelName = "echo:chamber"; 51 | List messages = []; 52 | final TextEditingController _textController = TextEditingController(); 53 | bool _isButtonEnabled = false; 54 | 55 | @override 56 | void initState() { 57 | EasyLoading.show(status: "Connecting..."); 58 | PhoenixChannelSocket.connect(onOpen: this._onOpenSocket, onError: this._onSocketError); 59 | super.initState(); 60 | } 61 | 62 | _onOpenSocket() { 63 | EasyLoading.dismiss(); 64 | _enableSubmitButton(); 65 | } 66 | 67 | // @TODO Figure out the steps to trigger this callback again 68 | // I was only able to trigger this callback once, but I was missing the error 69 | // parameter, therefore it threw an exception. 70 | _onSocketError(error) { 71 | print("socket error: $error"); 72 | _disableSubmitButton(); 73 | EasyLoading.show(status: "Retrying to reconnect..."); 74 | } 75 | 76 | _showToastError(err) { 77 | _disableSubmitButton(); 78 | 79 | Fluttertoast.showToast( 80 | msg: err.toString(), 81 | toastLength: Toast.LENGTH_LONG, 82 | gravity: ToastGravity.CENTER, 83 | timeInSecForIosWeb: 10, 84 | backgroundColor: Colors.red, 85 | textColor: Colors.white, 86 | fontSize: 16.0, 87 | ); 88 | } 89 | 90 | _onChannelError(payload, ref, joinRef) { 91 | _disableSubmitButton(); 92 | EasyLoading.show(status: "Waiting to rejoin channel..."); 93 | } 94 | 95 | _enableSubmitButton() { 96 | setState(() { 97 | _isButtonEnabled = true; 98 | }); 99 | } 100 | 101 | _disableSubmitButton() { 102 | setState(() { 103 | _isButtonEnabled = false; 104 | }); 105 | } 106 | 107 | _say(payload, _ref, _joinRef) { 108 | setState(() { 109 | messages.insert(0, ChatMessage(payload["message"])); 110 | }); 111 | } 112 | 113 | _sendMessage(message) async { 114 | EasyLoading.show(status: "Sending..."); 115 | _disableSubmitButton(); 116 | 117 | // Will join only if not already joined. 118 | bool isJoined = await PhoenixChannelSocket.join(_channelName, onMessage: this._say, onError: this._onChannelError); 119 | 120 | if (!isJoined) { 121 | EasyLoading.dismiss(); 122 | _showToastError("Failed to join the channel"); 123 | EasyLoading.show(status: "Reconnecting..."); 124 | return; 125 | } 126 | 127 | bool isMessagePushed = await PhoenixChannelSocket.push(message, _channelName); 128 | 129 | if (!isMessagePushed) { 130 | EasyLoading.dismiss(); 131 | _showToastError("Failed to push message to the channel"); 132 | EasyLoading.show(status: "Reconnecting..."); 133 | return; 134 | } 135 | 136 | _textController.clear(); 137 | 138 | EasyLoading.dismiss(); 139 | _enableSubmitButton(); 140 | } 141 | 142 | @override 143 | Widget build(BuildContext context) { 144 | return Scaffold( 145 | appBar: AppBar(title: Text(widget.title)), 146 | body: Column( 147 | children: [ 148 | Flexible( 149 | child: ListView.builder( 150 | reverse: true, 151 | itemBuilder: (BuildContext context, int index) { 152 | final message = messages[index]; 153 | return Card( 154 | child: Column( 155 | children: [ 156 | ListTile(leading: Icon(Icons.message), title: Text(message.text), subtitle: Text(message.time)), 157 | ], 158 | ), 159 | ); 160 | }, 161 | itemCount: messages.length, 162 | ), 163 | ), 164 | Divider(height: 1.0), 165 | Container( 166 | child: MessageComposer( 167 | textController: _textController, 168 | sendMessage: _sendMessage, 169 | isButtonEnabled: _isButtonEnabled, 170 | ), 171 | ), 172 | ], 173 | ), 174 | ); 175 | } 176 | } 177 | 178 | class ChatMessage { 179 | final String text; 180 | final DateTime received = DateTime.now(); 181 | ChatMessage(this.text); 182 | 183 | get time => DateFormat.Hms().format(received); 184 | } 185 | 186 | class MessageComposer extends StatelessWidget { 187 | final textController; 188 | final sendMessage; 189 | final isButtonEnabled; 190 | 191 | MessageComposer({this.textController, this.sendMessage, this.isButtonEnabled}); 192 | build(BuildContext context) { 193 | return Container( 194 | margin: EdgeInsets.symmetric(horizontal: 8.0), 195 | child: Row( 196 | children: [ 197 | Flexible( 198 | child: TextField( 199 | controller: textController, 200 | onSubmitted: sendMessage, 201 | enabled: isButtonEnabled, 202 | decoration: InputDecoration.collapsed(hintText: "Send a message"), 203 | ), 204 | ), 205 | Container( 206 | child: IconButton( 207 | icon: Icon(Icons.send), 208 | onPressed: isButtonEnabled ? () => sendMessage(textController.text) : null, 209 | ), 210 | ), 211 | ], 212 | ), 213 | ); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Approov Quickstart: Flutter Elixir Phoenix Channels 2 | 3 | [Approov](https://approov.io) is an API security solution used to verify that requests received by your backend services originate from trusted versions of your mobile apps. 4 | 5 | This is an Approov integration quickstart for Flutter and using a backend with [Elixir Phoenix Channels](https://hexdocs.pm/phoenix/channels.html). Additionally, a step-by-step tutorial guide using a [Echo Chamber App](https://github.com/approov/quickstart-flutter-elixir-phoenix-channels/blob/master/ECHO-CHAMBER-EXAMPLE.md) is also available. 6 | 7 | Note that the minimum OS requirement for iOS is 12 and for Android the minimum SDK version is 23 (Android 6.0). You cannot use Approov in apps that need to support OS versions older than this. 8 | 9 | > ## ⚠️ CAUTION 10 | > **This quickstart does not currently work with Approov integration because the packages in https://github.com/approov/approov-flutter-packages need to be updated to be compatible with Flutter 3.0 versions. Please contact Approov support if you require an updated version.** 11 | 12 | ## ADDING APPROOV ENABLED ELIXIR PHOENIX CHANNELS 13 | 14 | The Approov Enabled Elixir Phoenix Channels is available via a [`Github`](https://github.com/approov/approov-flutter-packages.git) package. This allows inclusion into the project by simply specifying a dependency in the `pubspec.yaml` files for the app. In the `dependencies:` section of `pubspec.yaml` file add the following package reference: 15 | 16 | ```yaml 17 | phoenix_wings: 18 | git: 19 | url: https://github.com/approov/approov-flutter-packages.git 20 | path: phoenix_wings 21 | approov_web_socket: 22 | git: 23 | url: https://github.com/approov/approov-flutter-packages.git 24 | path: approov_web_socket 25 | approov_service_flutter_httpclient: 26 | git: https://github.com/approov/approov-service-flutter-httpclient.git 27 | ``` 28 | 29 | Note that this creates a dependency on the latest version of the `approov-service-flutter-httpclient`, as do the dependencies in `approov-flutter-packages`. If you wish to create a dependency on a fixed tagged version you can use a syntax such as the following: 30 | 31 | ```yaml 32 | approov_service_flutter_httpclient: 33 | git: 34 | url: https://github.com/approov/approov-service-flutter-httpclient.git 35 | ref: 3.3.1 36 | ``` 37 | 38 | You will need to fork the `approov-flutter-packages` if you wish to fix their dependency to a specific tag. 39 | 40 | The `phoenix_wings` package uses a predefined header to forward the Approov Token and also forwards the Approov SDK initialization string to the `approov_web_socket` package. The `approov_web_socket` package interacts with the `approov_service_flutter_httpclient` by means of an `ApproovHttpClient` object. 41 | 42 | The `approov_service_flutter_httpclient` package is actually an open source wrapper layer that allows you to easily use Approov with `Flutter`. This has a further dependency to the closed source [Android Approov SDK](https://github.com/approov/approov-android-sdk) and [iOS Approov SDK](https://github.com/approov/approov-ios-sdk) packages. Those are automatically added as dependencies for the platform specific targets. 43 | 44 | The `approov_service_flutter_httpclient` package provides a number of accessible classes: 45 | 46 | 1. `ApproovService` provides a higher level interface to the underlying Approov SDK 47 | 2. `ApproovException`, and derived `ApproovNetworkException` and `ApproovRejectionException`, provide special exception classes for Approov related errors 48 | 3. `ApproovHttpClient` which is a drop-in replacement for the Dart IO library's `HttpClient` and calls the `ApproovService` 49 | 4. `ApproovClient` which is a drop-in replacement for Client from the Flutter http package (https://pub.dev/packages/http) and internally uses an `ApproovHttpClient` object 50 | 51 | ### ANDROID MANIFEST CHANGES 52 | 53 | The following app permissions need to be available in the manifest of your application to be able to use Approov: 54 | 55 | ```xml 56 | 57 | 58 | ``` 59 | 60 | Please [read this](https://approov.io/docs/latest/approov-usage-documentation/#targetting-android-11-and-above) section of the reference documentation if targetting Android 11 (API level 30) or above. 61 | 62 | ### IOS 63 | 64 | The `approov_service_flutter_httpclient` generates a [Cocoapods](https://cocoapods.org) dependency file which must be installed by executing: 65 | 66 | ```Bash 67 | pod install 68 | ``` 69 | 70 | in the directory containing the ios project files. 71 | 72 | ## USING APPROOV WITH PHOENIX CHANNELS 73 | 74 | First, you need to instantiate the `PhoenixSocket` with an additional parameter and use it to `connect()`: 75 | 76 | ```Dart 77 | PhoenixSocket socket = PhoenixSocket("${HttpService.websocketUrl}/socket/websocket", 78 | '', 79 | socketOptions: socket_options); 80 | ``` 81 | 82 | The `` is a custom string that configures your Approov account access. This will have been provided in your Approov onboarding email. 83 | 84 | Now, you need to add the Approov token when you join to the Phoenix channel: 85 | 86 | ```dart 87 | import 'package:approov_service_flutter_httpclient/approov_service_flutter_httpclient.dart'; 88 | 89 | Map urlParams = { 90 | "X-Approov-Token": await ApproovService.fetchToken('your.api.domain.com') 91 | }; 92 | 93 | final PhoenixChannel _channel = _socket.channel(channelName, urlParams); 94 | ``` 95 | 96 | Next, add the Approov token to the payload of each message sent to the Phoenix channel: 97 | 98 | ```dart 99 | import 'package:approov_service_flutter_httpclient/approov_service_flutter_httpclient.dart'; 100 | 101 | Map payload = { 102 | "X-Approov-Token": await ApproovService.fetchToken('your.api.domain.com'), 103 | "message" : message 104 | }; 105 | 106 | _channel.push(event: "name", payload: payload); 107 | ``` 108 | 109 | It's important that you also use the Approov token when joining to the channel and when sending messages to it, because your app environment can be compromised at any moment by an attacker, and when this occurs an invalid Approov token is sent in the API request headers to allow for your backend to reject any message it receives and to not send any data back as it would normally do. 110 | 111 | ## CHECKING IT WORKS 112 | 113 | Initially you won't have set which API domains to protect, so the interceptor will not add anything. It will have called Approov though and made contact with the Approov cloud service. You will see logging from Approov saying `UNKNOWN_URL`. 114 | 115 | Your Approov onboarding email should contain a link allowing you to access [Live Metrics Graphs](https://approov.io/docs/latest/approov-usage-documentation/#metrics-graphs). After you've run your app with Approov integration you should be able to see the results in the live metrics within a minute or so. At this stage you could even release your app to get details of your app population and the attributes of the devices they are running upon. 116 | 117 | ## NEXT STEPS 118 | To actually protect your APIs and/or secrets there are some further steps. Approov provides two different options for protection: 119 | 120 | * [API PROTECTION](https://github.com/approov/quickstart-flutter-httpclient/blob/master/API-PROTECTION.md): You should use this if you control the backend API(s) being protected and are able to modify them to ensure that a valid Approov token is being passed by the app. An [Approov Token](https://approov.io/docs/latest/approov-usage-documentation/#approov-tokens) is short lived crytographically signed JWT proving the authenticity of the call. 121 | 122 | * [SECRETS PROTECTION](https://github.com/approov/quickstart-flutter-httpclient/blob/master/SECRETS-PROTECTION.md): This allows app secrets, including API keys for 3rd party services, to be protected so that they no longer need to be included in the released app code. These secrets are only made available to valid apps at runtime. 123 | 124 | Note that it is possible to use both approaches side-by-side in the same app. 125 | 126 | See [REFERENCE](https://github.com/approov/quickstart-flutter-httpclient/blob/master/REFERENCE.md) for a complete list of all of the `ApproovService` methods. 127 | -------------------------------------------------------------------------------- /src/echo-chamber-app/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.12.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.2" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.4.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.2" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.19.1" 44 | cupertino_icons: 45 | dependency: "direct main" 46 | description: 47 | name: cupertino_icons 48 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.0.8" 52 | fake_async: 53 | dependency: transitive 54 | description: 55 | name: fake_async 56 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.2" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_easyloading: 66 | dependency: "direct main" 67 | description: 68 | name: flutter_easyloading 69 | sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "3.0.5" 73 | flutter_spinkit: 74 | dependency: transitive 75 | description: 76 | name: flutter_spinkit 77 | sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "5.2.1" 81 | flutter_test: 82 | dependency: "direct dev" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | flutter_web_plugins: 87 | dependency: transitive 88 | description: flutter 89 | source: sdk 90 | version: "0.0.0" 91 | fluttertoast: 92 | dependency: "direct main" 93 | description: 94 | name: fluttertoast 95 | sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" 96 | url: "https://pub.dev" 97 | source: hosted 98 | version: "8.2.12" 99 | http: 100 | dependency: "direct main" 101 | description: 102 | name: http 103 | sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f 104 | url: "https://pub.dev" 105 | source: hosted 106 | version: "1.3.0" 107 | http_parser: 108 | dependency: transitive 109 | description: 110 | name: http_parser 111 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 112 | url: "https://pub.dev" 113 | source: hosted 114 | version: "4.1.2" 115 | intl: 116 | dependency: "direct dev" 117 | description: 118 | name: intl 119 | sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" 120 | url: "https://pub.dev" 121 | source: hosted 122 | version: "0.17.0" 123 | leak_tracker: 124 | dependency: transitive 125 | description: 126 | name: leak_tracker 127 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "10.0.8" 131 | leak_tracker_flutter_testing: 132 | dependency: transitive 133 | description: 134 | name: leak_tracker_flutter_testing 135 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "3.0.9" 139 | leak_tracker_testing: 140 | dependency: transitive 141 | description: 142 | name: leak_tracker_testing 143 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "3.0.1" 147 | matcher: 148 | dependency: transitive 149 | description: 150 | name: matcher 151 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "0.12.17" 155 | material_color_utilities: 156 | dependency: transitive 157 | description: 158 | name: material_color_utilities 159 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "0.11.1" 163 | meta: 164 | dependency: transitive 165 | description: 166 | name: meta 167 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "1.16.0" 171 | path: 172 | dependency: transitive 173 | description: 174 | name: path 175 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "1.9.1" 179 | phoenix_wings: 180 | dependency: "direct main" 181 | description: 182 | name: phoenix_wings 183 | sha256: "0ca761c9f606009998ecf4e5ba42fe14d5b75ff614b6fbab1e96a7400b143ac5" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "1.0.2" 187 | sky_engine: 188 | dependency: transitive 189 | description: flutter 190 | source: sdk 191 | version: "0.0.0" 192 | source_span: 193 | dependency: transitive 194 | description: 195 | name: source_span 196 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 197 | url: "https://pub.dev" 198 | source: hosted 199 | version: "1.10.1" 200 | stack_trace: 201 | dependency: transitive 202 | description: 203 | name: stack_trace 204 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 205 | url: "https://pub.dev" 206 | source: hosted 207 | version: "1.12.1" 208 | stream_channel: 209 | dependency: transitive 210 | description: 211 | name: stream_channel 212 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 213 | url: "https://pub.dev" 214 | source: hosted 215 | version: "2.1.4" 216 | string_scanner: 217 | dependency: transitive 218 | description: 219 | name: string_scanner 220 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 221 | url: "https://pub.dev" 222 | source: hosted 223 | version: "1.4.1" 224 | term_glyph: 225 | dependency: transitive 226 | description: 227 | name: term_glyph 228 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 229 | url: "https://pub.dev" 230 | source: hosted 231 | version: "1.2.2" 232 | test_api: 233 | dependency: transitive 234 | description: 235 | name: test_api 236 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 237 | url: "https://pub.dev" 238 | source: hosted 239 | version: "0.7.4" 240 | typed_data: 241 | dependency: transitive 242 | description: 243 | name: typed_data 244 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 245 | url: "https://pub.dev" 246 | source: hosted 247 | version: "1.4.0" 248 | vector_math: 249 | dependency: transitive 250 | description: 251 | name: vector_math 252 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 253 | url: "https://pub.dev" 254 | source: hosted 255 | version: "2.1.4" 256 | vm_service: 257 | dependency: transitive 258 | description: 259 | name: vm_service 260 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" 261 | url: "https://pub.dev" 262 | source: hosted 263 | version: "14.3.1" 264 | web: 265 | dependency: transitive 266 | description: 267 | name: web 268 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 269 | url: "https://pub.dev" 270 | source: hosted 271 | version: "1.1.1" 272 | sdks: 273 | dart: ">=3.7.0-0 <4.0.0" 274 | flutter: ">=3.18.0-18.0.pre.54" 275 | -------------------------------------------------------------------------------- /src/echo-chamber-app/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /ECHO-CHAMBER-EXAMPLE.md: -------------------------------------------------------------------------------- 1 | # Echo Chamber Mobile App Example 2 | 3 | A super simple echo chamber mobile app example written in in [`Flutter`](https://flutter.dev/) to show how [Approov](https://approov.io/product) can be integrated when using the [phoenix_wings](https://pub.dev/packages/phoenix_wings) Dart package to talk with an [Elixir Phoenix Channels](https://hexdocs.pm/phoenix/channels.html) backend. This quickstart provides a step-by-step example of integrating Approov into an app using a simple `Echo Chamber` app example that performs an echo of user input based on a request to an API backend that can be protected with Approov. 4 | 5 | ## WHAT YOU WILL NEED 6 | * Access to a trial or paid Approov account 7 | * The `approov` command line tool [installed](https://approov.io/docs/latest/approov-installation/) with access to your account 8 | * [Android Studio](https://developer.android.com/studio) installed (version Bumblebee 2021.1 is used in this guide) if you will build the Android app 9 | * [Xcode](https://developer.apple.com/xcode/) installed (version 13.3 is used in this guide) to build iOS version of application 10 | * [Cocoapods](https://cocoapods.org) installed to support iOS building (1.11.3 used in this guide) 11 | * [Flutter](https://flutter.dev) stable version 3.0.1 is used in this guide. Note that integration is not possible with stable versions earlier than Flutter 3. 12 | * The contents of this repo 13 | 14 | ## Try The Echo Chamber App Without Approov 15 | 16 | First, clone this repo: 17 | 18 | > git clone https://github.com/approov/quickstart-flutter-elixir-phoenix-channels.git 19 | 20 | Next, open a shell on the directory `src/echo-chamber-app`. You can then use `flutter run` to build and run the app on a connected physical device. 21 | 22 | > NOTE: The mobile app will run against this live backend `https://unprotected.phoenix-channels.demo.approov.io`, and the code for it is in the [approov/quickstart-elixir-phoenix-channels-token-check](https://github.com/approov/quickstart-elixir-phoenix-channels-token-check/tree/main/src/unprotected-server/echo) Github repo at `src/unprotected-server/echo`. 23 | 24 | ### iOS Potential Issues 25 | 26 | If the iOS build fails with an error related to `Pods-Runner` then navigate inside `ios` folder using `cd ios` and run `pod install`. 27 | 28 | If the iOS build fails with a signing error, open the Xcode project located in `ios/Runner.xcworkspace`: 29 | 30 | ``` 31 | $ open ios/Runner.xcworkspace 32 | ``` 33 | 34 | and select your code signing team in the _Signing & Capabilities_ section of the project. 35 | 36 | Also ensure you modify the app's `Bundle Identifier` so it contains a unique string (you can simply append your company name). This is to avoid Apple rejecting a duplicate `Bundle Identifier` when code signing is performed. Then return to the shell and repeat the failed build step. 37 | 38 | Please also verify the minimum iOS supported version is set to `iOS 10` if there is a supported version mismatch error. 39 | 40 | ### Android Potential Issues 41 | If the Android build fails with `Manifest merger failed : Attribute application@label value=([...]) from AndroidManifest.xml:11:9-46 is also present at [approov-sdk.aar] AndroidManifest.xml:12:9-41 value=(@string/app_name)`, then open `android/app/src/main/AndroidManifest.xml` in an editor and make the following changes. 42 | 43 | - Add the schema as an attribute in the `manifest` tag: 44 | 45 | ``` 46 | 49 | ``` 50 | - Add the `android:label` and `tools` attributes to the `application` tag: 51 | ``` 52 | 56 | ``` 57 | 58 | ## ADDING APPROOV SUPPORT 59 | 60 | Approov protection is provided through the `approov_service_flutter_httpclient` plugin for both, Android and iOS mobile platforms. This plugin handles all Approov related functionality, including the fetching of Approov tokens and adding these to the `Approov-Token` header for each API request. It also provides you with [dynamic certificate pinning](https://approov.io/docs/latest/approov-usage-documentation/#approov-dynamic-pinning) out of the box, without the need for you to code anything. 61 | 62 | To enable Approov you only need to comment or comment out some lines of code that are after `// COMMENT LINE BELOW IF USING APPROOV` or `// UNCOMMENT LINES BELOW IF USING APPROOV`. 63 | 64 | Look at the `quickstart-flutter-elixir-phoenix-channels/src/echo-chamber-app/pubspec.yaml` and find the lines that need to be changed for the Approov integration. The `Phoenix Channels` support is located in the [approov-flutter-packages](https://github.com/approov/approov-flutter-packages.git) repository and is installed by the changes. This also includes the `approov-service-flutter-httpclient` package. 65 | 66 | Edit the file `quickstart-flutter-elixir-phoenix-channels/src/echo-chamber-app/lib/http_service.dart` by finding the lines that need to be changed when using Approov. Uncomment the appropriate lines and add the config line. The `` is a custom string that configures your Approov account access. This will have been provided in your Approov onboarding email. 67 | 68 | 69 | ### ADDING THE PROTECTED API DOMAIN 70 | 71 | The app will run against [this backend](https://github.com/approov/quickstart-elixir-phoenix-channels-token-check/tree/main/src/approov-protected-server/token-check/echo), that is live at `token.phoenix-channels.demo.approov.io`, thus we also need to let the Approov cloud service know the API domain for it: 72 | 73 | ```Bash 74 | approov api -add token.phoenix-channels.demo.approov.io 75 | ``` 76 | > **NOTE:** This command only needs to be executed the first time you register an APK/IPA with Approov. 77 | 78 | The Approov cloud service will not issue Approov tokens for your mobile app if you forget this step, even if the mobile app binary is registered and no tampering is detected with the binary on the environment is running on. 79 | 80 | If you want to run the mobile app against a backend you have control off, then you need to follow the [deployment guide](https://github.com/approov/quickstart-elixir-phoenix-channels-token-check/blob/main/DEPLOYMENT.md) for the backend of this Echo Chamber mobile app. 81 | 82 | ### ADD YOUR SIGNING CERTIFICATE TO APPROOV 83 | You should add the signing certificate used to sign apps so that Approov can recognize your app as being official. 84 | 85 | #### Android 86 | Add the local certificate used to sign apps in Android Studio. The following assumes it is in PKCS12 format: 87 | 88 | ``` 89 | approov appsigncert -add ~/.android/debug.keystore -storePassword android -autoReg 90 | ``` 91 | 92 | See [Android App Signing Certificates](https://approov.io/docs/latest/approov-usage-documentation/#android-app-signing-certificates) if your keystore format is not recognized or if you have any issues adding the certificate. 93 | 94 | #### iOS 95 | These are available in your Apple development account portal. Go to the initial screen showing program resources: 96 | 97 | ![Apple Program Resources](readme-images/program-resources.png) 98 | 99 | Click on `Certificates` and you will be presented with the full list of development and distribution certificates for the account. Click on the certificate being used to sign applications from your particular Xcode installation and you will be presented with the following dialog: 100 | 101 | ![Download Certificate](readme-images/download-cert.png) 102 | 103 | Now click on the `Download` button and a file with a `.cer` extension is downloaded, e.g. `development.cer`. Add it to Approov with: 104 | 105 | ``` 106 | approov appsigncert -add development.cer -autoReg 107 | ``` 108 | 109 | If it is not possible to download the correct certificate from the portal then it is also possible to [add app signing certificates from the app](https://approov.io/docs/latest/approov-usage-documentation/#adding-apple-app-signing-certificates-from-app). 110 | 111 | > **IMPORTANT:** Apps built to run on the iOS simulator are not code signed and thus auto-registration does not work for them. In this case you can consider [forcing a device ID to pass](https://approov.io/docs/latest/approov-usage-documentation/#forcing-a-device-id-to-pass) to get a valid attestation. 112 | 113 | ### RUN THE APP 114 | 115 | You can now use the Echo Chamber app and play with it, but you need to restart it in order for the mobile to get a valid Approov token, because in the first launch it was not yet registered with the Approov cloud service. 116 | 117 | ### WHAT IF THE ECHO CHAMBER APP DOESN'T WORK 118 | 119 | If you are unable to use the app then this may be because the device you are using has some characteristics that cause rejection for the currently set [Security Policy](https://approov.io/docs/latest/approov-usage-documentation/#security-policies) on your Approov account: 120 | 121 | * Ensure that the version of the app you are running is signed with the correct certificate. 122 | * Look at the Flutter logging for the device. Information about any Approov token fetched or an error is output at the debug level and is prefixed `ApproovService: updateRequest`. You can easily check the validity of the [loggable token](https://approov.io/docs/latest/approov-usage-documentation/#loggable-tokens) provided find out any reason for a failure. 123 | * Use `approov metrics` to see [Live Metrics](https://approov.io/docs/latest/approov-usage-documentation/#metrics-graphs) of the cause of failure. 124 | * You can use a debugger or emulator/simulator and get valid Approov tokens on a specific device by ensuring you are [forcing a device ID to pass](https://approov.io/docs/latest/approov-usage-documentation/#forcing-a-device-id-to-pass). As a shortcut, you can use the `latest` as discussed so that the `device ID` doesn't need to be extracted from the logs or an Approov token. 125 | * Also, you can use a debugger or Android emulator and get valid Approov tokens on any device if you [mark the signing certificate as being for development](https://approov.io/docs/latest/approov-usage-documentation/#development-app-signing-certificates). 126 | * Approov token data is logged to the console using a secure mechanism - that is, a _loggable_ version of the token is logged, rather than the _actual_ token for debug purposes. This is covered [here](https://www.approov.io/docs/latest/approov-usage-documentation/#loggable-tokens). 127 | -------------------------------------------------------------------------------- /src/echo-chamber-app/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0CCE44FBBAEAE0BC4F1586BB /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D06D5DC94C4E96FC2BF3C4E9 /* libPods-Runner.a */; }; 11 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 12 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 13 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 16 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 17 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 26 | remoteInfo = Runner; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXCopyFilesBuildPhase section */ 31 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 32 | isa = PBXCopyFilesBuildPhase; 33 | buildActionMask = 2147483647; 34 | dstPath = ""; 35 | dstSubfolderSpec = 10; 36 | files = ( 37 | ); 38 | name = "Embed Frameworks"; 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXCopyFilesBuildPhase section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 45 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 46 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 47 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 341A4B1473825213B9F9F134 /* 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 = ""; }; 49 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 50 | 57CA031116119EADA070264D /* 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 = ""; }; 51 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 52 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 53 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 54 | 916D1DB2B3302386F383F6B0 /* 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 = ""; }; 55 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 56 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 57 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 59 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 60 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 61 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | D06D5DC94C4E96FC2BF3C4E9 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | 0CCE44FBBAEAE0BC4F1586BB /* libPods-Runner.a in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 81 | ); 82 | path = RunnerTests; 83 | sourceTree = ""; 84 | }; 85 | 5936ADC6D94115DD50C8A484 /* Frameworks */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | D06D5DC94C4E96FC2BF3C4E9 /* libPods-Runner.a */, 89 | ); 90 | name = Frameworks; 91 | sourceTree = ""; 92 | }; 93 | 9740EEB11CF90186004384FC /* Flutter */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 97 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 98 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 99 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 100 | ); 101 | name = Flutter; 102 | sourceTree = ""; 103 | }; 104 | 97C146E51CF9000F007C117D = { 105 | isa = PBXGroup; 106 | children = ( 107 | 9740EEB11CF90186004384FC /* Flutter */, 108 | 97C146F01CF9000F007C117D /* Runner */, 109 | 97C146EF1CF9000F007C117D /* Products */, 110 | 331C8082294A63A400263BE5 /* RunnerTests */, 111 | DA4D71B11D254D96FFC2D9C4 /* Pods */, 112 | 5936ADC6D94115DD50C8A484 /* Frameworks */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | 97C146EF1CF9000F007C117D /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 97C146EE1CF9000F007C117D /* Runner.app */, 120 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 121 | ); 122 | name = Products; 123 | sourceTree = ""; 124 | }; 125 | 97C146F01CF9000F007C117D /* Runner */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 129 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 130 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 131 | 97C147021CF9000F007C117D /* Info.plist */, 132 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 133 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 134 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 135 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 136 | ); 137 | path = Runner; 138 | sourceTree = ""; 139 | }; 140 | DA4D71B11D254D96FFC2D9C4 /* Pods */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 57CA031116119EADA070264D /* Pods-Runner.debug.xcconfig */, 144 | 341A4B1473825213B9F9F134 /* Pods-Runner.release.xcconfig */, 145 | 916D1DB2B3302386F383F6B0 /* Pods-Runner.profile.xcconfig */, 146 | ); 147 | path = Pods; 148 | sourceTree = ""; 149 | }; 150 | /* End PBXGroup section */ 151 | 152 | /* Begin PBXNativeTarget section */ 153 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 154 | isa = PBXNativeTarget; 155 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 156 | buildPhases = ( 157 | 331C807D294A63A400263BE5 /* Sources */, 158 | 331C807F294A63A400263BE5 /* Resources */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 164 | ); 165 | name = RunnerTests; 166 | productName = RunnerTests; 167 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 168 | productType = "com.apple.product-type.bundle.unit-test"; 169 | }; 170 | 97C146ED1CF9000F007C117D /* Runner */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 173 | buildPhases = ( 174 | 7A747E8E7AAE5A027707A68A /* [CP] Check Pods Manifest.lock */, 175 | 9740EEB61CF901F6004384FC /* Run Script */, 176 | 97C146EA1CF9000F007C117D /* Sources */, 177 | 97C146EB1CF9000F007C117D /* Frameworks */, 178 | 97C146EC1CF9000F007C117D /* Resources */, 179 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 180 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 181 | 4C4B45B52A13C5F86AE1ED70 /* [CP] Copy Pods Resources */, 182 | ); 183 | buildRules = ( 184 | ); 185 | dependencies = ( 186 | ); 187 | name = Runner; 188 | productName = Runner; 189 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 190 | productType = "com.apple.product-type.application"; 191 | }; 192 | /* End PBXNativeTarget section */ 193 | 194 | /* Begin PBXProject section */ 195 | 97C146E61CF9000F007C117D /* Project object */ = { 196 | isa = PBXProject; 197 | attributes = { 198 | BuildIndependentTargetsInParallel = YES; 199 | LastUpgradeCheck = 1510; 200 | ORGANIZATIONNAME = ""; 201 | TargetAttributes = { 202 | 331C8080294A63A400263BE5 = { 203 | CreatedOnToolsVersion = 14.0; 204 | TestTargetID = 97C146ED1CF9000F007C117D; 205 | }; 206 | 97C146ED1CF9000F007C117D = { 207 | CreatedOnToolsVersion = 7.3.1; 208 | LastSwiftMigration = 1100; 209 | }; 210 | }; 211 | }; 212 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 213 | compatibilityVersion = "Xcode 9.3"; 214 | developmentRegion = en; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | Base, 219 | ); 220 | mainGroup = 97C146E51CF9000F007C117D; 221 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 222 | projectDirPath = ""; 223 | projectRoot = ""; 224 | targets = ( 225 | 97C146ED1CF9000F007C117D /* Runner */, 226 | 331C8080294A63A400263BE5 /* RunnerTests */, 227 | ); 228 | }; 229 | /* End PBXProject section */ 230 | 231 | /* Begin PBXResourcesBuildPhase section */ 232 | 331C807F294A63A400263BE5 /* Resources */ = { 233 | isa = PBXResourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | 97C146EC1CF9000F007C117D /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 244 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 245 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 246 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | /* End PBXResourcesBuildPhase section */ 251 | 252 | /* Begin PBXShellScriptBuildPhase section */ 253 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 254 | isa = PBXShellScriptBuildPhase; 255 | alwaysOutOfDate = 1; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | inputPaths = ( 260 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 261 | ); 262 | name = "Thin Binary"; 263 | outputPaths = ( 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | shellPath = /bin/sh; 267 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 268 | }; 269 | 4C4B45B52A13C5F86AE1ED70 /* [CP] Copy Pods Resources */ = { 270 | isa = PBXShellScriptBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | inputFileListPaths = ( 275 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", 276 | ); 277 | name = "[CP] Copy Pods Resources"; 278 | outputFileListPaths = ( 279 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | shellPath = /bin/sh; 283 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; 284 | showEnvVarsInLog = 0; 285 | }; 286 | 7A747E8E7AAE5A027707A68A /* [CP] Check Pods Manifest.lock */ = { 287 | isa = PBXShellScriptBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | ); 291 | inputFileListPaths = ( 292 | ); 293 | inputPaths = ( 294 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 295 | "${PODS_ROOT}/Manifest.lock", 296 | ); 297 | name = "[CP] Check Pods Manifest.lock"; 298 | outputFileListPaths = ( 299 | ); 300 | outputPaths = ( 301 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | shellPath = /bin/sh; 305 | 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"; 306 | showEnvVarsInLog = 0; 307 | }; 308 | 9740EEB61CF901F6004384FC /* Run Script */ = { 309 | isa = PBXShellScriptBuildPhase; 310 | alwaysOutOfDate = 1; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | ); 314 | inputPaths = ( 315 | ); 316 | name = "Run Script"; 317 | outputPaths = ( 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | shellPath = /bin/sh; 321 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 322 | }; 323 | /* End PBXShellScriptBuildPhase section */ 324 | 325 | /* Begin PBXSourcesBuildPhase section */ 326 | 331C807D294A63A400263BE5 /* Sources */ = { 327 | isa = PBXSourcesBuildPhase; 328 | buildActionMask = 2147483647; 329 | files = ( 330 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 331 | ); 332 | runOnlyForDeploymentPostprocessing = 0; 333 | }; 334 | 97C146EA1CF9000F007C117D /* Sources */ = { 335 | isa = PBXSourcesBuildPhase; 336 | buildActionMask = 2147483647; 337 | files = ( 338 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 339 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | /* End PBXSourcesBuildPhase section */ 344 | 345 | /* Begin PBXTargetDependency section */ 346 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 347 | isa = PBXTargetDependency; 348 | target = 97C146ED1CF9000F007C117D /* Runner */; 349 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 350 | }; 351 | /* End PBXTargetDependency section */ 352 | 353 | /* Begin PBXVariantGroup section */ 354 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 355 | isa = PBXVariantGroup; 356 | children = ( 357 | 97C146FB1CF9000F007C117D /* Base */, 358 | ); 359 | name = Main.storyboard; 360 | sourceTree = ""; 361 | }; 362 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 363 | isa = PBXVariantGroup; 364 | children = ( 365 | 97C147001CF9000F007C117D /* Base */, 366 | ); 367 | name = LaunchScreen.storyboard; 368 | sourceTree = ""; 369 | }; 370 | /* End PBXVariantGroup section */ 371 | 372 | /* Begin XCBuildConfiguration section */ 373 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | ALWAYS_SEARCH_USER_PATHS = NO; 377 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 378 | CLANG_ANALYZER_NONNULL = YES; 379 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 380 | CLANG_CXX_LIBRARY = "libc++"; 381 | CLANG_ENABLE_MODULES = YES; 382 | CLANG_ENABLE_OBJC_ARC = YES; 383 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 384 | CLANG_WARN_BOOL_CONVERSION = YES; 385 | CLANG_WARN_COMMA = YES; 386 | CLANG_WARN_CONSTANT_CONVERSION = YES; 387 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INFINITE_RECURSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 398 | CLANG_WARN_STRICT_PROTOTYPES = YES; 399 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 400 | CLANG_WARN_UNREACHABLE_CODE = YES; 401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 402 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 403 | COPY_PHASE_STRIP = NO; 404 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 405 | ENABLE_NS_ASSERTIONS = NO; 406 | ENABLE_STRICT_OBJC_MSGSEND = YES; 407 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 408 | GCC_C_LANGUAGE_STANDARD = gnu99; 409 | GCC_NO_COMMON_BLOCKS = YES; 410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 414 | GCC_WARN_UNUSED_FUNCTION = YES; 415 | GCC_WARN_UNUSED_VARIABLE = YES; 416 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 417 | MTL_ENABLE_DEBUG_INFO = NO; 418 | SDKROOT = iphoneos; 419 | SUPPORTED_PLATFORMS = iphoneos; 420 | TARGETED_DEVICE_FAMILY = "1,2"; 421 | VALIDATE_PRODUCT = YES; 422 | }; 423 | name = Profile; 424 | }; 425 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 426 | isa = XCBuildConfiguration; 427 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 428 | buildSettings = { 429 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 430 | CLANG_ENABLE_MODULES = YES; 431 | CODE_SIGN_STYLE = Manual; 432 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 433 | DEVELOPMENT_TEAM = ""; 434 | ENABLE_BITCODE = NO; 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = ( 437 | "$(inherited)", 438 | "@executable_path/Frameworks", 439 | ); 440 | PRODUCT_BUNDLE_IDENTIFIER = io.approov.echo; 441 | PRODUCT_NAME = "$(TARGET_NAME)"; 442 | PROVISIONING_PROFILE_SPECIFIER = ""; 443 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 444 | SWIFT_VERSION = 5.0; 445 | VERSIONING_SYSTEM = "apple-generic"; 446 | }; 447 | name = Profile; 448 | }; 449 | 331C8088294A63A400263BE5 /* Debug */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | BUNDLE_LOADER = "$(TEST_HOST)"; 453 | CODE_SIGN_STYLE = Automatic; 454 | CURRENT_PROJECT_VERSION = 1; 455 | DEVELOPMENT_TEAM = 653V7L98XW; 456 | GENERATE_INFOPLIST_FILE = YES; 457 | MARKETING_VERSION = 1.0; 458 | PRODUCT_BUNDLE_IDENTIFIER = io.approov.echo.RunnerTests; 459 | PRODUCT_NAME = "$(TARGET_NAME)"; 460 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 461 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 462 | SWIFT_VERSION = 5.0; 463 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 464 | }; 465 | name = Debug; 466 | }; 467 | 331C8089294A63A400263BE5 /* Release */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | BUNDLE_LOADER = "$(TEST_HOST)"; 471 | CODE_SIGN_STYLE = Automatic; 472 | CURRENT_PROJECT_VERSION = 1; 473 | DEVELOPMENT_TEAM = 653V7L98XW; 474 | GENERATE_INFOPLIST_FILE = YES; 475 | MARKETING_VERSION = 1.0; 476 | PRODUCT_BUNDLE_IDENTIFIER = io.approov.echo.RunnerTests; 477 | PRODUCT_NAME = "$(TARGET_NAME)"; 478 | SWIFT_VERSION = 5.0; 479 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 480 | }; 481 | name = Release; 482 | }; 483 | 331C808A294A63A400263BE5 /* Profile */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | BUNDLE_LOADER = "$(TEST_HOST)"; 487 | CODE_SIGN_STYLE = Automatic; 488 | CURRENT_PROJECT_VERSION = 1; 489 | DEVELOPMENT_TEAM = 653V7L98XW; 490 | GENERATE_INFOPLIST_FILE = YES; 491 | MARKETING_VERSION = 1.0; 492 | PRODUCT_BUNDLE_IDENTIFIER = io.approov.echo.RunnerTests; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | SWIFT_VERSION = 5.0; 495 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 496 | }; 497 | name = Profile; 498 | }; 499 | 97C147031CF9000F007C117D /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | buildSettings = { 502 | ALWAYS_SEARCH_USER_PATHS = NO; 503 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 504 | CLANG_ANALYZER_NONNULL = YES; 505 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 506 | CLANG_CXX_LIBRARY = "libc++"; 507 | CLANG_ENABLE_MODULES = YES; 508 | CLANG_ENABLE_OBJC_ARC = YES; 509 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 510 | CLANG_WARN_BOOL_CONVERSION = YES; 511 | CLANG_WARN_COMMA = YES; 512 | CLANG_WARN_CONSTANT_CONVERSION = YES; 513 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 514 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 515 | CLANG_WARN_EMPTY_BODY = YES; 516 | CLANG_WARN_ENUM_CONVERSION = YES; 517 | CLANG_WARN_INFINITE_RECURSION = YES; 518 | CLANG_WARN_INT_CONVERSION = YES; 519 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 520 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 521 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 522 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 523 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 524 | CLANG_WARN_STRICT_PROTOTYPES = YES; 525 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 526 | CLANG_WARN_UNREACHABLE_CODE = YES; 527 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 528 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 529 | COPY_PHASE_STRIP = NO; 530 | DEBUG_INFORMATION_FORMAT = dwarf; 531 | ENABLE_STRICT_OBJC_MSGSEND = YES; 532 | ENABLE_TESTABILITY = YES; 533 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 534 | GCC_C_LANGUAGE_STANDARD = gnu99; 535 | GCC_DYNAMIC_NO_PIC = NO; 536 | GCC_NO_COMMON_BLOCKS = YES; 537 | GCC_OPTIMIZATION_LEVEL = 0; 538 | GCC_PREPROCESSOR_DEFINITIONS = ( 539 | "DEBUG=1", 540 | "$(inherited)", 541 | ); 542 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 543 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 544 | GCC_WARN_UNDECLARED_SELECTOR = YES; 545 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 546 | GCC_WARN_UNUSED_FUNCTION = YES; 547 | GCC_WARN_UNUSED_VARIABLE = YES; 548 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 549 | MTL_ENABLE_DEBUG_INFO = YES; 550 | ONLY_ACTIVE_ARCH = YES; 551 | SDKROOT = iphoneos; 552 | TARGETED_DEVICE_FAMILY = "1,2"; 553 | }; 554 | name = Debug; 555 | }; 556 | 97C147041CF9000F007C117D /* Release */ = { 557 | isa = XCBuildConfiguration; 558 | buildSettings = { 559 | ALWAYS_SEARCH_USER_PATHS = NO; 560 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 561 | CLANG_ANALYZER_NONNULL = YES; 562 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 563 | CLANG_CXX_LIBRARY = "libc++"; 564 | CLANG_ENABLE_MODULES = YES; 565 | CLANG_ENABLE_OBJC_ARC = YES; 566 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 567 | CLANG_WARN_BOOL_CONVERSION = YES; 568 | CLANG_WARN_COMMA = YES; 569 | CLANG_WARN_CONSTANT_CONVERSION = YES; 570 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 571 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 572 | CLANG_WARN_EMPTY_BODY = YES; 573 | CLANG_WARN_ENUM_CONVERSION = YES; 574 | CLANG_WARN_INFINITE_RECURSION = YES; 575 | CLANG_WARN_INT_CONVERSION = YES; 576 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 577 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 578 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 579 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 580 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 581 | CLANG_WARN_STRICT_PROTOTYPES = YES; 582 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 583 | CLANG_WARN_UNREACHABLE_CODE = YES; 584 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 585 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 586 | COPY_PHASE_STRIP = NO; 587 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 588 | ENABLE_NS_ASSERTIONS = NO; 589 | ENABLE_STRICT_OBJC_MSGSEND = YES; 590 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 591 | GCC_C_LANGUAGE_STANDARD = gnu99; 592 | GCC_NO_COMMON_BLOCKS = YES; 593 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 594 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 595 | GCC_WARN_UNDECLARED_SELECTOR = YES; 596 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 597 | GCC_WARN_UNUSED_FUNCTION = YES; 598 | GCC_WARN_UNUSED_VARIABLE = YES; 599 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 600 | MTL_ENABLE_DEBUG_INFO = NO; 601 | SDKROOT = iphoneos; 602 | SUPPORTED_PLATFORMS = iphoneos; 603 | SWIFT_COMPILATION_MODE = wholemodule; 604 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 605 | TARGETED_DEVICE_FAMILY = "1,2"; 606 | VALIDATE_PRODUCT = YES; 607 | }; 608 | name = Release; 609 | }; 610 | 97C147061CF9000F007C117D /* Debug */ = { 611 | isa = XCBuildConfiguration; 612 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 613 | buildSettings = { 614 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 615 | CLANG_ENABLE_MODULES = YES; 616 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 617 | DEVELOPMENT_TEAM = 653V7L98XW; 618 | ENABLE_BITCODE = NO; 619 | INFOPLIST_FILE = Runner/Info.plist; 620 | LD_RUNPATH_SEARCH_PATHS = ( 621 | "$(inherited)", 622 | "@executable_path/Frameworks", 623 | ); 624 | PRODUCT_BUNDLE_IDENTIFIER = io.approov.echo; 625 | PRODUCT_NAME = "$(TARGET_NAME)"; 626 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 627 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 628 | SWIFT_VERSION = 5.0; 629 | VERSIONING_SYSTEM = "apple-generic"; 630 | }; 631 | name = Debug; 632 | }; 633 | 97C147071CF9000F007C117D /* Release */ = { 634 | isa = XCBuildConfiguration; 635 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 636 | buildSettings = { 637 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 638 | CLANG_ENABLE_MODULES = YES; 639 | CODE_SIGN_STYLE = Manual; 640 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 641 | DEVELOPMENT_TEAM = ""; 642 | ENABLE_BITCODE = NO; 643 | INFOPLIST_FILE = Runner/Info.plist; 644 | LD_RUNPATH_SEARCH_PATHS = ( 645 | "$(inherited)", 646 | "@executable_path/Frameworks", 647 | ); 648 | PRODUCT_BUNDLE_IDENTIFIER = io.approov.echo; 649 | PRODUCT_NAME = "$(TARGET_NAME)"; 650 | PROVISIONING_PROFILE_SPECIFIER = ""; 651 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 652 | SWIFT_VERSION = 5.0; 653 | VERSIONING_SYSTEM = "apple-generic"; 654 | }; 655 | name = Release; 656 | }; 657 | /* End XCBuildConfiguration section */ 658 | 659 | /* Begin XCConfigurationList section */ 660 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 661 | isa = XCConfigurationList; 662 | buildConfigurations = ( 663 | 331C8088294A63A400263BE5 /* Debug */, 664 | 331C8089294A63A400263BE5 /* Release */, 665 | 331C808A294A63A400263BE5 /* Profile */, 666 | ); 667 | defaultConfigurationIsVisible = 0; 668 | defaultConfigurationName = Release; 669 | }; 670 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 671 | isa = XCConfigurationList; 672 | buildConfigurations = ( 673 | 97C147031CF9000F007C117D /* Debug */, 674 | 97C147041CF9000F007C117D /* Release */, 675 | 249021D3217E4FDB00AE95B9 /* Profile */, 676 | ); 677 | defaultConfigurationIsVisible = 0; 678 | defaultConfigurationName = Release; 679 | }; 680 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 681 | isa = XCConfigurationList; 682 | buildConfigurations = ( 683 | 97C147061CF9000F007C117D /* Debug */, 684 | 97C147071CF9000F007C117D /* Release */, 685 | 249021D4217E4FDB00AE95B9 /* Profile */, 686 | ); 687 | defaultConfigurationIsVisible = 0; 688 | defaultConfigurationName = Release; 689 | }; 690 | /* End XCConfigurationList section */ 691 | }; 692 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 693 | } 694 | --------------------------------------------------------------------------------