├── .idea ├── .gitignore ├── vcs.xml ├── modules.xml ├── dart_meteor.iml ├── libraries │ └── Dart_SDK.xml └── codeStyles │ └── Project.xml ├── example ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── AppFrameworkInfo.plist │ │ ├── Generated.xcconfig │ │ └── flutter_export_environment.sh │ ├── 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 │ │ ├── GeneratedPluginRegistrant.m │ │ ├── AppDelegate.swift │ │ ├── GeneratedPluginRegistrant.h │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── RunnerTests │ │ └── RunnerTests.swift │ └── .gitignore ├── android │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── 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 │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── dart_meteor_example_app │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ ├── java │ │ │ │ │ └── io │ │ │ │ │ │ └── flutter │ │ │ │ │ │ └── plugins │ │ │ │ │ │ └── GeneratedPluginRegistrant.java │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle.kts │ ├── gradle.properties │ ├── local.properties │ ├── .gitignore │ ├── build.gradle.kts │ ├── settings.gradle.kts │ ├── example_android.iml │ ├── gradlew.bat │ └── gradlew ├── .idea │ ├── runConfigurations │ │ └── main_dart.xml │ ├── libraries │ │ ├── Flutter_for_Android.xml │ │ ├── KotlinJavaRuntime.xml │ │ └── Dart_SDK.xml │ ├── modules.xml │ └── workspace.xml ├── README.md ├── .gitignore ├── example.iml ├── .metadata ├── analysis_options.yaml ├── pubspec.yaml └── lib │ └── main.dart ├── lib ├── dart_meteor.dart └── src │ ├── meteor_client.dart │ └── ddp_client.dart ├── .gitignore ├── analysis_options.yaml ├── pubspec.yaml ├── CHANGELOG.md ├── .github └── workflows │ └── dart.yml ├── LICENSE ├── README.md └── test └── dart_meteor_test.dart /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /lib/dart_meteor.dart: -------------------------------------------------------------------------------- 1 | /// Meteor connection for dart 2 | /// This library is a meteor client warpper 3 | library dart_meteor; 4 | 5 | export 'src/meteor_client.dart'; 6 | export 'src/ddp_client.dart'; 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanutapi/dart_meteor/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/android/local.properties: -------------------------------------------------------------------------------- 1 | sdk.dir=/Users/tanutapiwong/Library/Android/sdk 2 | flutter.sdk=/Users/tanutapiwong/development/flutter 3 | flutter.buildMode=debug 4 | flutter.versionName=1.0.0 5 | flutter.versionCode=1 -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/dart_meteor_example_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.dart_meteor_example_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/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.12-all.zip 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/.idea/runConfigurations/main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/GeneratedPluginRegistrant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #import "GeneratedPluginRegistrant.h" 8 | 9 | @implementation GeneratedPluginRegistrant 10 | 11 | + (void)registerWithRegistry:(NSObject*)registry { 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/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. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea/ 3 | 4 | # Files and directories created by pub 5 | .dart_tool/ 6 | .packages 7 | .vscode/ 8 | # Remove the following pattern if you wish to check in your lock file 9 | pubspec.lock 10 | 11 | # Conventional directory for build outputs 12 | build/ 13 | 14 | # Directory created by dartdoc 15 | doc/api/ 16 | 17 | # File changes automatically when you need run the example 18 | example/android/local.properties -------------------------------------------------------------------------------- /example/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Defines a default set of lint rules enforced for 2 | # projects at Google. For details and rationale, 3 | # see https://github.com/dart-lang/lints#enabled-lints. 4 | include: package:lints/recommended.yaml 5 | 6 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 7 | # Uncomment to specify additional rules. 8 | # linter: 9 | # rules: 10 | # - camel_case_types 11 | 12 | analyzer: 13 | # exclude: 14 | # - path/to/excluded/files/** 15 | -------------------------------------------------------------------------------- /example/ios/Runner/GeneratedPluginRegistrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GeneratedPluginRegistrant_h 8 | #define GeneratedPluginRegistrant_h 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface GeneratedPluginRegistrant : NSObject 15 | + (void)registerWithRegistry:(NSObject*)registry; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | #endif /* GeneratedPluginRegistrant_h */ 20 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_meteor 2 | description: This library make connection between meteor backend and flutter app easily. Design to work seamlessly with StreamBuilder and FutureBuilder. 3 | version: 3.1.0 4 | homepage: https://github.com/tanutapi/dart_meteor 5 | 6 | environment: 7 | sdk: '>=2.12.0 <4.0.0' 8 | 9 | dependencies: 10 | crypto: ^3.0.2 11 | rxdart: ^0.28.0 12 | web_socket_channel: ^3.0.3 13 | 14 | dev_dependencies: 15 | lints: ^6.0.0 16 | test: ^1.17.4 17 | collection: ^1.15.0 18 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import androidx.annotation.Keep; 4 | import androidx.annotation.NonNull; 5 | import io.flutter.Log; 6 | 7 | import io.flutter.embedding.engine.FlutterEngine; 8 | 9 | /** 10 | * Generated file. Do not edit. 11 | * This file is generated by the Flutter tool based on the 12 | * plugins that support the Android platform. 13 | */ 14 | @Keep 15 | public final class GeneratedPluginRegistrant { 16 | private static final String TAG = "GeneratedPluginRegistrant"; 17 | public static void registerWith(@NonNull FlutterEngine flutterEngine) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/.idea/libraries/KotlinJavaRuntime.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # dart_meteor_example_app 2 | 3 | An example flutter project that use dart_meteor package. 4 | 5 | ## Getting Started 6 | 7 | Make a change in lib/main.dart to point to your meteor backend. You can use both http://, https://, ws:// or wsss://. 8 | ```dart 9 | import 'package:flutter/material.dart'; 10 | import 'package:dart_meteor/dart_meteor.dart'; 11 | 12 | MeteorClient meteor = MeteorClient.connect(url: 'https://yourdomain.com'); 13 | void main() => runApp(MyApp()); 14 | ``` 15 | 16 | Run flutter create in this example folder: 17 | ``` 18 | flutter create . 19 | ``` 20 | 21 | Run pub get in this example folder: 22 | ``` 23 | flutter pub get 24 | ``` 25 | 26 | Open Android emulator or iOS simulator then run the flutter: 27 | ``` 28 | flutter run 29 | ``` 30 | -------------------------------------------------------------------------------- /example/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.3" apply false 22 | id("org.jetbrains.kotlin.android") version "2.1.0" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /.idea/dart_meteor.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/ios/Flutter/Generated.xcconfig: -------------------------------------------------------------------------------- 1 | // This is a generated file; do not edit or check into version control. 2 | FLUTTER_ROOT=/Users/tanutapiwong/development/flutter 3 | FLUTTER_APPLICATION_PATH=/Users/tanutapiwong/Projects/dart_meteor/example 4 | COCOAPODS_PARALLEL_CODE_SIGN=true 5 | FLUTTER_TARGET=/Users/tanutapiwong/Projects/dart_meteor/example/lib/main.dart 6 | FLUTTER_BUILD_DIR=build 7 | FLUTTER_BUILD_NAME=1.0.0 8 | FLUTTER_BUILD_NUMBER=1 9 | EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 10 | EXCLUDED_ARCHS[sdk=iphoneos*]=armv7 11 | DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzIuMA==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049YmU2OThjNDhhNg==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049MTg4MTgwMDk0OQ==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My44LjA= 12 | DART_OBFUSCATION=false 13 | TRACK_WIDGET_CREATION=true 14 | TREE_SHAKE_ICONS=false 15 | PACKAGE_CONFIG=/Users/tanutapiwong/Projects/dart_meteor/example/.dart_tool/package_config.json 16 | -------------------------------------------------------------------------------- /example/ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/tanutapiwong/development/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/tanutapiwong/Projects/dart_meteor/example" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=/Users/tanutapiwong/Projects/dart_meteor/example/lib/main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "FLUTTER_BUILD_NAME=1.0.0" 9 | export "FLUTTER_BUILD_NUMBER=1" 10 | export "DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzIuMA==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049YmU2OThjNDhhNg==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049MTg4MTgwMDk0OQ==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My44LjA=" 11 | export "DART_OBFUSCATION=false" 12 | export "TRACK_WIDGET_CREATION=true" 13 | export "TREE_SHAKE_ICONS=false" 14 | export "PACKAGE_CONFIG=/Users/tanutapiwong/Projects/dart_meteor/example/.dart_tool/package_config.json" 15 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "be698c48a6750c8cb8e61c740ca9991bb947aba2" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 17 | base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 18 | - platform: android 19 | create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 20 | base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 21 | - platform: ios 22 | create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 23 | base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 4.0.0-beta.1, 4.0.0-beta.2 2 | - Adding Web platform support by using the `web_socket_channel`. 3 | 4 | # 3.0.0 5 | - BREAKING CHANGE. The `meteor.collection('collectionName')` streams are now `hasData == true` and have an empty map at the beginning. 6 | # 2.1.4 7 | - Acessing to serverId and sessionId 8 | # 2.1.3 9 | - Resend subscription packets on reconnection. 10 | # 2.1.2 11 | - Return MeteorClientLoginResult on logoutOtherClients. 12 | # 2.1.1 13 | - Return null if no current value presents for the 'currentValue'. 14 | # 2.1.0 15 | - Fix a major bug on escaping DateTime when doing method call and subscribe. 16 | # 2.0.4 17 | - Fix bug on meteor.user() does not set back to null after user has been logged out. 18 | ## 2.0.3 19 | - Separated login function. 20 | 21 | ## 2.0.2 22 | - Fix bugs on connecting to Meteor 2.x.x and on logout function. 23 | 24 | ## 2.0.1 25 | - Fix a $date bug when parsing an array result from methods/collections. 26 | 27 | ## 2.0.0 28 | - Null safety and some API changes. 29 | 30 | ## 1.1.2 31 | - Lower crypto package version to match the flutter_test. 32 | 33 | ## 1.1.1 34 | - Allows both int and String for MeteorError.error 35 | 36 | ## 1.1.0 37 | 38 | - Pin rxdart to 0.24.1 and crypto to 2.1.5. 39 | 40 | ## 1.0.8 41 | 42 | - Allow passing email to loginWithPassword. 43 | 44 | ## 1.0.7 45 | 46 | - **Don't use this release** 47 | 48 | ## 1.0.5 - 1.0.6 49 | 50 | - Pin rxdart version to 0.22.6 51 | 52 | ## 1.0.1 - 1.0.4 53 | 54 | - Update README and example 55 | 56 | ## 1.0.0 - 1.0.3 57 | 58 | - Initial version. 59 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Prepare docker network 11 | run: docker network create test_net 12 | - name: Start MongoDB 13 | run: docker run --rm --name mongodb --network test_net -d mongo 14 | - run: docker ps 15 | - run: sleep 5 16 | - run: docker ps 17 | - name: Start webapp 18 | run: docker run --rm --name webapp --network test_net -p 3000:3000 -e "MONGO_URL=mongodb://mongodb:27017/meteor" -e "ROOT_URL=http://webapp" -d tanutapi/simple-meteor-chat:latest 19 | - run: sleep 30 20 | - run: docker ps 21 | - run: docker logs webapp 22 | - run: sudo apt-get update 23 | - run: sudo apt-get install apt-transport-https curl -y 24 | - name: Check webapp 25 | run: curl 127.0.0.1:3000 26 | - run: sudo sh -c 'wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' 27 | - run: sudo sh -c 'wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list' 28 | - run: sudo apt-get update 29 | - name: Install dart 30 | run: sudo apt-get install dart -y 31 | - run: export PATH="$PATH:/usr/lib/dart/bin" 32 | - uses: actions/checkout@v1 33 | - name: Install dependencies 34 | run: rm -rf ./example && PATH="$PATH:/usr/lib/dart/bin" dart pub get 35 | - name: Run tests 36 | run: PATH="$PATH:/usr/lib/dart/bin" dart run test 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Tanut Apiwong 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of the project. -------------------------------------------------------------------------------- /example/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 = "com.example.dart_meteor_example_app" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_11.toString() 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.example.dart_meteor_example_app" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.getByName("debug") 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /example/.idea/workspace.xml: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/android/example_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Dart Meteor Example App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | dart_meteor_example_app 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_meteor_example_app 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=3.8.0 <4.0.0" 18 | flutter: ">=3.32.0" 19 | 20 | dependencies: 21 | flutter: 22 | sdk: flutter 23 | 24 | # The following adds the Cupertino Icons font to your application. 25 | # Use with the CupertinoIcons class for iOS style icons. 26 | cupertino_icons: ^1.0.8 27 | dart_meteor: 28 | path: ../ 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # The "flutter_lints" package below contains a set of recommended lints to 35 | # encourage good coding practices. The lint set provided by the package is 36 | # activated in the `analysis_options.yaml` file located at the root of your 37 | # package. See that file for information about deactivating specific lint 38 | # rules and activating additional ones. 39 | flutter_lints: ^5.0.0 40 | 41 | 42 | # For information on the generic Dart part of this file, see the 43 | # following page: https://dart.dev/tools/pub/pubspec 44 | 45 | # The following section is specific to Flutter. 46 | flutter: 47 | 48 | # The following line ensures that the Material Icons font is 49 | # included with your application, so that you can use the icons in 50 | # the material Icons class. 51 | uses-material-design: true 52 | 53 | # To add assets to your application, add an assets section, like this: 54 | # assets: 55 | # - images/a_dot_burr.jpeg 56 | # - images/a_dot_ham.jpeg 57 | 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | # https://flutter.dev/assets-and-images/#resolution-aware. 60 | 61 | # For details regarding adding assets from package dependencies, see 62 | # https://flutter.dev/assets-and-images/#from-packages 63 | 64 | # To add custom fonts to your application, add a fonts section here, 65 | # in this "flutter" section. Each entry in this list should have a 66 | # "family" key with the font family name, and a "fonts" key with a 67 | # list giving the asset and other descriptors for the font. For 68 | # example: 69 | # fonts: 70 | # - family: Schyler 71 | # fonts: 72 | # - asset: fonts/Schyler-Regular.ttf 73 | # - asset: fonts/Schyler-Italic.ttf 74 | # style: italic 75 | # - family: Trajan Pro 76 | # fonts: 77 | # - asset: fonts/TrajanPro.ttf 78 | # - asset: fonts/TrajanPro_Bold.ttf 79 | # weight: 700 80 | # 81 | # For details regarding fonts from package dependencies, 82 | # see https://flutter.dev/custom-fonts/#from-packages 83 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:dart_meteor/dart_meteor.dart'; 3 | 4 | MeteorClient meteor = MeteorClient.connect(url: 'https://yourdomain.com'); 5 | void main() => runApp(MyApp()); 6 | 7 | class MyApp extends StatefulWidget { 8 | const MyApp({super.key}); 9 | 10 | @override 11 | MyAppState createState() => MyAppState(); 12 | } 13 | 14 | class MyAppState extends State { 15 | String _methodResult = ''; 16 | 17 | void _callMethod() { 18 | meteor.call('helloMethod').then((result) { 19 | setState(() { 20 | _methodResult = result.toString(); 21 | }); 22 | }).catchError((err) { 23 | if (err is MeteorError) { 24 | setState(() { 25 | _methodResult = err.message ?? 'Unknown error'; 26 | }); 27 | } 28 | }); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return MaterialApp( 34 | home: Scaffold( 35 | appBar: AppBar( 36 | title: Text('Package dart_meteor Example'), 37 | ), 38 | body: Container( 39 | padding: EdgeInsets.all(8.0), 40 | child: Column( 41 | children: [ 42 | StreamBuilder( 43 | stream: meteor.status(), 44 | builder: (context, snapshot) { 45 | if (snapshot.hasData && snapshot.data != null) { 46 | if (snapshot.data!.status == 47 | DdpConnectionStatusValues.connected) { 48 | return ElevatedButton( 49 | onPressed: () { 50 | meteor.disconnect(); 51 | }, 52 | child: Text('Disconnect'), 53 | ); 54 | } 55 | return ElevatedButton( 56 | onPressed: () { 57 | meteor.reconnect(); 58 | }, 59 | child: Text('Connect'), 60 | ); 61 | } 62 | return Container(); 63 | }, 64 | ), 65 | StreamBuilder( 66 | stream: meteor.status(), 67 | builder: (context, snapshot) { 68 | if (snapshot.hasData && snapshot.data != null) { 69 | return Text('Meteor Status ${snapshot.data!.toString()}'); 70 | } 71 | return Text('Meteor Status: ---'); 72 | }, 73 | ), 74 | StreamBuilder( 75 | stream: meteor.userId(), 76 | builder: (context, snapshot) { 77 | if (snapshot.hasData && snapshot.data != null) { 78 | return ElevatedButton( 79 | onPressed: () { 80 | meteor.logout(); 81 | }, 82 | child: Text('Logout'), 83 | ); 84 | } 85 | return ElevatedButton( 86 | onPressed: () { 87 | debugPrint('Logging in...'); 88 | meteor.loginWithPassword('yourusername', 'yourpassword').then((res) { 89 | debugPrint(res.token); 90 | }); 91 | }, 92 | child: Text('Login'), 93 | ); 94 | }), 95 | StreamBuilder( 96 | stream: meteor.user(), 97 | builder: (context, snapshot) { 98 | if (snapshot.hasData && snapshot.data != null) { 99 | return Text(snapshot.data.toString()); 100 | } 101 | return Text('User: ----'); 102 | }, 103 | ), 104 | ElevatedButton( 105 | onPressed: _callMethod, 106 | child: Text('Method Call'), 107 | ), 108 | Text(_methodResult), 109 | ], 110 | ), 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 44 | 50 | 51 | 52 | 53 | 54 | 66 | 68 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | For Dart VM, Flutter iOS/Android/Web (master branch) ![](https://github.com/tanutapi/dart_meteor/workflows/Testing/badge.svg?branch=master) 2 | 3 | # A Meteor DDP library for Dart/Flutter developers. 4 | 5 | This library connects the Meteor backend and the Flutter app—designed to work seamlessly with StreamBuilder and FutureBuilder. 6 | 7 | ## Change on 4.0.0-beta.1 8 | Using the `web_socket_channel` to make this package supports Dart VM, iOS, Android, and Web. Thank you to mel-mouk. 9 | 10 | ## Change on 3.1.0 ## 11 | Bump the SDK version to <4.0.0 and update dependencies. 12 | 13 | ## Change on 3.0.0 ## 14 | BREAKING CHANGE. The `meteor.collection('collectionName')` streams are now `snapshot.hasData == true` and have an empty map at the beginning. 15 | 16 | ## Change on 2.0.0 ## 17 | 18 | Passing arguments to the meteor method is now optional. In version 1.x.x you did: `meteor.call('your_method_name', [param1, param2])`. Now in version 2.x.x and greater, it will be `meteor.call('your_method_name', args: [param1, param2])` or just `meteor.call('your_method_name')` if you don't want to pass any argument to your method. 19 | 20 | Same as a subscription. In version 1.x.x you did: `meteor.subscribe('your_pub', [param1, param2])`. Now in version 2.x.x and greater, it will be `meteor.subscribe('your_pub', args: [param1, param2])` or just `meteor.subscribe('your_pub')` if you don't want to pass any argument to your publish function. 21 | 22 | In version 1.x.x, you have to call `meteor.prepareCollection('your_collection_name')` before you can use it. Now in version 2.x.x, you don't have to prepare a collection. You now access the collection by calling `collection` method `meteor.collection('messages').listen((value) { ... })`. 23 | 24 | `DateTime` is now directly supported. You can pass a `DateTime` variable as a meteor method parameter and receive DateTime from the collections and methods. 25 | 26 | ## Usage 27 | 28 | I have published a post on Medium showing how to handle connection status, user authentication, and subscriptions. Please check https://medium.com/@tanutapi/writing-flutter-mobile-application-with-meteor-backend-643d2c1947d0?source=friends_link&sk=52ce2fa2603934e7395e2d19dd54e06c 29 | 30 | A simple usage example: 31 | 32 | First, create an instance of MeteorClient in your app's global scope to use it anywhere in your project. 33 | 34 | ```dart 35 | import 'package:flutter/material.dart'; 36 | import 'package:dart_meteor/dart_meteor.dart'; 37 | 38 | MeteorClient meteor = MeteorClient.connect(url: 'https://yourdomain.com'); 39 | void main() => runApp(MyApp()); 40 | ``` 41 | 42 | In your StatefulWidget/StatelessWidget, thanks to [rxdart][rxdart], you can use FutuerBuilder or StreamBuilder to build your widget based on a response from meteor's DDP server. 43 | 44 | ```dart 45 | class MyApp extends StatefulWidget { 46 | @override 47 | _MyAppState createState() => _MyAppState(); 48 | } 49 | 50 | class _MyAppState extends State { 51 | String _methodResult = ''; 52 | 53 | void _callMethod() { 54 | meteor.call('helloMethod').then((result) { 55 | setState(() { 56 | _methodResult = result.toString(); 57 | }); 58 | }).catchError((err) { 59 | if (err is MeteorError) { 60 | setState(() { 61 | _methodResult = err.message; 62 | }); 63 | } 64 | }); 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return MaterialApp( 70 | home: Scaffold( 71 | appBar: AppBar( 72 | title: Text('Package dart_meteor Example'), 73 | ), 74 | body: Container( 75 | padding: EdgeInsets.all(8.0), 76 | child: Column( 77 | children: [ 78 | StreamBuilder( 79 | stream: meteor.status(), 80 | builder: (context, snapshot) { 81 | if (snapshot.hasData) { 82 | if (snapshot.data.status == 83 | DdpConnectionStatusValues.connected) { 84 | return RaisedButton( 85 | child: Text('Disconnect'), 86 | onPressed: () { 87 | meteor.disconnect(); 88 | }, 89 | ); 90 | } 91 | return RaisedButton( 92 | child: Text('Connect'), 93 | onPressed: () { 94 | meteor.reconnect(); 95 | }, 96 | ); 97 | } 98 | return Container(); 99 | }, 100 | ), 101 | StreamBuilder( 102 | stream: meteor.status(), 103 | builder: (context, snapshot) { 104 | if (snapshot.hasData) { 105 | return Text('Meteor Status ${snapshot.data.toString()}'); 106 | } 107 | return Text('Meteor Status: ---'); 108 | }, 109 | ), 110 | StreamBuilder( 111 | stream: meteor.userId(), 112 | builder: (context, snapshot) { 113 | if (snapshot.hasData) { 114 | return RaisedButton( 115 | child: Text('Logout'), 116 | onPressed: () { 117 | meteor.logout(); 118 | }, 119 | ); 120 | } 121 | return RaisedButton( 122 | child: Text('Login'), 123 | onPressed: () { 124 | meteor.loginWithPassword( 125 | 'yourusername', 'yourpassword'); 126 | }, 127 | ); 128 | }), 129 | StreamBuilder( 130 | stream: meteor.user(), 131 | builder: (context, snapshot) { 132 | if (snapshot.hasData) { 133 | return Text(snapshot.data.toString()); 134 | } 135 | return Text('User: ----'); 136 | }, 137 | ), 138 | RaisedButton( 139 | child: Text('Method Call'), 140 | onPressed: _callMethod, 141 | ), 142 | Text(_methodResult), 143 | ], 144 | ), 145 | ), 146 | ), 147 | ); 148 | } 149 | } 150 | ``` 151 | 152 | ## Making a method call to your server 153 | 154 | Making a method call to your server returns a Future. You MUST handle `catchError` to prevent your app from crashing if something goes wrong. 155 | 156 | ```dart 157 | meteor.call('helloMethod').then((result) { 158 | setState(() { 159 | _methodResult = result.toString(); 160 | }); 161 | }).catchError((err) { 162 | if (err is MeteorError) { 163 | setState(() { 164 | _methodResult = err.message; 165 | }); 166 | } 167 | }); 168 | ``` 169 | You can also use it with a FutureBuilder. 170 | ```dart 171 | FutureBuilder( 172 | future: meteor.call('sumMethod', args: [5, 10]), 173 | builder: (context, snapshot) { 174 | if (snapshot.hasData) { 175 | // your snapshot.data should be 5 + 10 = 15 176 | return Text('Answer is: ${snapshot.data}'); 177 | } 178 | }, 179 | ), 180 | ``` 181 | 182 | You can find an example project inside [/example][example]. 183 | 184 | ## Collections & Subscriptions 185 | You can access your collections by calling `collection('your_collection_name')`. 186 | It will return a `Stream`, which you can use with your `StreamBuilder`. Through the returned `Stream` reference, you can listen to the updates of the collection. 187 | 188 | ```dart 189 | meteor.collection('your_collections'); 190 | ``` 191 | 192 | The above code will return a stream backed by the rxdart `BehaviorSubject`, a special StreamController that captures the latest item added to the Stream and emits it as the first item to any new listener. You can use it as a regular Stream. 193 | 194 | To make collections available in the Flutter app, you might make a subscription to your server with the following: 195 | 196 | ```dart 197 | class YourWidget extends StatefulWidget { 198 | YourWidget() {} 199 | 200 | @override 201 | _YourWidgetState createState() => _YourWidgetState(); 202 | } 203 | 204 | class _YourWidgetState extends State { 205 | SubscriptionHandler _subscriptionHandler; 206 | 207 | @override 208 | void initState() { 209 | super.initState(); 210 | _subscriptionHandler = meteor.subscribe('your_pub', args: ['param_1', 'param_2']); 211 | } 212 | 213 | @override 214 | void dispose() { 215 | _subscriptionHandler.stop(); 216 | super.dispose(); 217 | } 218 | 219 | @override 220 | Widget build(BuildContext context) { 221 | return StreamBuilder( 222 | stream: meteor.collection('your_collection'), 223 | builder: 224 | (context, AsyncSnapshot> snapshot) { 225 | int docCount = 0; 226 | if (snapshot.hasData) { 227 | docCount = snapshot.data.length; 228 | } 229 | return Text('Total document count: $docCount'); 230 | }, 231 | ); 232 | } 233 | } 234 | ``` 235 | 236 | The collection was returned as a Map. The key is a document .\_id, and its value is the whole document. 237 | 238 | Ex. 239 | ``` 240 | { 241 | "DGbsysgxzSf7Cr8Jg": { 242 | "_id": "DGbsysgxzSf7Cr8Jg", 243 | field1: 0, 244 | field2: "a", 245 | field3: true, 246 | field4: SomeDate 247 | } 248 | } 249 | ``` 250 | We don't provide something like minimongo as the official Meteor did. You can use `reduce`, `map`, and `where` with the collection and get the same result as you did with a query in the `minimongo` `Meteor` web client. 251 | 252 | ## Don't want to access data via Stream 253 | Getting the current data from a stream is sometimes complicated. Especially when you want to get the latest value just for condition checking, you can access the latest value from the `collection`, `user`, `userId` directly with `meteor.collectionCurrentValue('your_collection_name')`, `meteor.userCurrentValue()`, and `meteor.userIdCurrentValue()`. 254 | 255 | ## findOne with _id 256 | The best way to access the document if you have an id is 257 | ``` 258 | // Non-reactive 259 | // An example of accessing a document by its id 260 | final id = 'DGbsysgxzSf7Cr8Jg'; 261 | final doc = meteor.collectionCurrentValue('your_collection_name')[id]; 262 | if (doc != null) { 263 | // do something 264 | } 265 | 266 | // Non-reactive 267 | // An example of accessing a user by userId 268 | final userId = 'Sf7Cr8JgDGbsysgxz'; 269 | final user = meteor.collectionCurrentValue('users')[userId]; 270 | if (user != null) { 271 | // do something 272 | } 273 | ``` 274 | 275 | ## Features and bugs 276 | 277 | Please file feature requests and bugs at the [issue tracker][tracker]. 278 | 279 | [tracker]: https://github.com/tanutapi/dart_meteor/issues 280 | [rxdart]: https://pub.dev/packages/rxdart 281 | [example]: https://github.com/tanutapi/dart_meteor/tree/master/example 282 | -------------------------------------------------------------------------------- /lib/src/meteor_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'package:crypto/crypto.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | import 'ddp_client.dart'; 6 | 7 | class MeteorClientLoginResult { 8 | String userId; 9 | String token; 10 | DateTime tokenExpires; 11 | MeteorClientLoginResult({ 12 | required this.userId, 13 | required this.token, 14 | required this.tokenExpires, 15 | }); 16 | } 17 | 18 | class MeteorError extends Error { 19 | String? details; 20 | dynamic error; 21 | String? errorType; 22 | bool isClientSafe = true; 23 | String? message; 24 | String? reason; 25 | String? stack; 26 | 27 | MeteorError.parse(Map object) { 28 | try { 29 | details = object['details']?.toString(); 30 | error = object['error'] is String 31 | ? int.tryParse(object['error']) ?? object['error'] 32 | : object['error']; 33 | errorType = object['errorType']?.toString(); 34 | isClientSafe = object['isClientSafe'] == true; 35 | message = object['message']?.toString(); 36 | reason = object['reason']?.toString(); 37 | stack = object['stack']?.toString(); 38 | } catch (_) {} 39 | } 40 | 41 | @override 42 | String toString() { 43 | return ''' 44 | isClientSafe: $isClientSafe 45 | errorType: $errorType 46 | error: $error 47 | details: $details 48 | message: $message 49 | reason: $reason 50 | stack: $stack 51 | '''; 52 | } 53 | } 54 | 55 | enum UserLogInStatus { 56 | loggedOut, 57 | loggedIn, 58 | loggingIn, 59 | loggingOut, 60 | } 61 | 62 | class MeteorClient { 63 | late DdpClient connection; 64 | 65 | final BehaviorSubject _statusSubject = BehaviorSubject(); 66 | late Stream _statusStream; 67 | 68 | final BehaviorSubject _logInStatusSubject = 69 | BehaviorSubject.seeded(UserLogInStatus.loggedOut); 70 | late Stream _logInStatusStream; 71 | 72 | final BehaviorSubject _loggingInSubject = BehaviorSubject(); 73 | late Stream _loggingInStream; 74 | 75 | final BehaviorSubject _userIdSubject = BehaviorSubject(); 76 | late Stream _userIdStream; 77 | 78 | final BehaviorSubject?> _userSubject = BehaviorSubject(); 79 | late Stream?> _userStream; 80 | 81 | String? _userId; 82 | String? _token; 83 | DateTime? _tokenExpires; 84 | UserLogInStatus _logInStatus = UserLogInStatus.loggedOut; 85 | 86 | /// Meteor.collections 87 | final Map> _collections = {}; 88 | final Map>> _collectionsSubject = 89 | {}; 90 | final Map>> _collectionsStreams = {}; 91 | 92 | MeteorClient.connect( 93 | {required String url, 94 | bool debug = false, 95 | userAgent = 'DartMeteor/2.0.4'}) { 96 | url = url.replaceFirst(RegExp(r'^http'), 'ws'); 97 | if (!url.endsWith('websocket')) { 98 | url = '${url.replaceFirst(RegExp(r'/$'), '')}/websocket'; 99 | } 100 | print('MeteorClient[$hashCode] - Make a connection to $url'); 101 | connection = DdpClient(url: url, debug: debug, userAgent: userAgent); 102 | 103 | connection.status().listen((ddpStatus) { 104 | _statusSubject.add(ddpStatus); 105 | }) 106 | ..onError((dynamic error) { 107 | _statusSubject.addError(error); 108 | }) 109 | ..onDone(() { 110 | _statusSubject.close(); 111 | }); 112 | _statusStream = _statusSubject.stream; 113 | 114 | _loggingInStream = _loggingInSubject.stream; 115 | _logInStatusStream = _logInStatusSubject.stream; 116 | _logInStatusStream.listen((event) { 117 | _loggingInSubject.add(event == UserLogInStatus.loggingIn); 118 | }); 119 | _userIdStream = _userIdSubject.stream; 120 | _userStream = _userSubject.stream; 121 | 122 | _prepareCollection('users'); 123 | 124 | connection.dataStreamController.stream.listen((data) { 125 | String collectionName = data['collection']; 126 | String id = data['id']; 127 | dynamic fields = data['fields']; 128 | if (fields != null) { 129 | fields['_id'] = id; 130 | DdpClient.formatSpecialFieldValues(fields); 131 | } 132 | 133 | _prepareCollection(collectionName); 134 | 135 | if (data['msg'] == 'removed') { 136 | _collections[collectionName]!.remove(id); 137 | } else if (data['msg'] == 'added') { 138 | if (fields != null) { 139 | _collections[collectionName]![id] = fields; 140 | } 141 | } else if (data['msg'] == 'changed') { 142 | if (fields != null) { 143 | if (_collections[collectionName]![id] != null && 144 | _collections[collectionName]![id] is Map) { 145 | fields.forEach((k, v) { 146 | _collections[collectionName]![id][k] = v; 147 | }); 148 | } 149 | } else if (data['cleared'] != null && data['cleared'] is List) { 150 | List clearList = data['cleared']; 151 | if (_collections[collectionName]![id] != null && 152 | _collections[collectionName]![id] is Map) { 153 | for (var k in clearList) { 154 | _collections[collectionName]![id].remove(k); 155 | } 156 | } 157 | } 158 | } 159 | 160 | _collectionsSubject[collectionName]!.add(_collections[collectionName]!); 161 | if (collectionName == 'users' && id == _userId) { 162 | if (_collections['users'] != null && 163 | _collections['users']![_userId] != null) { 164 | _userSubject.add(_collections['users']![_userId]); 165 | } 166 | } 167 | }) 168 | ..onError((dynamic error) {}) 169 | ..onDone(() {}); 170 | 171 | connection.onReconnect((OnReconnectionCallback reconnectionCallback) { 172 | print('MeteorClient[$hashCode] - connection.onReconnect()'); 173 | _loginWithExistingToken().catchError((error) { 174 | return null; 175 | }); 176 | }); 177 | 178 | _statusStream.listen((ddpStatus) { 179 | if (ddpStatus.status == DdpConnectionStatusValues.connected && 180 | !isAlreadyRunStartupFunctions) { 181 | isAlreadyRunStartupFunctions = true; 182 | for (var func in _startupFunctions) { 183 | try { 184 | func(); 185 | } catch (e) { 186 | rethrow; 187 | } 188 | } 189 | } 190 | }); 191 | 192 | userId().listen((userId) { 193 | if (_collections['users'] != null && 194 | _collections['users']![userId] != null) { 195 | _userSubject.add(_collections['users']![userId]); 196 | } else { 197 | _userSubject.add(null); 198 | } 199 | }); 200 | } 201 | 202 | void _prepareCollection(String collectionName) { 203 | if (_collections[collectionName] == null) { 204 | _collections[collectionName] = {}; 205 | var subject = _collectionsSubject[collectionName] = 206 | BehaviorSubject>(); 207 | subject.add({}); 208 | _collectionsStreams[collectionName] = subject.stream; 209 | } 210 | } 211 | 212 | /// Get [Stream] of `collection` on given a `collectionName`. 213 | Stream> collection(String collectionName) { 214 | if (_collections[collectionName] == null) { 215 | _prepareCollection(collectionName); 216 | } 217 | return _collectionsStreams[collectionName]!; 218 | } 219 | 220 | Map? collectionCurrentValue(String collectionName) { 221 | if (_collections[collectionName] == null) { 222 | _prepareCollection(collectionName); 223 | } 224 | return _collectionsSubject[collectionName]!.valueOrNull; 225 | } 226 | 227 | // =========================================================== 228 | // Core 229 | 230 | /// Boolean variable. True if running in client environment. 231 | bool isClient() { 232 | return true; 233 | } 234 | 235 | /// Boolean variable. True if running in server environment. 236 | bool isServer() { 237 | return false; 238 | } 239 | 240 | /// Boolean variable. True if running in a Cordova mobile environment. 241 | bool isCordova() { 242 | return false; 243 | } 244 | 245 | /// Boolean variable. True if running in development environment. 246 | bool isDevelopment() { 247 | return !bool.fromEnvironment('dart.vm.product'); 248 | } 249 | 250 | /// Boolean variable. True if running in production environment. 251 | bool isProduction() { 252 | return bool.fromEnvironment('dart.vm.product'); 253 | } 254 | 255 | bool isAlreadyRunStartupFunctions = false; 256 | final List _startupFunctions = []; 257 | 258 | /// Run code when a client successfully make a connection to server. 259 | void startup(Function func) { 260 | _startupFunctions.add(func); 261 | } 262 | 263 | // Meteor.wrapAsync(func, [context]) 264 | 265 | void defer(Function Function() func) { 266 | Future.delayed(Duration(seconds: 0), func); 267 | } 268 | 269 | // Meteor.absoluteUrl([path], [options]) 270 | 271 | // Meteor.settings 272 | 273 | // Meteor.release 274 | 275 | // =========================================================== 276 | // Publish and subscribe 277 | 278 | /// Subscribe to a record set. Returns a SubscriptionHandler that provides stop() and ready() methods. 279 | /// 280 | /// `name` 281 | /// Name of the subscription. Matches the name of the server's publish() call. 282 | /// 283 | /// `args` 284 | /// Arguments passed to publisher function on server. 285 | SubscriptionHandler subscribe(String name, 286 | {List args = const [], 287 | Function Function(dynamic error)? onStop, 288 | Function? onReady}) { 289 | var handler = 290 | connection.subscribe(name, args, onStop: onStop, onReady: onReady); 291 | return handler; 292 | } 293 | 294 | // =========================================================== 295 | // Methods 296 | 297 | /// Invoke a method passing an array of arguments. 298 | /// 299 | /// `name` Name of method to invoke 300 | /// 301 | /// `args` List of method arguments 302 | Future call(String name, {List args = const []}) async { 303 | try { 304 | return await connection.call(name, args); 305 | } catch (e) { 306 | if (e is Map) { 307 | throw MeteorError.parse(e); 308 | } 309 | rethrow; 310 | } 311 | } 312 | 313 | /// Invoke a method passing an array of arguments. 314 | /// 315 | /// `name` Name of method to invoke 316 | /// 317 | /// `args` List of method arguments 318 | Future apply(String name, List args) async { 319 | try { 320 | return await connection.apply(name, args); 321 | } catch (e) { 322 | if (e is Map) { 323 | throw MeteorError.parse(e); 324 | } 325 | rethrow; 326 | } 327 | } 328 | 329 | // =========================================================== 330 | // Server Connections 331 | 332 | /// Get the current connection status. 333 | Stream status() { 334 | return _statusStream; 335 | } 336 | 337 | /// Force an immediate reconnection attempt if the client is not connected to the server. 338 | /// This method does nothing if the client is already connected. 339 | void reconnect() { 340 | connection.reconnect(); 341 | } 342 | 343 | /// Disconnect the client from the server. 344 | void disconnect() { 345 | connection.disconnect(); 346 | } 347 | 348 | // =========================================================== 349 | // Accounts 350 | 351 | /// Get the current user record, or null if no user is logged in. A reactive data source. 352 | Stream?> user() { 353 | return _userStream; 354 | } 355 | 356 | Map? userCurrentValue() { 357 | return _userSubject.valueOrNull; 358 | } 359 | 360 | /// Get the current user id, or null if no user is logged in. A reactive data source. 361 | Stream userId() { 362 | return _userIdStream; 363 | } 364 | 365 | String? userIdCurrentValue() { 366 | return _userIdSubject.valueOrNull; 367 | } 368 | 369 | /// A Map containing user documents. 370 | Stream> get users => _collectionsStreams['users']!; 371 | 372 | /// Current log-in status of login methods (such as Meteor.loginWithPassword, Meteor.loginWithFacebook, or Accounts.createUser). 373 | /// A reactive data source. 374 | Stream logInStatus() { 375 | return _logInStatusStream; 376 | } 377 | 378 | /// True if a login method (such as Meteor.loginWithPassword, Meteor.loginWithFacebook, or Accounts.createUser) is currently in progress. 379 | /// A reactive data source. 380 | Stream loggingIn() { 381 | return _loggingInStream; 382 | } 383 | 384 | /// Log the user out. 385 | Future logout() { 386 | _logInStatus = UserLogInStatus.loggingOut; 387 | var completer = Completer(); 388 | call('logout').then((result) { 389 | _userId = null; 390 | _token = null; 391 | _tokenExpires = null; 392 | _logInStatus = UserLogInStatus.loggedOut; 393 | _logInStatusSubject.add(_logInStatus); 394 | _userIdSubject.add(_userId); 395 | completer.complete(); 396 | }).catchError((error) { 397 | _userId = null; 398 | _token = null; 399 | _tokenExpires = null; 400 | _logInStatus = UserLogInStatus.loggedOut; 401 | _logInStatusSubject.add(_logInStatus); 402 | _userIdSubject.add(_userId); 403 | connection.disconnect(); 404 | Future.delayed(Duration(seconds: 2), () { 405 | connection.reconnect(); 406 | }); 407 | completer.completeError(error); 408 | }); 409 | return completer.future; 410 | } 411 | 412 | /// Log out other clients logged in as the current user, but does not log out the client that calls this function. 413 | Future logoutOtherClients() { 414 | var completer = Completer(); 415 | _logInStatus = UserLogInStatus.loggingIn; 416 | call('getNewToken').then((result) { 417 | _userId = result['id']; 418 | _token = result['token']; 419 | _tokenExpires = result['tokenExpires']; 420 | _logInStatus = UserLogInStatus.loggedIn; 421 | _logInStatusSubject.add(_logInStatus); 422 | _userIdSubject.add(_userId); 423 | call('removeOtherTokens').then((value) { 424 | completer.complete(MeteorClientLoginResult( 425 | userId: _userId!, 426 | token: _token!, 427 | tokenExpires: _tokenExpires!, 428 | )); 429 | }); 430 | }).catchError((error) { 431 | _logInStatus = UserLogInStatus.loggedOut; 432 | completer.completeError(error); 433 | }); 434 | return completer.future; 435 | } 436 | 437 | /// Log the user in with a password. 438 | /// 439 | /// [user] 440 | /// Either a string interpreted as a username or an email; 441 | /// or an object with a single key: email, username or id. 442 | /// Username or email match in a case insensitive manner. 443 | /// 444 | /// [password] password 445 | /// 446 | /// [delayOnLoginErrorSecond] 447 | /// If login errors, delay for specificed second before throw an error. 448 | /// The user's password. 449 | Future loginWithPassword( 450 | String user, String password, 451 | {int delayOnLoginErrorSecond = 0}) { 452 | return login( 453 | { 454 | 'user': !user.contains('@') ? {'username': user} : {'email': user}, 455 | 'password': { 456 | 'digest': sha256.convert(utf8.encode(password)).toString(), 457 | 'algorithm': 'sha-256' 458 | }, 459 | }, 460 | delayOnLoginErrorSecond: delayOnLoginErrorSecond, 461 | ); 462 | } 463 | 464 | Future loginWithToken({ 465 | required String token, 466 | DateTime? tokenExpires, 467 | }) { 468 | _token = token; 469 | if (tokenExpires == null) { 470 | _tokenExpires = DateTime.now().add(Duration(hours: 1)); 471 | } else { 472 | _tokenExpires = tokenExpires; 473 | } 474 | return _loginWithExistingToken(); 475 | } 476 | 477 | Future _loginWithExistingToken() { 478 | var completer = Completer(); 479 | print('MeteorClient[$hashCode] - Trying to login with existing token...'); 480 | if (_tokenExpires != null) { 481 | print( 482 | 'MeteorClient[$hashCode] - Token expires ${_tokenExpires!.toString()}'); 483 | print('MeteorClient[$hashCode] - Current time is ${DateTime.now()}'); 484 | print( 485 | 'MeteorClient[$hashCode] - Token expires is after now ${_tokenExpires!.isAfter(DateTime.now())}', 486 | ); 487 | } 488 | 489 | if (_token != null && 490 | _tokenExpires != null && 491 | _tokenExpires!.isAfter(DateTime.now())) { 492 | return login({'resume': _token}); 493 | } else { 494 | completer.complete(null); 495 | } 496 | 497 | return completer.future; 498 | } 499 | 500 | Future login( 501 | Map loginData, { 502 | int delayOnLoginErrorSecond = 0, 503 | }) { 504 | var completer = Completer(); 505 | _logInStatus = UserLogInStatus.loggingIn; 506 | _logInStatusSubject.add(_logInStatus); 507 | 508 | call('login', args: [ 509 | loginData, 510 | ]).then((result) { 511 | _userId = result['id']; 512 | _token = result['token']; 513 | _tokenExpires = result['tokenExpires']; 514 | _logInStatus = UserLogInStatus.loggedIn; 515 | _logInStatusSubject.add(_logInStatus); 516 | _userIdSubject.add(_userId); 517 | completer.complete(MeteorClientLoginResult( 518 | userId: _userId!, 519 | token: _token!, 520 | tokenExpires: _tokenExpires!, 521 | )); 522 | }).catchError((error) { 523 | Future.delayed(Duration(seconds: delayOnLoginErrorSecond), () { 524 | _userId = null; 525 | _token = null; 526 | _tokenExpires = null; 527 | _logInStatus = UserLogInStatus.loggedOut; 528 | _logInStatusSubject.add(_logInStatus); 529 | _userIdSubject.add(_userId); 530 | completer.completeError(error); 531 | }); 532 | }); 533 | return completer.future; 534 | } 535 | 536 | // =========================================================== 537 | // Passwords 538 | 539 | /// Change the current user's password. Must be logged in. 540 | Future changePassword(String oldPassword, String newPassword) { 541 | return call('changePassword', args: [oldPassword, newPassword]); 542 | } 543 | 544 | /// Request a forgot password email. 545 | /// 546 | /// [email] 547 | /// The email address to send a password reset link. 548 | Future forgotPassword(String email) { 549 | return call('forgotPassword', args: [ 550 | {'email': email} 551 | ]); 552 | } 553 | 554 | /// Reset the password for a user using a token received in email. Logs the user in afterwards. 555 | /// 556 | /// [token] 557 | /// The token retrieved from the reset password URL. 558 | /// 559 | /// [newPassword] 560 | /// A new password for the user. This is not sent in plain text over the wire. 561 | Future resetPassword(String token, String newPassword) { 562 | return call('resetPassword', args: [token, newPassword]); 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /lib/src/ddp_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:web_socket_channel/web_socket_channel.dart'; 3 | import 'dart:async'; 4 | import 'dart:math'; 5 | 6 | enum DdpConnectionStatusValues { 7 | connected, 8 | connecting, 9 | failed, 10 | waiting, 11 | offline 12 | } 13 | 14 | class DdpConnectionStatus { 15 | bool connected; 16 | DdpConnectionStatusValues status; 17 | int retryCount; 18 | Duration retryTime; 19 | String? reason; 20 | 21 | DdpConnectionStatus({ 22 | required this.connected, 23 | required this.status, 24 | required this.retryCount, 25 | required this.retryTime, 26 | required this.reason, 27 | }); 28 | 29 | @override 30 | String toString() { 31 | return 'connected: $connected, status: $status, retryCount: $retryCount, retryTime: $retryTime, reason: $reason'; 32 | } 33 | } 34 | 35 | class SubscriptionHandler { 36 | final DdpClient ddpClient; 37 | final String subId; 38 | final StreamController _readyStreamController = StreamController(); 39 | late Stream _readyStream; 40 | final String subName; 41 | final List args; 42 | SubscriptionHandler(this.ddpClient, this.subId, this.subName, this.args) { 43 | _readyStream = _readyStreamController.stream.asBroadcastStream(); 44 | _readyStreamController.sink.add(false); 45 | } 46 | Stream ready() { 47 | return _readyStream; 48 | } 49 | 50 | void stop() { 51 | if (ddpClient._connectionStatus.connected) { 52 | ddpClient._sendMsgUnsub(subId); 53 | } 54 | } 55 | } 56 | 57 | class SubscriptionCallback { 58 | Function Function(dynamic error)? onStop; 59 | Function? onReady; 60 | SubscriptionCallback({ 61 | required this.onStop, 62 | required this.onReady, 63 | }); 64 | } 65 | 66 | class OnReconnectionCallback { 67 | DdpClient ddpClient; 68 | String id; 69 | Function callback; 70 | OnReconnectionCallback({ 71 | required this.ddpClient, 72 | required this.id, 73 | required this.callback, 74 | }); 75 | 76 | void stop() { 77 | ddpClient._onReconnectCallbacks.remove(id); 78 | } 79 | } 80 | 81 | class DdpClient { 82 | final int PING_SEC_INTERVAL = 20; 83 | final int PONG_WITHIN_SEC = 5; 84 | final Random _random = Random.secure(); 85 | 86 | final StreamController _statusStreamController = 87 | StreamController(); 88 | StreamController dataStreamController = StreamController(); 89 | late DdpConnectionStatus _connectionStatus; 90 | String url; 91 | String userAgent; 92 | WebSocketChannel? _socket; 93 | int maxRetryCount; 94 | final Map _onReconnectCallbacks = {}; 95 | String? serverId; 96 | String? sessionId; 97 | int _currentMethodId = 0; 98 | bool _flagToBeResetAtPongMsg = false; 99 | Timer? _pingPeriodicTimer; 100 | final Map> _methodCompleters = {}; 101 | final Map _subscriptions = {}; 102 | final Map _subscriptionHandlers = {}; 103 | bool _isTryToReconnect = true; 104 | Timer? _scheduleReconnectTimer; 105 | 106 | final bool debug; 107 | 108 | DdpClient({ 109 | required this.url, 110 | this.maxRetryCount = 20, 111 | this.debug = false, 112 | required this.userAgent, 113 | }) { 114 | _connectionStatus = DdpConnectionStatus( 115 | connected: false, 116 | status: DdpConnectionStatusValues.waiting, 117 | retryCount: 0, 118 | retryTime: Duration(seconds: 0), 119 | reason: null, 120 | ); 121 | _statusStreamController.sink.add(_connectionStatus); 122 | _connect(); 123 | } 124 | 125 | void printDebug(String str) { 126 | if (debug) { 127 | print('DDP[${_socket.hashCode}] - ${DateTime.now()}'); 128 | print('DDP[${_socket.hashCode}] - $str'); 129 | } 130 | } 131 | 132 | /// Register a function to call as the first step of reconnecting. 133 | /// This function can call methods which will be executed before any other outstanding methods. 134 | /// For example, this can be used to re-establish the appropriate authentication context on the connection. 135 | /// callback: 136 | /// The function to call. It will be called with a single argument, the connection object that is reconnecting. 137 | void onReconnect( 138 | void Function(OnReconnectionCallback reconnection) callback) { 139 | var id = _generateUID(16); 140 | var onReconnectCallback = 141 | OnReconnectionCallback(ddpClient: this, id: id, callback: callback); 142 | _onReconnectCallbacks[id] = onReconnectCallback; 143 | } 144 | 145 | String _generateUID(int numOfByte) { 146 | var values = List.generate(numOfByte, (i) => _random.nextInt(256)); 147 | return base64Url.encode(values); 148 | } 149 | 150 | SubscriptionHandler subscribe( 151 | String name, 152 | List params, { 153 | Function Function(dynamic error)? onStop, 154 | Function? onReady, 155 | }) { 156 | var id = '$name-${_generateUID(16)}'; 157 | _subscriptions[id] = SubscriptionCallback(onStop: onStop, onReady: onReady); 158 | params = DdpClient.escapeSpecialFieldValues(params); 159 | var handler = SubscriptionHandler(this, id, name, params); 160 | _subscriptionHandlers[id] = handler; 161 | _sendMsgSub(id, name, params); 162 | return handler; 163 | } 164 | 165 | Future call(String method, List params) { 166 | return apply(method, params); 167 | } 168 | 169 | Future apply(String method, List params) { 170 | var methodCompleter = Completer(); 171 | var newId = _currentMethodId.toString(); 172 | params = DdpClient.escapeSpecialFieldValues(params); 173 | _sendMsgMethod(method, params, newId); 174 | _currentMethodId++; 175 | _methodCompleters[newId] = methodCompleter; 176 | return methodCompleter.future; 177 | } 178 | 179 | Stream status() { 180 | return _statusStreamController.stream; 181 | } 182 | 183 | void reconnect() { 184 | printDebug('Reconnect: the connection status is ... $_connectionStatus'); 185 | if (_connectionStatus.status != DdpConnectionStatusValues.connected && 186 | _connectionStatus.status != DdpConnectionStatusValues.connecting) { 187 | if (_scheduleReconnectTimer != null) { 188 | if (_scheduleReconnectTimer!.isActive) { 189 | _scheduleReconnectTimer!.cancel(); 190 | _scheduleReconnectTimer = null; 191 | } 192 | } 193 | _connect(); 194 | } 195 | } 196 | 197 | void disconnect() { 198 | printDebug('Begin of disconnect()'); 199 | _isTryToReconnect = false; 200 | if (_socket != null) { 201 | _socket!.sink.close().then((value) { 202 | _socket = null; 203 | }).catchError((err) { 204 | printDebug(err); 205 | _socket = null; 206 | }); 207 | } 208 | // Cancel ping-pong timer 209 | if (_pingPeriodicTimer != null) { 210 | _pingPeriodicTimer!.cancel(); 211 | _pingPeriodicTimer = null; 212 | } 213 | 214 | // Reset ping-pong flag 215 | _flagToBeResetAtPongMsg = false; 216 | 217 | serverId = null; 218 | sessionId = null; 219 | _connectionStatus.connected = false; 220 | _connectionStatus.status = DdpConnectionStatusValues.offline; 221 | _connectionStatus.retryCount = 0; 222 | _connectionStatus.reason = null; 223 | _statusStreamController.sink.add(_connectionStatus); 224 | printDebug('End of disconnect()'); 225 | } 226 | 227 | void _connect() async { 228 | if (_connectionStatus.status != DdpConnectionStatusValues.connected && 229 | _connectionStatus.status != DdpConnectionStatusValues.connecting) { 230 | _isTryToReconnect = true; 231 | _connectionStatus.status = DdpConnectionStatusValues.connecting; 232 | _connectionStatus.reason = null; 233 | _statusStreamController.sink.add(_connectionStatus); 234 | try { 235 | _socket = WebSocketChannel.connect(Uri.parse(url)); 236 | _connectionStatus.retryCount = 0; 237 | _connectionStatus.retryTime = Duration(seconds: 1); 238 | _socket!.stream.listen( 239 | _onData, 240 | onDone: _onDone, 241 | onError: _onError, 242 | cancelOnError: true, 243 | ); 244 | _sendMsgConnect(); 245 | } catch (err) { 246 | print(err); 247 | _connectionStatus.status = DdpConnectionStatusValues.failed; 248 | _connectionStatus.reason = err.toString(); 249 | _statusStreamController.sink.add(_connectionStatus); 250 | _socket = null; 251 | printDebug( 252 | 'Schedule to reconnect due to websocket exception while trying to connect to the server!', 253 | ); 254 | _scheduleReconnect(); 255 | } 256 | } 257 | } 258 | 259 | void _scheduleReconnect() { 260 | if (_connectionStatus.status == DdpConnectionStatusValues.offline || 261 | _connectionStatus.status == DdpConnectionStatusValues.failed) { 262 | _connectionStatus.retryCount++; 263 | if (_connectionStatus.retryCount <= maxRetryCount) { 264 | _connectionStatus.connected = false; 265 | _connectionStatus.status = DdpConnectionStatusValues.waiting; 266 | _connectionStatus.retryTime = 267 | Duration(seconds: min(5 * (_connectionStatus.retryCount - 1), 30)); 268 | _connectionStatus.reason = null; 269 | _statusStreamController.sink.add(_connectionStatus); 270 | printDebug('Retry to connect in ${_connectionStatus.retryTime}'); 271 | 272 | if (_scheduleReconnectTimer != null) { 273 | if (_scheduleReconnectTimer!.isActive) { 274 | _scheduleReconnectTimer!.cancel(); 275 | _scheduleReconnectTimer = null; 276 | } 277 | } 278 | _scheduleReconnectTimer = Timer(_connectionStatus.retryTime, () { 279 | printDebug('Retry to connect count: ${_connectionStatus.retryCount}'); 280 | _connect(); 281 | }); 282 | } else { 283 | _connectionStatus.connected = false; 284 | _connectionStatus.status = DdpConnectionStatusValues.failed; 285 | _connectionStatus.reason = 'DDP. Reach max retry attempt'; 286 | _statusStreamController.sink.add(_connectionStatus); 287 | } 288 | } 289 | } 290 | 291 | void _sendMsgConnect() { 292 | if (_socket != null) { 293 | var data = { 294 | 'msg': 'connect', 295 | 'version': '1', 296 | 'support': ['1', 'pre1', 'pre2'], 297 | }; 298 | if (sessionId != null) { 299 | data['session'] = sessionId!; 300 | } 301 | var msg = json.encode(data); 302 | printDebug('Send: $msg'); 303 | _socket!.sink.add(msg); 304 | 305 | // Resend all subscriptions 306 | _subscriptionHandlers.forEach((id, handler) { 307 | _sendMsgSub(id, handler.subName, handler.args); 308 | }); 309 | } 310 | } 311 | 312 | void _sendMsgPing() { 313 | if (_socket != null) { 314 | var msg = json.encode({'msg': 'ping'}); 315 | printDebug('Send: $msg'); 316 | _socket!.sink.add(msg); 317 | var sentTime = DateTime.now(); 318 | _flagToBeResetAtPongMsg = true; 319 | Future.delayed(Duration(seconds: PONG_WITHIN_SEC), () { 320 | if (_flagToBeResetAtPongMsg == true) { 321 | printDebug(''); 322 | printDebug('Disconnect due to not receiving PONG'); 323 | printDebug('The latest PING was sent since $sentTime'); 324 | printDebug('The current time is ${DateTime.now()}'); 325 | printDebug( 326 | 'Time diff since the PING was sent is ${DateTime.now().difference(sentTime)}', 327 | ); 328 | disconnect(); 329 | } 330 | }); 331 | } 332 | } 333 | 334 | void _sendMsgPong() { 335 | if (_socket != null) { 336 | var msg = json.encode({'msg': 'pong'}); 337 | printDebug('Send: $msg'); 338 | _socket!.sink.add(msg); 339 | } 340 | } 341 | 342 | void _sendMsgSub(String id, String name, List params) { 343 | if (_socket != null) { 344 | var data = { 345 | 'msg': 'sub', 346 | 'name': name, 347 | 'params': params, 348 | 'id': id, 349 | }; 350 | var msg = json.encode(data); 351 | printDebug('Send: $msg'); 352 | _socket!.sink.add(msg); 353 | } 354 | } 355 | 356 | void _sendMsgUnsub(String id) { 357 | if (_socket != null) { 358 | var data = { 359 | 'msg': 'unsub', 360 | 'id': id, 361 | }; 362 | var msg = json.encode(data); 363 | printDebug('Send: $msg'); 364 | _socket!.sink.add(msg); 365 | } 366 | } 367 | 368 | void _sendMsgMethod(String method, List params, String id, 369 | {Map? randomSeed}) { 370 | if (_socket != null) { 371 | var data = { 372 | 'msg': 'method', 373 | 'method': method, 374 | 'params': params, 375 | 'id': id, 376 | }; 377 | if (randomSeed != null) { 378 | data['randomSeed'] = randomSeed; 379 | } 380 | var msg = json.encode(data); 381 | printDebug('Send: $msg'); 382 | _socket!.sink.add(msg); 383 | } 384 | } 385 | 386 | void _onData(dynamic data) { 387 | printDebug('Received: $data'); 388 | var dataMap = json.decode(data) ?? {}; 389 | var msg = dataMap['msg']; 390 | if (_connectionStatus.status == DdpConnectionStatusValues.connecting) { 391 | if (dataMap['server_id'] != null) { 392 | serverId = dataMap['server_id']; 393 | if (debug) { 394 | print('DDP[${_socket.hashCode}] - Server ID: $serverId'); 395 | } 396 | } else if (msg == 'connected') { 397 | for (var reconnectCallback in _onReconnectCallbacks.values) { 398 | reconnectCallback.callback(reconnectCallback); 399 | } 400 | 401 | _connectionStatus.connected = true; 402 | _connectionStatus.status = DdpConnectionStatusValues.connected; 403 | _connectionStatus.reason = null; 404 | _statusStreamController.sink.add(_connectionStatus); 405 | sessionId = dataMap['session']; 406 | 407 | // Cancel ping-pong timer 408 | if (_pingPeriodicTimer != null) { 409 | _pingPeriodicTimer!.cancel(); 410 | _pingPeriodicTimer = null; 411 | } 412 | 413 | _pingPeriodicTimer = 414 | Timer.periodic(Duration(seconds: PING_SEC_INTERVAL), (timer) { 415 | _sendMsgPing(); 416 | }); 417 | } else if (msg == 'failed') { 418 | serverId = null; 419 | sessionId = null; 420 | _connectionStatus.connected = false; 421 | _connectionStatus.status = DdpConnectionStatusValues.failed; 422 | _connectionStatus.reason = 423 | 'Failed connect to server. Protocol version ${dataMap['version']} is suggested!'; 424 | _statusStreamController.sink.add(_connectionStatus); 425 | } 426 | } else if (_connectionStatus.status == 427 | DdpConnectionStatusValues.connected) { 428 | if (msg == 'ping') { 429 | _sendMsgPong(); 430 | } else if (msg == 'pong') { 431 | _flagToBeResetAtPongMsg = false; 432 | } else if (msg == 'nosub') { 433 | if (dataMap['id'] != null) { 434 | String id = dataMap['id']; 435 | var sub = _subscriptions[id]; 436 | if (sub != null && sub.onStop != null) { 437 | sub.onStop!(dataMap['error']); 438 | _subscriptions.remove(id); 439 | sub = null; 440 | } else if (sub == null) { 441 | printDebug('Unknown "nosub" error!'); 442 | } 443 | var handler = _subscriptionHandlers[id]; 444 | if (handler != null) { 445 | _subscriptionHandlers.remove(id); 446 | handler = null; 447 | } 448 | } 449 | } else if (msg == 'added') { 450 | dataStreamController.sink.add(dataMap); 451 | } else if (msg == 'changed') { 452 | dataStreamController.sink.add(dataMap); 453 | } else if (msg == 'removed') { 454 | dataStreamController.sink.add(dataMap); 455 | } else if (msg == 'ready') { 456 | // subs: array of strings (ids passed to 'sub' which have sent their initial batch of data) 457 | List? subs = dataMap['subs']; 458 | if (subs != null) { 459 | for (var id in subs) { 460 | var sub = _subscriptions[id]; 461 | if (sub != null && sub.onReady != null) { 462 | sub.onReady!(); 463 | } 464 | var handler = _subscriptionHandlers[id]; 465 | if (handler != null) { 466 | handler._readyStreamController.sink.add(true); 467 | } 468 | } 469 | } 470 | } else if (msg == 'addedBefore') { 471 | } else if (msg == 'movedBefore') { 472 | } else if (msg == 'result') { 473 | if (dataMap['id'] != null) { 474 | String id = dataMap['id']; 475 | var completer = _methodCompleters[id]; 476 | if (completer != null) { 477 | if (dataMap['error'] != null) { 478 | completer.completeError(dataMap['error']); 479 | } else { 480 | DdpClient.formatSpecialFieldValues(dataMap); 481 | var result = dataMap['result']; 482 | completer.complete(result); 483 | } 484 | _methodCompleters.remove(id); 485 | } else { 486 | printDebug('No method completer found!'); 487 | } 488 | } 489 | } else if (msg == 'updated') { 490 | List methodIds = dataMap['methods']; 491 | printDebug(methodIds.toString()); 492 | } 493 | } 494 | } 495 | 496 | /// Escape a special value before sending it out to Meteor server 497 | /// ex. 498 | /// createdAt: DateTime Instance 2020-08-30 23:15:57.471 499 | /// become 500 | /// createdAt: {$date: 1598804210504} 501 | static dynamic escapeSpecialFieldValues(dynamic params) { 502 | if (params is DateTime) { 503 | return { 504 | '\$date': params.millisecondsSinceEpoch, 505 | }; 506 | } else if (params is List) { 507 | return params.map((param) => escapeSpecialFieldValues(param)).toList(); 508 | } else if (params is Map) { 509 | var newMap = {}; 510 | params.forEach((key, value) { 511 | newMap[key] = escapeSpecialFieldValues(value); 512 | }); 513 | return newMap; 514 | } 515 | return params; 516 | } 517 | 518 | /// Format a special value 519 | /// ex. 520 | /// createdAt: {$date: 1598804210504} 521 | /// become 522 | /// createdAt: DateTime Instance 2020-08-30 23:15:57.471 523 | static void formatSpecialFieldValues( 524 | dynamic object, { 525 | dynamic parent, 526 | dynamic field, 527 | }) { 528 | if (object is Map) { 529 | object.forEach((k, v) { 530 | if (v is Map || v is List) { 531 | DdpClient.formatSpecialFieldValues(v, parent: object, field: k); 532 | } else if (k == '\$date') { 533 | if (parent != null && field != null) { 534 | parent[field] = DateTime.fromMillisecondsSinceEpoch(v); 535 | return parent[field]; 536 | } 537 | } 538 | }); 539 | } else if (object is List) { 540 | object.asMap().forEach((idx, subObject) { 541 | formatSpecialFieldValues(subObject, parent: object, field: idx); 542 | }); 543 | } 544 | } 545 | 546 | void _onDone() { 547 | _socket = null; 548 | if (_isTryToReconnect) { 549 | printDebug( 550 | 'Disconnect the socket due to "onDone" event on the websocket!', 551 | ); 552 | disconnect(); 553 | printDebug( 554 | 'ScheduleReconnect due to "onDone" event on the websocket!', 555 | ); 556 | _scheduleReconnect(); 557 | } else { 558 | disconnect(); 559 | } 560 | } 561 | 562 | void _onError(dynamic error) { 563 | _socket = null; 564 | if (_isTryToReconnect) { 565 | printDebug('Disconnect due to "onError" event on the websocket!'); 566 | disconnect(); 567 | printDebug('ScheduleReconnect due to "onError" event on the websocket!'); 568 | _scheduleReconnect(); 569 | } else { 570 | printDebug('Disconnect due to "onError" event on the websocket!'); 571 | disconnect(); 572 | } 573 | } 574 | } 575 | -------------------------------------------------------------------------------- /test/dart_meteor_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:rxdart/rxdart.dart'; 4 | 5 | import 'package:dart_meteor/dart_meteor.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | var url = 'ws://127.0.0.1:3000'; 9 | 10 | void main() { 11 | group('Environment', () { 12 | var meteor = MeteorClient.connect( 13 | url: url, 14 | debug: true, 15 | ); 16 | 17 | test('meteor.isClient', () { 18 | expect(meteor.isClient(), isTrue); 19 | }); 20 | 21 | test('meteor.isServer', () { 22 | expect(meteor.isServer(), isFalse); 23 | }); 24 | 25 | test('meteor.isCordova', () { 26 | expect(meteor.isCordova(), isFalse); 27 | }); 28 | }); 29 | 30 | group('MeteorError', () { 31 | var meteor = MeteorClient.connect( 32 | url: url, 33 | debug: true, 34 | ); 35 | 36 | setUp(() async { 37 | meteor.reconnect(); 38 | await Future.delayed(Duration(seconds: 2)); 39 | }); 40 | 41 | tearDown(() { 42 | meteor.disconnect(); 43 | }); 44 | 45 | test('It should throw error as integer', () async { 46 | try { 47 | await meteor.call('methodThatThrowErrorAsInt'); 48 | } on MeteorError catch (e) { 49 | expect(e.error, 500); 50 | expect(e.reason, 'This is an error'); 51 | } 52 | }); 53 | 54 | test('It should throw error as string', () async { 55 | try { 56 | await meteor.call('methodThatThrowErrorAsString'); 57 | } on MeteorError catch (e) { 58 | expect(e.error, 'error'); 59 | expect(e.reason, 'This is an error'); 60 | } 61 | }); 62 | }); 63 | 64 | group('Method', () { 65 | var meteor = MeteorClient.connect( 66 | url: url, 67 | debug: true, 68 | ); 69 | 70 | setUp(() async { 71 | meteor.reconnect(); 72 | await Future.delayed(Duration(seconds: 2)); 73 | }); 74 | 75 | tearDown(() { 76 | meteor.disconnect(); 77 | }); 78 | 79 | test('Method that return a Number', () async { 80 | var result = await meteor.call('methodThatReturnNumber'); 81 | expect(result, isA()); 82 | }); 83 | 84 | test('Method that return a String', () async { 85 | var result = await meteor.call('methodThatReturnString'); 86 | expect(result, isA()); 87 | }); 88 | 89 | test('Method that return a DateTime', () async { 90 | var result = await meteor.call('methodThatReturnDateTime'); 91 | expect(result, isA()); 92 | }); 93 | 94 | test('Method that return a Object', () async { 95 | var result = await meteor.call('methodThatReturnObject'); 96 | expect(result, isA()); 97 | expect(result['createdAt'], isA()); 98 | }); 99 | 100 | test('Method that return a nested Date Object', () async { 101 | var result = await meteor.call('methodThatReturnNestedDateObject'); 102 | // From the simple-meteor-chat test backend project... 103 | // return { 104 | // a: { 105 | // createdAt: new Date(), 106 | // b: { 107 | // c: { 108 | // createdAt: new Date(), 109 | // } 110 | // } 111 | // }, 112 | // createdAt: new Date(), 113 | // }; 114 | expect(result, isA()); 115 | expect(result['createdAt'], isA()); 116 | expect(result['a']['createdAt'], isA()); 117 | expect(result['a']['b']['c']['createdAt'], isA()); 118 | }); 119 | 120 | test('Method that return an array of nested Date Objects', () async { 121 | var result = await meteor.call('methodThatReturnArrayOfNestedDateObject'); 122 | // From the simple-meteor-chat test backend project... 123 | // return [{ 124 | // a: { 125 | // createdAt: date1, 126 | // b: { 127 | // c: [{ 128 | // createdAt: date2, 129 | // }, { 130 | // createdAt: date3, 131 | // }, { 132 | // createdAt: date4, 133 | // }], 134 | // d: [date5, date6], 135 | // } 136 | // }, 137 | // createdAt: [date5, date6], 138 | // }, { 139 | // a: { 140 | // createdAt: date1, 141 | // b: { 142 | // c: [{ 143 | // createdAt: date2, 144 | // }, { 145 | // createdAt: date3, 146 | // }, { 147 | // createdAt: date4, 148 | // }], 149 | // d: [date5, date6], 150 | // } 151 | // }, 152 | // createdAt: [date5, date6], 153 | // }]; 154 | print(result); 155 | expect(result, isA()); 156 | expect(result[0]['createdAt'][0], isA()); 157 | expect(result[0]['createdAt'][1], isA()); 158 | expect(result[0]['a']['createdAt'], isA()); 159 | expect(result[0]['a']['b']['c'][0]['createdAt'], isA()); 160 | expect(result[0]['a']['b']['c'][1]['createdAt'], isA()); 161 | expect(result[0]['a']['b']['c'][2]['createdAt'], isA()); 162 | expect(result[0]['a']['b']['d'][0], isA()); 163 | expect(result[0]['a']['b']['d'][1], isA()); 164 | 165 | expect(result[1]['createdAt'][0], isA()); 166 | expect(result[1]['createdAt'][1], isA()); 167 | expect(result[1]['a']['createdAt'], isA()); 168 | expect(result[1]['a']['b']['c'][0]['createdAt'], isA()); 169 | expect(result[1]['a']['b']['c'][1]['createdAt'], isA()); 170 | expect(result[1]['a']['b']['c'][2]['createdAt'], isA()); 171 | expect(result[1]['a']['b']['d'][0], isA()); 172 | expect(result[1]['a']['b']['d'][1], isA()); 173 | }); 174 | 175 | test('Method that accept a datetime', () async { 176 | var result = await meteor 177 | .call('methodThatReturnTheNextDay', args: [DateTime.now()]); 178 | print(result); 179 | expect(result, isA()); 180 | expect(result['input'], isA()); 181 | expect(result['output'], isA()); 182 | }); 183 | 184 | test('Method that accept object of datetime', () async { 185 | var result = await meteor.call('methodThatAcceptObjectOfDate', args: [ 186 | { 187 | 'minDate': DateTime.now(), 188 | 'maxDate': DateTime.now(), 189 | } 190 | ]); 191 | print(result); 192 | expect(result, isA()); 193 | expect(result['minDate'], isA()); 194 | expect(result['maxDate'], isA()); 195 | }); 196 | }); 197 | 198 | group('Login and logout', () { 199 | var meteor = MeteorClient.connect( 200 | url: url, 201 | debug: true, 202 | ); 203 | 204 | setUp(() async { 205 | meteor.reconnect(); 206 | await Future.delayed(Duration(seconds: 2)); 207 | }); 208 | 209 | tearDown(() { 210 | meteor.disconnect(); 211 | }); 212 | 213 | test('meteor.loginWithPassword', () async { 214 | var result = await meteor.loginWithPassword('user1', 'password1'); 215 | print('MeteorClientLoginResult: $result'); 216 | print('UserID: ${meteor.userIdCurrentValue()}'); 217 | expect(meteor.userIdCurrentValue(), isNotNull); 218 | }); 219 | 220 | test('meteor.logout', () async { 221 | var result = await meteor.loginWithPassword('user1', 'password1'); 222 | print('MeteorClientLoginResult: $result'); 223 | print('UserID: ${meteor.userIdCurrentValue()}'); 224 | expect(meteor.userIdCurrentValue(), isNotNull); 225 | expect(meteor.userCurrentValue(), isNotNull); 226 | await meteor.logout(); 227 | expect(meteor.userIdCurrentValue(), isNull); 228 | expect(meteor.userCurrentValue(), isNull); 229 | }); 230 | 231 | test('meteor.logoutOtherClients', () async { 232 | var result1 = await meteor.loginWithPassword('user1', 'password1'); 233 | print('MeteorClientLoginResult: $result1'); 234 | print('UserID: ${meteor.userIdCurrentValue()}'); 235 | expect(meteor.userIdCurrentValue(), isNotNull); 236 | expect(meteor.userCurrentValue(), isNotNull); 237 | 238 | var result2 = await meteor.logoutOtherClients(); 239 | expect(result2, isNotNull); 240 | expect(meteor.userIdCurrentValue(), isNotNull); 241 | expect(meteor.userCurrentValue(), isNotNull); 242 | // Must be the same userId 243 | expect(result2.userId, result1.userId); 244 | // Must be a different token 245 | expect(result2.token, isNot(result1.token)); 246 | // From https://github.com/meteor/meteor/blob/dae7af832d08a8b19384ac19aa5a5a9b6b005e55/packages/accounts-base/accounts_server.js#L682 247 | // Be careful not to generate a new token that has a later 248 | // expiration than the curren token. Otherwise, a bad guy with a 249 | // stolen token could use this method to stop his stolen token from 250 | // ever expiring. 251 | expect(result2.tokenExpires.millisecondsSinceEpoch, 252 | lessThanOrEqualTo(result1.tokenExpires.millisecondsSinceEpoch)); 253 | }); 254 | }); 255 | 256 | group('Subscription', () { 257 | var meteor = MeteorClient.connect( 258 | url: url, 259 | debug: true, 260 | ); 261 | 262 | setUp(() async { 263 | meteor.reconnect(); 264 | await Future.delayed(Duration(seconds: 2)); 265 | }); 266 | 267 | tearDown(() { 268 | meteor.disconnect(); 269 | }); 270 | 271 | test('meteor.loggingIn', () async { 272 | var state = 0; 273 | var currentLoggingIn = true; 274 | meteor.loggingIn().listen((event) { 275 | print('Before: loggingIn state: $state, loggingIn: $event'); 276 | currentLoggingIn = event; 277 | if (state == 0 && event == false) { 278 | state++; 279 | } else if (state == 1 && event == true) { 280 | state++; 281 | } else if (state == 2 && event == false) { 282 | state++; 283 | } 284 | print('After: loggingIn state: $state, loggingIn: $event'); 285 | }); 286 | await meteor.loginWithPassword('user1', 'password1'); 287 | expect(meteor.userId(), isNotNull); 288 | await Future.delayed(Duration(seconds: 5)); 289 | expect(state, 3); 290 | expect(currentLoggingIn, false); 291 | }); 292 | }); 293 | 294 | group('subscription', () { 295 | late MeteorClient meteor; 296 | 297 | setUp(() async { 298 | meteor = MeteorClient.connect( 299 | url: url, 300 | debug: true, 301 | ); 302 | await Future.delayed(Duration(seconds: 2)); 303 | }); 304 | 305 | tearDown(() { 306 | meteor.disconnect(); 307 | }); 308 | 309 | test('meteor.subscribe with onReady', () async { 310 | var completer = Completer(); 311 | expect(completer.future, completion(true)); 312 | meteor.subscribe( 313 | 'messages', 314 | args: [], 315 | onReady: () { 316 | print('onReady is called.'); 317 | completer.complete(true); 318 | }, 319 | ); 320 | await Future.delayed(Duration(seconds: 5)); 321 | if (!completer.isCompleted) { 322 | completer.complete(false); 323 | } 324 | }); 325 | 326 | test('SubscriptionHandler.ready()', () async { 327 | var completer = Completer(); 328 | expect(completer.future, completion(true)); 329 | var subHandler = meteor.subscribe( 330 | 'messages', 331 | args: [], 332 | ); 333 | var s = 0; 334 | subHandler.ready().listen((value) { 335 | if (s == 0) { 336 | expect(value, false); 337 | } else if (s == 1) { 338 | expect(value, true); 339 | if (value) { 340 | completer.complete(true); 341 | } 342 | } 343 | s++; 344 | }); 345 | await Future.delayed(Duration(seconds: 5)); 346 | if (!completer.isCompleted) { 347 | completer.complete(false); 348 | } 349 | }); 350 | 351 | test( 352 | 'collection(messages) stream should have values and "createdAt" should be instance of DateTime', 353 | () async { 354 | var completer = Completer(); 355 | expect(completer.future, completion(true)); 356 | await meteor.loginWithPassword('user1', 'password1'); 357 | await meteor.call('clearAllMessages'); 358 | meteor.subscribe( 359 | 'messages', 360 | ); 361 | meteor.collection('messages').listen((value) { 362 | print('collection messages listen:'); 363 | print(value); 364 | 365 | if (value.isNotEmpty) { 366 | if (value[value.keys.first]['createdAt'] is! DateTime) { 367 | if (!completer.isCompleted) { 368 | completer.complete(false); 369 | } 370 | } else { 371 | if (!completer.isCompleted) { 372 | completer.complete(true); 373 | } 374 | } 375 | } 376 | }); 377 | await meteor.call('sendMessage', args: ['message 1']); 378 | await meteor.call('sendMessage', args: ['message 2']); 379 | await meteor.call('sendMessage', args: ['message 3']); 380 | await Future.delayed(Duration(seconds: 10)); 381 | if (!completer.isCompleted) { 382 | completer.complete(false); 383 | } 384 | }); 385 | 386 | test('meteor should resume subscription after reconnected', () async { 387 | var completer = Completer(); 388 | expect(completer.future, completion(true)); 389 | await meteor.loginWithPassword('user1', 'password1'); 390 | await meteor.call('clearAllMessages'); 391 | meteor.subscribe( 392 | 'messages', 393 | ); 394 | meteor.collection('messages').listen((value) { 395 | var msgCnt = value.values.toList().length; 396 | print('resume subscription, message count: $msgCnt'); 397 | if (msgCnt == 2) { 398 | completer.complete(true); 399 | } 400 | }); 401 | await meteor.call('sendMessage', args: ['message 1']); 402 | await Future.delayed(Duration(seconds: 2)); 403 | meteor.disconnect(); 404 | await Future.delayed(Duration(seconds: 2)); 405 | meteor.reconnect(); 406 | await Future.delayed(Duration(seconds: 2)); 407 | await meteor.call('sendMessage', args: ['message 2']); 408 | await Future.delayed(Duration(seconds: 2)); 409 | if (!completer.isCompleted) { 410 | completer.complete(false); 411 | } 412 | }); 413 | }); 414 | 415 | group('Reactive with rxdart', () { 416 | late MeteorClient meteor; 417 | 418 | setUp(() async { 419 | meteor = MeteorClient.connect( 420 | url: url, 421 | debug: true, 422 | ); 423 | await Future.delayed(Duration(seconds: 2)); 424 | }); 425 | 426 | tearDown(() { 427 | meteor.disconnect(); 428 | }); 429 | 430 | test( 431 | 'reactive on subscription, expect assets contains one document if we do stop the fist subscription', 432 | () async { 433 | var completer = Completer(); 434 | expect(completer.future, completion(true)); 435 | await meteor.loginWithPassword('user1', 'password1'); 436 | var reactive = BehaviorSubject(); 437 | SubscriptionHandler? sub; 438 | reactive.add('user1'); 439 | reactive.listen((username) { 440 | if (sub != null) { 441 | sub!.stop(); 442 | } 443 | sub = meteor.subscribe('assets', args: [username], onReady: () async { 444 | await Future.delayed(Duration(seconds: 2)); 445 | var assets = meteor.collectionCurrentValue('assets'); 446 | if (username == 'user2' && assets!.length == 1) { 447 | assets.forEach((k, v) { 448 | if (v['owner'] == 'user2') { 449 | completer.complete(true); 450 | } 451 | }); 452 | } 453 | }); 454 | }); 455 | var bFirst = true; 456 | meteor.collection('assets').listen((value) { 457 | print('collection assets listen:'); 458 | print(value); 459 | 460 | if (bFirst) { 461 | reactive.add('user2'); 462 | bFirst = false; 463 | } 464 | }); 465 | await Future.delayed(Duration(seconds: 5)); 466 | if (!completer.isCompleted) { 467 | completer.complete(false); 468 | } 469 | }); 470 | 471 | test( 472 | 'reactive on subscription, expect assets contains two documents if we does not stop the fist subscription', 473 | () async { 474 | var completer = Completer(); 475 | expect(completer.future, completion(true)); 476 | await meteor.loginWithPassword('user1', 'password1'); 477 | var reactive = BehaviorSubject(); 478 | SubscriptionHandler sub; 479 | reactive.add('user1'); 480 | reactive.listen((username) { 481 | sub = meteor.subscribe('assets', args: [username], onReady: () async { 482 | await Future.delayed(Duration(seconds: 2)); 483 | var assets = meteor.collectionCurrentValue('assets'); 484 | if (username == 'user2' && assets!.length == 2) { 485 | assets.forEach((k, v) { 486 | if (v['owner'] == 'user2') { 487 | completer.complete(true); 488 | } 489 | }); 490 | } 491 | }); 492 | }); 493 | var bFirst = true; 494 | meteor.collection('assets').listen((value) { 495 | print('collection assets listen:'); 496 | print(value); 497 | 498 | if (bFirst) { 499 | reactive.add('user2'); 500 | bFirst = false; 501 | } 502 | }); 503 | await Future.delayed(Duration(seconds: 5)); 504 | if (!completer.isCompleted) { 505 | completer.complete(false); 506 | } 507 | }); 508 | }); 509 | 510 | group('escapeSpecialFieldValues function', () { 511 | test('escapeSpecialFieldValues 1', () { 512 | var a = [ 513 | 'a', 514 | 1, 515 | true, 516 | ['b', 2, false] 517 | ]; 518 | var b = DdpClient.escapeSpecialFieldValues(a); 519 | assert(DeepCollectionEquality().equals(a, b)); 520 | }); 521 | 522 | test('escapeSpecialFieldValues 2', () { 523 | var now = DateTime.now(); 524 | var a = [ 525 | 'a', 526 | 1, 527 | true, 528 | ['b', 2, false], 529 | now, 530 | ]; 531 | var b = DdpClient.escapeSpecialFieldValues(a); 532 | assert(DeepCollectionEquality().equals(b, [ 533 | 'a', 534 | 1, 535 | true, 536 | ['b', 2, false], 537 | {'\$date': now.millisecondsSinceEpoch}, 538 | ])); 539 | }); 540 | 541 | test('escapeSpecialFieldValues 3', () { 542 | var now = DateTime.now(); 543 | var a = [ 544 | { 545 | 'a': 'a', 546 | 'b': 1, 547 | 'c': true, 548 | 'd': ['b', 2, false], 549 | 'e': now, 550 | } 551 | ]; 552 | var b = DdpClient.escapeSpecialFieldValues(a); 553 | print(a); 554 | print(b); 555 | assert(DeepCollectionEquality().equals(b, [ 556 | { 557 | 'a': 'a', 558 | 'b': 1, 559 | 'c': true, 560 | 'd': ['b', 2, false], 561 | 'e': {'\$date': now.millisecondsSinceEpoch}, 562 | } 563 | ])); 564 | }); 565 | 566 | test('escapeSpecialFieldValues 4', () { 567 | var now = DateTime.now(); 568 | var a = [ 569 | { 570 | 'a': 'a', 571 | 'b': 1, 572 | 'c': true, 573 | 'd': ['b', 2, false], 574 | 'e': now, 575 | }, 576 | now, 577 | { 578 | 'a': 'a', 579 | 'b': 1, 580 | 'c': true, 581 | 'd': ['b', 2, now], 582 | 'e': now, 583 | }, 584 | ]; 585 | var b = DdpClient.escapeSpecialFieldValues(a); 586 | print(a); 587 | print(b); 588 | assert(DeepCollectionEquality().equals(b, [ 589 | { 590 | 'a': 'a', 591 | 'b': 1, 592 | 'c': true, 593 | 'd': ['b', 2, false], 594 | 'e': {'\$date': now.millisecondsSinceEpoch}, 595 | }, 596 | {'\$date': now.millisecondsSinceEpoch}, 597 | { 598 | 'a': 'a', 599 | 'b': 1, 600 | 'c': true, 601 | 'd': [ 602 | 'b', 603 | 2, 604 | {'\$date': now.millisecondsSinceEpoch} 605 | ], 606 | 'e': {'\$date': now.millisecondsSinceEpoch}, 607 | }, 608 | ])); 609 | }); 610 | 611 | test('escapeSpecialFieldValues 5', () { 612 | var now = DateTime.now(); 613 | var a = {'a': now}; 614 | var b = DdpClient.escapeSpecialFieldValues(a); 615 | print(a); 616 | print(b); 617 | assert(DeepCollectionEquality().equals( 618 | b, 619 | { 620 | 'a': {'\$date': now.millisecondsSinceEpoch}, 621 | }, 622 | )); 623 | }); 624 | 625 | test('escapeSpecialFieldValues 6', () { 626 | var now = DateTime.now(); 627 | var a = now; 628 | var b = DdpClient.escapeSpecialFieldValues(a); 629 | print(a); 630 | print(b); 631 | assert(DeepCollectionEquality().equals( 632 | b, 633 | {'\$date': now.millisecondsSinceEpoch}, 634 | )); 635 | }); 636 | 637 | test('escapeSpecialFieldValues 7', () { 638 | var a = 1; 639 | var b = DdpClient.escapeSpecialFieldValues(a); 640 | print(a); 641 | print(b); 642 | assert(DeepCollectionEquality().equals( 643 | b, 644 | a, 645 | )); 646 | }); 647 | }); 648 | } 649 | -------------------------------------------------------------------------------- /example/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 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 25 | remoteInfo = Runner; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXCopyFilesBuildPhase section */ 30 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 31 | isa = PBXCopyFilesBuildPhase; 32 | buildActionMask = 2147483647; 33 | dstPath = ""; 34 | dstSubfolderSpec = 10; 35 | files = ( 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 46 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 48 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 49 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 75 | ); 76 | path = RunnerTests; 77 | sourceTree = ""; 78 | }; 79 | 9740EEB11CF90186004384FC /* Flutter */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 83 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 84 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 85 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 86 | ); 87 | name = Flutter; 88 | sourceTree = ""; 89 | }; 90 | 97C146E51CF9000F007C117D = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9740EEB11CF90186004384FC /* Flutter */, 94 | 97C146F01CF9000F007C117D /* Runner */, 95 | 97C146EF1CF9000F007C117D /* Products */, 96 | 331C8082294A63A400263BE5 /* RunnerTests */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 97C146EF1CF9000F007C117D /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 97C146EE1CF9000F007C117D /* Runner.app */, 104 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 97C146F01CF9000F007C117D /* Runner */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 113 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 114 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 115 | 97C147021CF9000F007C117D /* Info.plist */, 116 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 117 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 118 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 119 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 120 | ); 121 | path = Runner; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 130 | buildPhases = ( 131 | 331C807D294A63A400263BE5 /* Sources */, 132 | 331C807F294A63A400263BE5 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 138 | ); 139 | name = RunnerTests; 140 | productName = RunnerTests; 141 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 142 | productType = "com.apple.product-type.bundle.unit-test"; 143 | }; 144 | 97C146ED1CF9000F007C117D /* Runner */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 147 | buildPhases = ( 148 | 9740EEB61CF901F6004384FC /* Run Script */, 149 | 97C146EA1CF9000F007C117D /* Sources */, 150 | 97C146EB1CF9000F007C117D /* Frameworks */, 151 | 97C146EC1CF9000F007C117D /* Resources */, 152 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 153 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Runner; 160 | productName = Runner; 161 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 97C146E61CF9000F007C117D /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | BuildIndependentTargetsInParallel = YES; 171 | LastUpgradeCheck = 1510; 172 | ORGANIZATIONNAME = ""; 173 | TargetAttributes = { 174 | 331C8080294A63A400263BE5 = { 175 | CreatedOnToolsVersion = 14.0; 176 | TestTargetID = 97C146ED1CF9000F007C117D; 177 | }; 178 | 97C146ED1CF9000F007C117D = { 179 | CreatedOnToolsVersion = 7.3.1; 180 | LastSwiftMigration = 1100; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 185 | compatibilityVersion = "Xcode 9.3"; 186 | developmentRegion = en; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | en, 190 | Base, 191 | ); 192 | mainGroup = 97C146E51CF9000F007C117D; 193 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | 97C146ED1CF9000F007C117D /* Runner */, 198 | 331C8080294A63A400263BE5 /* RunnerTests */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | 331C807F294A63A400263BE5 /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 217 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | alwaysOutOfDate = 1; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | inputPaths = ( 232 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 233 | ); 234 | name = "Thin Binary"; 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 240 | }; 241 | 9740EEB61CF901F6004384FC /* Run Script */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | alwaysOutOfDate = 1; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | inputPaths = ( 248 | ); 249 | name = "Run Script"; 250 | outputPaths = ( 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | shellPath = /bin/sh; 254 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 255 | }; 256 | /* End PBXShellScriptBuildPhase section */ 257 | 258 | /* Begin PBXSourcesBuildPhase section */ 259 | 331C807D294A63A400263BE5 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | 97C146EA1CF9000F007C117D /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 272 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin PBXTargetDependency section */ 279 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 280 | isa = PBXTargetDependency; 281 | target = 97C146ED1CF9000F007C117D /* Runner */; 282 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 283 | }; 284 | /* End PBXTargetDependency section */ 285 | 286 | /* Begin PBXVariantGroup section */ 287 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 288 | isa = PBXVariantGroup; 289 | children = ( 290 | 97C146FB1CF9000F007C117D /* Base */, 291 | ); 292 | name = Main.storyboard; 293 | sourceTree = ""; 294 | }; 295 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 296 | isa = PBXVariantGroup; 297 | children = ( 298 | 97C147001CF9000F007C117D /* Base */, 299 | ); 300 | name = LaunchScreen.storyboard; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXVariantGroup section */ 304 | 305 | /* Begin XCBuildConfiguration section */ 306 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 311 | CLANG_ANALYZER_NONNULL = YES; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 338 | ENABLE_NS_ASSERTIONS = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 341 | GCC_C_LANGUAGE_STANDARD = gnu99; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 350 | MTL_ENABLE_DEBUG_INFO = NO; 351 | SDKROOT = iphoneos; 352 | SUPPORTED_PLATFORMS = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | VALIDATE_PRODUCT = YES; 355 | }; 356 | name = Profile; 357 | }; 358 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 359 | isa = XCBuildConfiguration; 360 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CLANG_ENABLE_MODULES = YES; 364 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 365 | DEVELOPMENT_TEAM = 97M85WK68C; 366 | ENABLE_BITCODE = NO; 367 | INFOPLIST_FILE = Runner/Info.plist; 368 | LD_RUNPATH_SEARCH_PATHS = ( 369 | "$(inherited)", 370 | "@executable_path/Frameworks", 371 | ); 372 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dartMeteorExampleApp; 373 | PRODUCT_NAME = "$(TARGET_NAME)"; 374 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 375 | SWIFT_VERSION = 5.0; 376 | VERSIONING_SYSTEM = "apple-generic"; 377 | }; 378 | name = Profile; 379 | }; 380 | 331C8088294A63A400263BE5 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | BUNDLE_LOADER = "$(TEST_HOST)"; 384 | CODE_SIGN_STYLE = Automatic; 385 | CURRENT_PROJECT_VERSION = 1; 386 | GENERATE_INFOPLIST_FILE = YES; 387 | MARKETING_VERSION = 1.0; 388 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dartMeteorExampleApp.RunnerTests; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | SWIFT_VERSION = 5.0; 393 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 394 | }; 395 | name = Debug; 396 | }; 397 | 331C8089294A63A400263BE5 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | BUNDLE_LOADER = "$(TEST_HOST)"; 401 | CODE_SIGN_STYLE = Automatic; 402 | CURRENT_PROJECT_VERSION = 1; 403 | GENERATE_INFOPLIST_FILE = YES; 404 | MARKETING_VERSION = 1.0; 405 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dartMeteorExampleApp.RunnerTests; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_VERSION = 5.0; 408 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 409 | }; 410 | name = Release; 411 | }; 412 | 331C808A294A63A400263BE5 /* Profile */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | BUNDLE_LOADER = "$(TEST_HOST)"; 416 | CODE_SIGN_STYLE = Automatic; 417 | CURRENT_PROJECT_VERSION = 1; 418 | GENERATE_INFOPLIST_FILE = YES; 419 | MARKETING_VERSION = 1.0; 420 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dartMeteorExampleApp.RunnerTests; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | SWIFT_VERSION = 5.0; 423 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 424 | }; 425 | name = Profile; 426 | }; 427 | 97C147031CF9000F007C117D /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ALWAYS_SEARCH_USER_PATHS = NO; 431 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 432 | CLANG_ANALYZER_NONNULL = YES; 433 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 434 | CLANG_CXX_LIBRARY = "libc++"; 435 | CLANG_ENABLE_MODULES = YES; 436 | CLANG_ENABLE_OBJC_ARC = YES; 437 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 438 | CLANG_WARN_BOOL_CONVERSION = YES; 439 | CLANG_WARN_COMMA = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 442 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 443 | CLANG_WARN_EMPTY_BODY = YES; 444 | CLANG_WARN_ENUM_CONVERSION = YES; 445 | CLANG_WARN_INFINITE_RECURSION = YES; 446 | CLANG_WARN_INT_CONVERSION = YES; 447 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 449 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 451 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 452 | CLANG_WARN_STRICT_PROTOTYPES = YES; 453 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 454 | CLANG_WARN_UNREACHABLE_CODE = YES; 455 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 456 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 457 | COPY_PHASE_STRIP = NO; 458 | DEBUG_INFORMATION_FORMAT = dwarf; 459 | ENABLE_STRICT_OBJC_MSGSEND = YES; 460 | ENABLE_TESTABILITY = YES; 461 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 462 | GCC_C_LANGUAGE_STANDARD = gnu99; 463 | GCC_DYNAMIC_NO_PIC = NO; 464 | GCC_NO_COMMON_BLOCKS = YES; 465 | GCC_OPTIMIZATION_LEVEL = 0; 466 | GCC_PREPROCESSOR_DEFINITIONS = ( 467 | "DEBUG=1", 468 | "$(inherited)", 469 | ); 470 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 471 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 472 | GCC_WARN_UNDECLARED_SELECTOR = YES; 473 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 474 | GCC_WARN_UNUSED_FUNCTION = YES; 475 | GCC_WARN_UNUSED_VARIABLE = YES; 476 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 477 | MTL_ENABLE_DEBUG_INFO = YES; 478 | ONLY_ACTIVE_ARCH = YES; 479 | SDKROOT = iphoneos; 480 | TARGETED_DEVICE_FAMILY = "1,2"; 481 | }; 482 | name = Debug; 483 | }; 484 | 97C147041CF9000F007C117D /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | ALWAYS_SEARCH_USER_PATHS = NO; 488 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 489 | CLANG_ANALYZER_NONNULL = YES; 490 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 491 | CLANG_CXX_LIBRARY = "libc++"; 492 | CLANG_ENABLE_MODULES = YES; 493 | CLANG_ENABLE_OBJC_ARC = YES; 494 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 495 | CLANG_WARN_BOOL_CONVERSION = YES; 496 | CLANG_WARN_COMMA = YES; 497 | CLANG_WARN_CONSTANT_CONVERSION = YES; 498 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 499 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 500 | CLANG_WARN_EMPTY_BODY = YES; 501 | CLANG_WARN_ENUM_CONVERSION = YES; 502 | CLANG_WARN_INFINITE_RECURSION = YES; 503 | CLANG_WARN_INT_CONVERSION = YES; 504 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 506 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 507 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 508 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 509 | CLANG_WARN_STRICT_PROTOTYPES = YES; 510 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 511 | CLANG_WARN_UNREACHABLE_CODE = YES; 512 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 513 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 514 | COPY_PHASE_STRIP = NO; 515 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 516 | ENABLE_NS_ASSERTIONS = NO; 517 | ENABLE_STRICT_OBJC_MSGSEND = YES; 518 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 519 | GCC_C_LANGUAGE_STANDARD = gnu99; 520 | GCC_NO_COMMON_BLOCKS = YES; 521 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 522 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 523 | GCC_WARN_UNDECLARED_SELECTOR = YES; 524 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 525 | GCC_WARN_UNUSED_FUNCTION = YES; 526 | GCC_WARN_UNUSED_VARIABLE = YES; 527 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 528 | MTL_ENABLE_DEBUG_INFO = NO; 529 | SDKROOT = iphoneos; 530 | SUPPORTED_PLATFORMS = iphoneos; 531 | SWIFT_COMPILATION_MODE = wholemodule; 532 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 533 | TARGETED_DEVICE_FAMILY = "1,2"; 534 | VALIDATE_PRODUCT = YES; 535 | }; 536 | name = Release; 537 | }; 538 | 97C147061CF9000F007C117D /* Debug */ = { 539 | isa = XCBuildConfiguration; 540 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 541 | buildSettings = { 542 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 543 | CLANG_ENABLE_MODULES = YES; 544 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 545 | DEVELOPMENT_TEAM = 97M85WK68C; 546 | ENABLE_BITCODE = NO; 547 | INFOPLIST_FILE = Runner/Info.plist; 548 | LD_RUNPATH_SEARCH_PATHS = ( 549 | "$(inherited)", 550 | "@executable_path/Frameworks", 551 | ); 552 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dartMeteorExampleApp; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 555 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 556 | SWIFT_VERSION = 5.0; 557 | VERSIONING_SYSTEM = "apple-generic"; 558 | }; 559 | name = Debug; 560 | }; 561 | 97C147071CF9000F007C117D /* Release */ = { 562 | isa = XCBuildConfiguration; 563 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 564 | buildSettings = { 565 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 566 | CLANG_ENABLE_MODULES = YES; 567 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 568 | DEVELOPMENT_TEAM = 97M85WK68C; 569 | ENABLE_BITCODE = NO; 570 | INFOPLIST_FILE = Runner/Info.plist; 571 | LD_RUNPATH_SEARCH_PATHS = ( 572 | "$(inherited)", 573 | "@executable_path/Frameworks", 574 | ); 575 | PRODUCT_BUNDLE_IDENTIFIER = com.example.dartMeteorExampleApp; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 578 | SWIFT_VERSION = 5.0; 579 | VERSIONING_SYSTEM = "apple-generic"; 580 | }; 581 | name = Release; 582 | }; 583 | /* End XCBuildConfiguration section */ 584 | 585 | /* Begin XCConfigurationList section */ 586 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | 331C8088294A63A400263BE5 /* Debug */, 590 | 331C8089294A63A400263BE5 /* Release */, 591 | 331C808A294A63A400263BE5 /* Profile */, 592 | ); 593 | defaultConfigurationIsVisible = 0; 594 | defaultConfigurationName = Release; 595 | }; 596 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 597 | isa = XCConfigurationList; 598 | buildConfigurations = ( 599 | 97C147031CF9000F007C117D /* Debug */, 600 | 97C147041CF9000F007C117D /* Release */, 601 | 249021D3217E4FDB00AE95B9 /* Profile */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 97C147061CF9000F007C117D /* Debug */, 610 | 97C147071CF9000F007C117D /* Release */, 611 | 249021D4217E4FDB00AE95B9 /* Profile */, 612 | ); 613 | defaultConfigurationIsVisible = 0; 614 | defaultConfigurationName = Release; 615 | }; 616 | /* End XCConfigurationList section */ 617 | }; 618 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 619 | } 620 | --------------------------------------------------------------------------------