├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── PusherClientPlugin.h │ ├── MChannel.swift │ ├── SwiftPusherClientPlugin.swift │ ├── PusherClientPlugin.m │ ├── Constants.swift │ ├── StreamHandler.swift │ ├── ChannelEventListener.swift │ ├── AuthRequestBuilder.swift │ ├── ConnectionListener.swift │ ├── Models.swift │ └── PusherService.swift ├── .gitignore └── pusher_client.podspec ├── android ├── .idea │ ├── .name │ ├── compiler.xml │ ├── vcs.xml │ ├── misc.xml │ ├── modules.xml │ ├── gradle.xml │ └── jarRepositories.xml ├── settings.gradle ├── gradle.properties ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── github │ │ └── chinloyal │ │ └── pusher_client │ │ ├── core │ │ ├── contracts │ │ │ └── MChannel.kt │ │ └── utils │ │ │ ├── Constants.kt │ │ │ └── JsonEncodedConnectionFactory.kt │ │ ├── pusher │ │ ├── listeners │ │ │ ├── FlutterChannelEventListener.kt │ │ │ ├── FlutterPrivateChannelEventListener.kt │ │ │ ├── FlutterPrivateEncryptedChannelEventListener.kt │ │ │ ├── FlutterBaseChannelEventListener.kt │ │ │ ├── FlutterPresenceChannelEventListener.kt │ │ │ └── ConnectionListener.kt │ │ └── PusherService.kt │ │ └── PusherClientPlugin.kt ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── build.gradle ├── example ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ ├── Podfile.lock │ └── Podfile ├── android │ ├── gradle.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 │ │ │ │ │ └── values │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── github │ │ │ │ │ │ └── chinloyal │ │ │ │ │ │ └── pusher_client_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── README.md ├── .gitignore ├── test │ └── widget_test.dart ├── pubspec.yaml ├── lib │ └── main.dart └── pubspec.lock ├── .gitignore ├── lib ├── pusher_client.dart └── src │ ├── pusher │ ├── pusher_client.g.dart │ ├── pusher_auth.g.dart │ ├── pusher_auth.dart │ ├── pusher_event.g.dart │ ├── pusher_event.dart │ ├── pusher_options.g.dart │ ├── pusher_options.dart │ ├── channel.dart │ └── pusher_client.dart │ ├── models │ ├── connection_error.g.dart │ ├── connection_state_change.g.dart │ ├── connection_error.dart │ ├── connection_state_change.dart │ ├── event_stream_result.dart │ └── event_stream_result.g.dart │ └── contracts │ └── stream_handler.dart ├── .metadata ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── FUNDING.yml └── workflows │ └── dart.yml ├── .vscode └── launch.json ├── pubspec.yaml ├── pusher_client.iml ├── test └── pusher_client_test.dart ├── LICENSE ├── CHANGELOG.md ├── pubspec.lock └── README.md /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/.idea/.name: -------------------------------------------------------------------------------- 1 | pusher_client -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'pusher_client' 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | .idea/ 9 | .fvm/** -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ios/Classes/PusherClientPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface PusherClientPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinloyal/pusher_client/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/chinloyal/pusher_client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/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/chinloyal/pusher_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinloyal/pusher_client/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/chinloyal/pusher_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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-5.6.2-all.zip 6 | -------------------------------------------------------------------------------- /ios/Classes/MChannel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MChannel.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/26/20. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol MChannel { 11 | func register(messenger: FlutterBinaryMessenger) -> Void 12 | } 13 | -------------------------------------------------------------------------------- /lib/pusher_client.dart: -------------------------------------------------------------------------------- 1 | library pusher_client; 2 | 3 | export 'src/pusher/channel.dart'; 4 | export 'src/pusher/pusher_auth.dart'; 5 | export 'src/pusher/pusher_options.dart'; 6 | export 'src/pusher/pusher_client.dart' show PusherClient; 7 | export 'src/pusher/pusher_event.dart'; 8 | -------------------------------------------------------------------------------- /ios/Classes/SwiftPusherClientPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftPusherClientPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | PusherService().register(messenger: registrar.messenger()) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 24 14:05:31 EST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/core/contracts/MChannel.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.core.contracts 2 | 3 | import androidx.annotation.NonNull 4 | import io.flutter.plugin.common.BinaryMessenger 5 | 6 | interface MChannel { 7 | fun register (@NonNull messenger: BinaryMessenger) 8 | } -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/pusher/listeners/FlutterChannelEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.pusher.listeners 2 | 3 | class FlutterChannelEventListener: FlutterBaseChannelEventListener() { 4 | companion object { 5 | val instance = FlutterChannelEventListener() 6 | } 7 | } -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/github/chinloyal/pusher_client_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client_example 2 | 3 | import androidx.annotation.NonNull 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | 7 | class MainActivity: FlutterActivity() { 8 | } 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /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: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/pusher/pusher_client.g.dart: -------------------------------------------------------------------------------- 1 | // YOU SHOULD MODIFY BY HAND 2 | 3 | part of 'pusher_client.dart'; 4 | 5 | // InitArgs _$InitArgsFromJson(Map json) { 6 | // return InitArgs( 7 | // enableLogging: json['enableLogging'] as bool, 8 | // ); 9 | // } 10 | 11 | Map _$InitArgsToJson(InitArgs instance) => { 12 | 'enableLogging': instance.enableLogging, 13 | }; 14 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: chinloyal 7 | 8 | --- 9 | 10 | ### Steps to reproduce 11 | 12 | What must I do to reproduce the bug? 13 | 14 | ### Expected behavior 15 | 16 | What did you expect to happen? 17 | 18 | ### Actual behavior 19 | 20 | What actually happened? Submit stack traces or anything that you think would help. 21 | 22 | ### Any improvements you suggest 23 | 24 | ... 25 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/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 | -------------------------------------------------------------------------------- /lib/src/pusher/pusher_auth.g.dart: -------------------------------------------------------------------------------- 1 | // YOU SHOULD MODIFY BY HAND 2 | 3 | part of 'pusher_auth.dart'; 4 | 5 | PusherAuth _$PusherAuthFromJson(Map json) { 6 | return PusherAuth( 7 | json['endpoint'] as String?, 8 | headers: (json['headers'] as Map?)?.map( 9 | (k, e) => MapEntry(k, e as String), 10 | ), 11 | ); 12 | } 13 | 14 | Map _$PusherAuthToJson(PusherAuth instance) => 15 | { 16 | 'endpoint': instance.endpoint, 17 | 'headers': instance.headers, 18 | }; 19 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/core/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.core.utils 2 | 3 | enum class Constants(val value: String) { 4 | CONNECTION_ESTABLISHED("pusher:connection_established"), 5 | ERROR("pusher:error"), 6 | SUBSCRIBE("pusher:subscribe"), 7 | UNSUBSCRIBE("pusher:unsubscribe"), 8 | SUBSCRIPTION_ERROR("pusher:subscription_error"), 9 | SUBSCRIPTION_SUCCEEDED("pusher:subscription_succeeded"), 10 | MEMBER_ADDED("pusher:member_added"), 11 | MEMBER_REMOVED("pusher:member_removed"), 12 | } -------------------------------------------------------------------------------- /lib/src/pusher/pusher_auth.dart: -------------------------------------------------------------------------------- 1 | part 'pusher_auth.g.dart'; 2 | 3 | class PusherAuth { 4 | /// The endpoint to be called when authenticating. 5 | final String? endpoint; 6 | 7 | /// Additional headers to be sent as part of the request. 8 | final Map? headers; 9 | 10 | PusherAuth( 11 | this.endpoint, { 12 | this.headers = const {'Content-Type': 'application/json'}, 13 | }); 14 | 15 | factory PusherAuth.fromJson(Map json) => 16 | _$PusherAuthFromJson(json); 17 | Map toJson() => _$PusherAuthToJson(this); 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "pusher_client", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "example", 14 | "cwd": "example", 15 | "request": "launch", 16 | "type": "dart" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /lib/src/models/connection_error.g.dart: -------------------------------------------------------------------------------- 1 | // YOU SHOULD MODIFY BY HAND 2 | 3 | part of 'connection_error.dart'; 4 | 5 | ConnectionError _$ConnectionErrorFromJson(Map json) { 6 | return ConnectionError( 7 | message: json['message'] as String?, 8 | code: json['code'] as String?, 9 | exception: json['exception'] as String?, 10 | ); 11 | } 12 | 13 | Map _$ConnectionErrorToJson(ConnectionError instance) => 14 | { 15 | 'message': instance.message, 16 | 'code': instance.code, 17 | 'exception': instance.exception, 18 | }; 19 | -------------------------------------------------------------------------------- /lib/src/models/connection_state_change.g.dart: -------------------------------------------------------------------------------- 1 | // YOU SHOULD MODIFY BY HAND 2 | 3 | part of 'connection_state_change.dart'; 4 | 5 | ConnectionStateChange _$ConnectionStateChangeFromJson( 6 | Map json) { 7 | return ConnectionStateChange( 8 | currentState: json['currentState'] as String?, 9 | previousState: json['previousState'] as String?, 10 | ); 11 | } 12 | 13 | Map _$ConnectionStateChangeToJson( 14 | ConnectionStateChange instance) => 15 | { 16 | 'currentState': instance.currentState, 17 | 'previousState': instance.previousState, 18 | }; 19 | -------------------------------------------------------------------------------- /lib/src/models/connection_error.dart: -------------------------------------------------------------------------------- 1 | part 'connection_error.g.dart'; 2 | 3 | class ConnectionError { 4 | /// A message indicating the cause of the error. 5 | final String? message; 6 | 7 | /// The error code for the message. Can be null. 8 | final String? code; 9 | 10 | /// The exception that was thrown, if any. Can be null. 11 | final String? exception; 12 | 13 | ConnectionError({this.message, this.code, this.exception}); 14 | 15 | factory ConnectionError.fromJson(Map json) => 16 | _$ConnectionErrorFromJson(json); 17 | 18 | Map toJson() => _$ConnectionErrorToJson(this); 19 | } 20 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # pusher_client_example 2 | 3 | Demonstrates how to use the pusher_client plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /ios/Classes/PusherClientPlugin.m: -------------------------------------------------------------------------------- 1 | #import "PusherClientPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "pusher_client-Swift.h" 9 | #endif 10 | 11 | @implementation PusherClientPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftPusherClientPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /lib/src/pusher/pusher_event.g.dart: -------------------------------------------------------------------------------- 1 | // YOU SHOULD MODIFY BY HAND 2 | 3 | part of 'pusher_event.dart'; 4 | 5 | PusherEvent _$PusherEventFromJson(Map json) { 6 | return PusherEvent( 7 | channelName: json['channelName'] as String?, 8 | eventName: json['eventName'] as String?, 9 | data: json['data'] as String?, 10 | userId: json['userId'] as String?, 11 | ); 12 | } 13 | 14 | Map _$PusherEventToJson(PusherEvent instance) => 15 | { 16 | 'channelName': instance.channelName, 17 | 'eventName': instance.eventName, 18 | 'data': instance.data, 19 | 'userId': instance.userId, 20 | }; 21 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /lib/src/models/connection_state_change.dart: -------------------------------------------------------------------------------- 1 | part 'connection_state_change.g.dart'; 2 | 3 | class ConnectionStateChange { 4 | /// The current connection state. The state the connection has transitioned 5 | /// to. 6 | final String? currentState; 7 | 8 | /// The previous connections state. The state the connection has transitioned 9 | /// from. 10 | final String? previousState; 11 | 12 | ConnectionStateChange({ 13 | this.currentState, 14 | this.previousState, 15 | }); 16 | 17 | factory ConnectionStateChange.fromJson(Map json) => 18 | _$ConnectionStateChangeFromJson(json); 19 | Map toJson() => _$ConnectionStateChangeToJson(this); 20 | } 21 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/core/utils/JsonEncodedConnectionFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.core.utils 2 | 3 | import com.pusher.client.util.ConnectionFactory 4 | import org.json.JSONObject 5 | 6 | class JsonEncodedConnectionFactory : ConnectionFactory() { 7 | override fun getCharset(): String { 8 | return "UTF-8"; 9 | } 10 | 11 | override fun getContentType(): String { 12 | return "application/json"; 13 | } 14 | 15 | override fun getBody(): String { 16 | val data: JSONObject = JSONObject(); 17 | data.put("channel_name", channelName); 18 | data.put("socket_id", socketId); 19 | 20 | return data.toString(); 21 | } 22 | } -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /ios/Classes/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/31/20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Constants { 11 | enum Events: String, CaseIterable { 12 | case connectionEstablished = "pusher:connection_established" 13 | case error = "pusher:error" 14 | case subscribe = "pusher:subscribe" 15 | case unsubscribe = "pusher:unsubscribe" 16 | case subscriptionError = "pusher:subscription_error" 17 | case subscriptionSucceeded = "pusher:subscription_succeeded" 18 | } 19 | 20 | enum PresenceEvents: String, CaseIterable { 21 | case memberAdded = "pusher:member_added" 22 | case memberRemoved = "pusher:member_removed" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pusher_client 2 | description: | 3 | A Pusher Channels client plugin for Flutter targeting Android and iOS. It 4 | wraps pusher-websocket-java v2.2.5 and pusher-websocket-swift v8.0.0. 5 | version: 2.0.0 6 | homepage: https://github.com/chinloyal/pusher_client 7 | documentation: https://github.com/chinloyal/pusher_client/blob/master/README.md 8 | 9 | 10 | environment: 11 | sdk: '>=2.12.0 <3.0.0' 12 | flutter: ">=1.20.0 <2.0.0" 13 | 14 | dependencies: 15 | flutter: 16 | sdk: flutter 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | flutter: 23 | plugin: 24 | platforms: 25 | android: 26 | package: com.github.chinloyal.pusher_client 27 | pluginClass: PusherClientPlugin 28 | ios: 29 | pluginClass: PusherClientPlugin 30 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | testdata.txt -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: chinloyal 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Publish pusher_client to pub.dev 2 | on: 3 | release: 4 | types: 5 | - published 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | container: 10 | image: google/dart:latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Setup credentials 14 | run: | 15 | mkdir -p ~/.pub-cache 16 | cat < ~/.pub-cache/credentials.json 17 | { 18 | "accessToken":"${{ secrets.PUB_ACCESS_TOKEN }}", 19 | "refreshToken":"${{ secrets.PUB_REFRESH_TOKEN }}", 20 | "tokenEndpoint":"https://accounts.google.com/o/oauth2/token", 21 | "scopes": [ "openid", "https://www.googleapis.com/auth/userinfo.email" ], 22 | "expiration": 1604281072748 23 | } 24 | EOF 25 | - name: Publish package 26 | run: pub publish -f 27 | 28 | -------------------------------------------------------------------------------- /ios/Classes/StreamHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamHandler.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/29/20. 6 | // 7 | 8 | class StreamHandler: NSObject, FlutterStreamHandler { 9 | static let `default`: StreamHandler = StreamHandler() 10 | 11 | private override init(){} 12 | 13 | struct Utils { 14 | static var eventSink: FlutterEventSink? 15 | } 16 | 17 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 18 | Utils.eventSink = events 19 | PusherService.Utils.debugLog(msg: "Event stream listening...") 20 | return nil 21 | } 22 | 23 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 24 | PusherService.Utils.debugLog(msg: "Event stream cancelled.") 25 | return nil 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/pusher/pusher_event.dart: -------------------------------------------------------------------------------- 1 | part 'pusher_event.g.dart'; 2 | 3 | class PusherEvent { 4 | /// The name of the channel that the event was triggered on. Not present 5 | /// in events without an associated channel, For example "pusher:error" events 6 | /// relating to the connection. 7 | final String? channelName; 8 | 9 | /// The name of the event. 10 | final String? eventName; 11 | 12 | /// The data that was passed when the event was triggered. 13 | final String? data; 14 | 15 | /// The ID of the user who triggered the event. Only present in 16 | /// client event on presence channels. 17 | final String? userId; 18 | 19 | PusherEvent({ 20 | this.channelName, 21 | this.eventName, 22 | this.data, 23 | this.userId, 24 | }); 25 | 26 | factory PusherEvent.fromJson(Map json) => 27 | _$PusherEventFromJson(json); 28 | Map toJson() => _$PusherEventToJson(this); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/models/event_stream_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:pusher_client/src/models/connection_error.dart'; 2 | import 'package:pusher_client/src/models/connection_state_change.dart'; 3 | import 'package:pusher_client/src/pusher/pusher_event.dart'; 4 | 5 | part 'event_stream_result.g.dart'; 6 | 7 | class EventStreamResult { 8 | final ConnectionStateChange? connectionStateChange; 9 | final ConnectionError? connectionError; 10 | final PusherEvent? pusherEvent; 11 | 12 | EventStreamResult({ 13 | this.connectionStateChange, 14 | this.connectionError, 15 | this.pusherEvent, 16 | }); 17 | 18 | bool get isConnectionStateChange => connectionStateChange != null; 19 | bool get isConnectionError => connectionError != null; 20 | bool get isPusherEvent => pusherEvent != null; 21 | 22 | factory EventStreamResult.fromJson(Map json) => 23 | _$EventStreamResultFromJson(json); 24 | Map toJson() => _$EventStreamResultToJson(this); 25 | } 26 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/PusherClientPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client 2 | 3 | import androidx.annotation.NonNull 4 | import com.github.chinloyal.pusher_client.pusher.PusherService 5 | 6 | import io.flutter.embedding.engine.plugins.FlutterPlugin 7 | import io.flutter.plugin.common.MethodCall 8 | import io.flutter.plugin.common.MethodChannel 9 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 10 | import io.flutter.plugin.common.MethodChannel.Result 11 | import io.flutter.plugin.common.PluginRegistry.Registrar 12 | 13 | /** PusherClientPlugin */ 14 | class PusherClientPlugin: FlutterPlugin { 15 | 16 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 17 | PusherService().register(flutterPluginBinding.binaryMessenger); 18 | } 19 | 20 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 21 | // channel.setMethodCallHandler(null) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pusher_client.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:pusher_client_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /test/pusher_client_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:pusher_client/pusher_client.dart'; 5 | 6 | void main() { 7 | WidgetsFlutterBinding.ensureInitialized(); 8 | MethodChannel channel = 9 | const MethodChannel('com.github.chinloyal/pusher_client'); 10 | group('PusherClient Test | ', () { 11 | setUp(() { 12 | channel.setMockMethodCallHandler((call) { 13 | switch (call.method) { 14 | case 'init': 15 | return null; 16 | case 'connect': 17 | return null; 18 | default: 19 | throw UnimplementedError(); 20 | } 21 | }); 22 | }); 23 | 24 | test('Pusher client returns a singleton', () { 25 | var pusher1 = PusherClient('key', PusherOptions()); 26 | var pusher2 = PusherClient('key', PusherOptions()); 27 | 28 | expect(pusher1.hashCode, pusher2.hashCode); 29 | }); 30 | 31 | // test() 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/models/event_stream_result.g.dart: -------------------------------------------------------------------------------- 1 | // YOU SHOULD MODIFY BY HAND 2 | 3 | part of 'event_stream_result.dart'; 4 | 5 | EventStreamResult _$EventStreamResultFromJson(Map json) { 6 | return EventStreamResult( 7 | connectionStateChange: json['connectionStateChange'] == null 8 | ? null 9 | : ConnectionStateChange.fromJson( 10 | json['connectionStateChange'] as Map), 11 | connectionError: json['connectionError'] == null 12 | ? null 13 | : ConnectionError.fromJson( 14 | json['connectionError'] as Map), 15 | pusherEvent: json['pusherEvent'] == null 16 | ? null 17 | : PusherEvent.fromJson(json['pusherEvent'] as Map), 18 | ); 19 | } 20 | 21 | Map _$EventStreamResultToJson(EventStreamResult instance) => 22 | { 23 | 'connectionStateChange': instance.connectionStateChange, 24 | 'connectionError': instance.connectionError, 25 | 'pusherEvent': instance.pusherEvent, 26 | }; 27 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.github.chinloyal.pusher_client' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.3.50' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.5.0' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 29 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 16 35 | } 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | } 39 | 40 | dependencies { 41 | implementation "com.pusher:pusher-java-client:2.2.5" 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 47 | } -------------------------------------------------------------------------------- /lib/src/contracts/stream_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/services.dart'; 4 | 5 | abstract class StreamHandler { 6 | static const EventChannel _eventStream = const EventChannel('com.github.chinloyal/pusher_client_stream'); 7 | late StreamSubscription _eventStreamSubscription; 8 | 9 | static Map _listeners = {}; 10 | 11 | /// Add a listener to the event channel stream for pusher, 12 | /// any class that extends [StreamHandler] should use this method. 13 | void registerListener(String classId, dynamic Function(dynamic) method) { 14 | StreamHandler._listeners[classId] = method; 15 | _eventStreamSubscription = _eventStream.receiveBroadcastStream().listen(_eventHandler); 16 | } 17 | 18 | /// This method will close the entire event channel stream 19 | /// which is why it should only be used by [PusherClient] 20 | void cancelEventChannelStream() { 21 | _eventStreamSubscription.cancel(); 22 | } 23 | 24 | void _eventHandler(event) { 25 | _listeners.values.forEach((method) { 26 | method(event); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Romario Chinloy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /ios/Classes/ChannelEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChannelEventListener.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/31/20. 6 | // 7 | 8 | import Foundation 9 | import PusherSwiftWithEncryption 10 | 11 | class ChannelEventListener { 12 | static let `default`: ChannelEventListener = ChannelEventListener() 13 | 14 | private init(){} 15 | 16 | func onEvent(event: PusherEvent) -> Void { 17 | let pusherEvent = Event(eventName: event.eventName, channelName: event.channelName, userId: event.userId, data: event.data) 18 | 19 | do { 20 | let data = try JSONEncoder().encode(EventStreamResult(pusherEvent: pusherEvent)) 21 | StreamHandler.Utils.eventSink!(String(data: data, encoding: .utf8)) 22 | 23 | PusherService.Utils.debugLog(msg: "[ON_EVENT] Channel: \(event.channelName ?? ""), EventName: \(event.eventName), Data: \(event.data ?? ""), User Id: \(event.userId ?? "")") 24 | } catch let err { 25 | StreamHandler.Utils.eventSink!(FlutterError(code: "ON_EVENT_ERROR", message: err.localizedDescription, details: err)) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ios/pusher_client.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint pusher_client.podspec' to validate before publishing. 4 | # 5 | require 'yaml' 6 | pubspec = YAML.load_file(File.join('..', 'pubspec.yaml')) 7 | libraryVersion = pubspec['version'].gsub('+', '-') 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'pusher_client' 11 | s.version = libraryVersion 12 | s.summary = 'A pusher client plugin.' 13 | s.description = <<-DESC 14 | A pusher client plugin that works. 15 | DESC 16 | s.homepage = 'https://github.com/chinloyal/pusher_client' 17 | s.license = { :file => '../LICENSE', :type => 'MIT' } 18 | s.author = { 'Romario Chinloy' => 'jordain7@protonmail.com' } 19 | s.source = { :path => '.' } 20 | s.source_files = 'Classes/**/*' 21 | s.dependency 'Flutter' 22 | s.dependency 'PusherSwiftWithEncryption', '~> 8.0.0' 23 | s.platform = :ios, '9.0' 24 | 25 | # Flutter.framework does not contain a i386 slice. 26 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 27 | s.swift_version = '5.0' 28 | end 29 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - pusher_client (1.0.0): 4 | - Flutter 5 | - PusherSwiftWithEncryption (~> 8.0.0) 6 | - PusherSwiftWithEncryption (8.0.0): 7 | - ReachabilitySwift (~> 5.0) 8 | - Sodium (= 0.8.0) 9 | - Starscream (~> 3.1) 10 | - ReachabilitySwift (5.0.0) 11 | - Sodium (0.8.0) 12 | - Starscream (3.1.1) 13 | 14 | DEPENDENCIES: 15 | - Flutter (from `Flutter`) 16 | - pusher_client (from `.symlinks/plugins/pusher_client/ios`) 17 | 18 | SPEC REPOS: 19 | trunk: 20 | - PusherSwiftWithEncryption 21 | - ReachabilitySwift 22 | - Sodium 23 | - Starscream 24 | 25 | EXTERNAL SOURCES: 26 | Flutter: 27 | :path: Flutter 28 | pusher_client: 29 | :path: ".symlinks/plugins/pusher_client/ios" 30 | 31 | SPEC CHECKSUMS: 32 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 33 | pusher_client: 98d16b50f0ebb4d67d3f61b29723142c03179da9 34 | PusherSwiftWithEncryption: 8ed9c9cf743b0530aa3a8c60de6734475f1aa67d 35 | ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 36 | Sodium: 63c0ca312a932e6da481689537d4b35568841bdc 37 | Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0 38 | 39 | PODFILE CHECKSUM: a75497545d4391e2d394c3668e20cfb1c2bbd4aa 40 | 41 | COCOAPODS: 1.9.3 42 | -------------------------------------------------------------------------------- /ios/Classes/AuthRequestBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthRequestBuilder.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/27/20. 6 | // 7 | 8 | import PusherSwiftWithEncryption 9 | 10 | class AuthRequestBuilder: AuthRequestBuilderProtocol { 11 | let pusherAuth: PusherAuth 12 | 13 | init(pusherAuth: PusherAuth) { 14 | self.pusherAuth = pusherAuth 15 | } 16 | 17 | func requestFor(socketID: String, channelName: String) -> URLRequest? { 18 | var request = URLRequest(url: URL(string: pusherAuth.endpoint)!) 19 | request.httpMethod = "POST" 20 | do { 21 | let authRequest = AuthRequest(socket_id: socketID, channel_name: channelName) 22 | if(pusherAuth.headers.values.contains("application/json")) { 23 | request.httpBody = try JSONEncoder().encode(authRequest) 24 | } else { 25 | request.httpBody = authRequest.valueUrlEncoded!.data(using: String.Encoding.utf8) 26 | } 27 | 28 | pusherAuth.headers.forEach { (key: String, value: String) in 29 | request.addValue(value, forHTTPHeaderField: key) 30 | } 31 | 32 | return request 33 | } catch { 34 | return nil 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.0 - August 31, 2021 2 | 3 | * Upgraded to null safety [@nizwar](https://github.com/nizwar) 4 | 5 | ## 1.1.3 - January 24, 2021 6 | * BUGFIX: Binding to channel events cancels calls to onConnectionStateChange 7 | * Added a Stream Handler contract to allow multiple classes to listen to the same event channel 8 | 9 | ## 1.1.2 - January 20, 2021 10 | * PATCH: Removed log4j dependency for android 11 | 12 | ## 1.1.1 - January 19, 2021 13 | * BUGFIX: ConnectionEventListener null safety issue for Android (fixed by [@spiritinlife](https://github.com/spiritinlife)) 14 | 15 | ## 1.1.0 - November 20, 2020 16 | * MINOR: Removed json_serializable and build_runner dependencies, this prevents conflict with projects that have to use those libraries at a specific version. 17 | 18 | ## 1.0.2 - November 17, 2020 19 | * BUGFIX: Fixes issue #1, Double backslashes in the bind event breaks the JSON encoding. (This issue was only on android, iOS worked fine.) 20 | 21 | ## 1.0.1 - November 7, 2020 22 | 23 | * BUGFIX: Fixed not being able to bind to multiple types of channels at once. 24 | 25 | ## 1.0.0 - October 26, 2020 26 | 27 | * This is a complete Pusher Channels client library for flutter that fully supports Android and iOS with all the necessary and newest features such as having private encrypted channels. Triggering client event also works on both Android and iOS 28 | -------------------------------------------------------------------------------- /lib/src/pusher/pusher_options.g.dart: -------------------------------------------------------------------------------- 1 | // YOU SHOULD MODIFY BY HAND 2 | 3 | part of 'pusher_options.dart'; 4 | 5 | PusherOptions _$PusherOptionsFromJson(Map json) { 6 | return PusherOptions( 7 | auth: json['auth'] == null 8 | ? null 9 | : PusherAuth.fromJson(json['auth'] as Map), 10 | cluster: json['cluster'] as String?, 11 | host: json['host'] as String?, 12 | wsPort: json['wsPort'] as int?, 13 | wssPort: json['wssPort'] as int?, 14 | encrypted: json['encrypted'] as bool?, 15 | activityTimeout: json['activityTimeout'] as int?, 16 | pongTimeout: json['pongTimeout'] as int?, 17 | maxReconnectionAttempts: json['maxReconnectionAttempts'] as int?, 18 | maxReconnectGapInSeconds: json['maxReconnectGapInSeconds'] as int?, 19 | ); 20 | } 21 | 22 | Map _$PusherOptionsToJson(PusherOptions instance) => 23 | { 24 | 'auth': instance.auth, 25 | 'cluster': instance.cluster, 26 | 'host': instance.host, 27 | 'encrypted': instance.encrypted, 28 | 'activityTimeout': instance.activityTimeout, 29 | 'pongTimeout': instance.pongTimeout, 30 | 'wsPort': instance.wsPort, 31 | 'wssPort': instance.wssPort, 32 | 'maxReconnectionAttempts': instance.maxReconnectionAttempts, 33 | 'maxReconnectGapInSeconds': instance.maxReconnectGapInSeconds, 34 | }; 35 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/pusher/listeners/FlutterPrivateChannelEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.pusher.listeners 2 | 3 | import com.github.chinloyal.pusher_client.core.utils.Constants 4 | import com.github.chinloyal.pusher_client.pusher.PusherService 5 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.enableLogging 6 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.errorLog 7 | import com.pusher.client.channel.PrivateChannelEventListener 8 | import com.pusher.client.channel.PusherEvent 9 | import java.lang.Exception 10 | 11 | class FlutterPrivateChannelEventListener: FlutterBaseChannelEventListener(), PrivateChannelEventListener { 12 | companion object { 13 | val instance = FlutterPrivateChannelEventListener() 14 | } 15 | 16 | override fun onAuthenticationFailure(message: String, e: Exception) { 17 | errorLog(message) 18 | if(enableLogging) e.printStackTrace() 19 | } 20 | 21 | override fun onSubscriptionSucceeded(channelName: String) { 22 | this.onEvent(PusherEvent(mapOf( 23 | "event" to Constants.SUBSCRIPTION_SUCCEEDED.value, 24 | "channel" to channelName, 25 | "user_id" to null, 26 | "data" to null 27 | ))) 28 | PusherService.debugLog("[PRIVATE] Subscribed: $channelName") 29 | } 30 | } -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/pusher/listeners/FlutterPrivateEncryptedChannelEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.pusher.listeners 2 | 3 | import com.github.chinloyal.pusher_client.core.utils.Constants 4 | import com.github.chinloyal.pusher_client.pusher.PusherService 5 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.enableLogging 6 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.errorLog 7 | import com.pusher.client.channel.PrivateEncryptedChannelEventListener 8 | import com.pusher.client.channel.PusherEvent 9 | import java.lang.Exception 10 | 11 | class FlutterPrivateEncryptedChannelEventListener: FlutterBaseChannelEventListener(), PrivateEncryptedChannelEventListener { 12 | companion object { 13 | val instance = FlutterPrivateEncryptedChannelEventListener() 14 | } 15 | 16 | override fun onDecryptionFailure(event: String, reason: String) { 17 | errorLog("[PRIVATE-ENCRYPTED] Reason: $reason, Event: $event") 18 | } 19 | 20 | override fun onAuthenticationFailure(message: String, e: Exception) { 21 | errorLog(message) 22 | if(enableLogging) e.printStackTrace() 23 | } 24 | 25 | override fun onSubscriptionSucceeded(channelName: String) { 26 | this.onEvent(PusherEvent(mapOf( 27 | "event" to Constants.SUBSCRIPTION_SUCCEEDED.value, 28 | "channel" to channelName, 29 | "user_id" to null, 30 | "data" to null 31 | ))) 32 | PusherService.debugLog("[PRIVATE-ENCRYPTED] Subscribed: $channelName") 33 | } 34 | } -------------------------------------------------------------------------------- /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 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | pusher_client_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ios/Classes/ConnectionListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionListener.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/27/20. 6 | // 7 | 8 | import PusherSwiftWithEncryption 9 | 10 | class ConnectionListener: PusherDelegate { 11 | static let `default`: ConnectionListener = ConnectionListener() 12 | 13 | private init(){} 14 | 15 | func changedConnectionState(from old: ConnectionState, to new: ConnectionState) { 16 | do { 17 | let connectionStateChange = ConnectionStateChange(currentState: new.stringValue(), previousState: old.stringValue()) 18 | PusherService.Utils.debugLog(msg: "[\(new.stringValue())]") 19 | let data = try JSONEncoder().encode(EventStreamResult(connectionStateChange: connectionStateChange)) 20 | 21 | StreamHandler.Utils.eventSink!(String(data: data, encoding: .utf8)) 22 | 23 | } catch let err { 24 | PusherService.Utils.errorLog(msg: err.localizedDescription) 25 | } 26 | 27 | } 28 | 29 | func receivedError(error: PusherError) { 30 | do { 31 | let connectionError = ConnectionError(message: error.message, code: String(describing: error.code), exception: error.description) 32 | 33 | PusherService.Utils.debugLog(msg: "[ON_ERROR]: message: \(error.message), code: \(String(describing: error.code))") 34 | let data = try JSONEncoder().encode(EventStreamResult(connectionError: connectionError)) 35 | StreamHandler.Utils.eventSink!(String(data: data, encoding: .utf8)) 36 | } catch let err { 37 | PusherService.Utils.errorLog(msg: err.localizedDescription) 38 | } 39 | 40 | } 41 | 42 | // func debugLog(message: String) { 43 | // PusherService.Utils.debugLog(msg: message) 44 | // } 45 | } 46 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.github.chinloyal.pusher_client_example" 42 | minSdkVersion 16 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/pusher/listeners/FlutterBaseChannelEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.pusher.listeners 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import com.github.chinloyal.pusher_client.core.utils.Constants 6 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.debugLog 7 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.eventSink 8 | import com.pusher.client.channel.ChannelEventListener 9 | import com.pusher.client.channel.PusherEvent 10 | import org.json.JSONObject 11 | import java.lang.Exception 12 | 13 | open class FlutterBaseChannelEventListener: ChannelEventListener { 14 | private val eventStreamJson = JSONObject(); 15 | 16 | override fun onEvent(event: PusherEvent) { 17 | Handler(Looper.getMainLooper()).post { 18 | try { 19 | val eventJson = JSONObject(mapOf( 20 | "channelName" to event.channelName, 21 | "eventName" to event.eventName, 22 | "userId" to event.userId, 23 | "data" to event.data 24 | )) 25 | 26 | eventStreamJson.put("pusherEvent", eventJson) 27 | 28 | eventSink?.success(eventStreamJson.toString()) 29 | debugLog(""" 30 | |[ON_EVENT] Channel: ${event.channelName}, EventName: ${event.eventName}, 31 | |Data: ${event.data}, User Id: ${event.userId} 32 | """.trimMargin()) 33 | } catch (e: Exception) { 34 | eventSink?.error("ON_EVENT_ERROR", e.message, e) 35 | } 36 | 37 | } 38 | } 39 | 40 | override fun onSubscriptionSucceeded(channelName: String) { 41 | this.onEvent(PusherEvent(mapOf( 42 | "event" to Constants.SUBSCRIPTION_SUCCEEDED.value, 43 | "channel" to channelName, 44 | "user_id" to null, 45 | "data" to null 46 | ))) 47 | debugLog("[PUBLIC] Subscribed: $channelName") 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /lib/src/pusher/pusher_options.dart: -------------------------------------------------------------------------------- 1 | import 'package:pusher_client/src/pusher/pusher_auth.dart'; 2 | 3 | part 'pusher_options.g.dart'; 4 | 5 | class PusherOptions { 6 | /// Sets the authorization options to be used when authenticating private, 7 | /// private-encrypted and presence channels. 8 | final PusherAuth? auth; 9 | 10 | /// A "cluster" represents the physical location of the servers that handle 11 | /// requests from your Channels app. 12 | /// 13 | /// For example, the Channels cluster `mt1` is in Northern Virginia 14 | /// in the United States. 15 | final String? cluster; 16 | 17 | /// The host to which connections will be made. 18 | final String? host; 19 | 20 | /// Whether the connection to Pusher should be use TLS. 21 | final bool? encrypted; 22 | 23 | /// The number of milliseconds of inactivity at which a "ping" will be 24 | /// triggered to check the connection. 25 | final int? activityTimeout; 26 | 27 | /// The port to which non TLS connections will be made. 28 | final int? wsPort; 29 | 30 | /// The port to which encrypted connections will be made. 31 | final int? wssPort; 32 | 33 | /// The number of milliseconds after a "ping" is sent that the client will 34 | /// wait to receive a "pong" response from the server before considering the 35 | /// connection broken and triggering a transition to the disconnected state. 36 | /// 37 | /// The default value is 30,000. 38 | final int? pongTimeout; 39 | 40 | /// Number of reconnect attempts when websocket connection failed 41 | final int? maxReconnectionAttempts; 42 | 43 | /// The delay in two reconnection extends exponentially (1, 2, 4, .. seconds) 44 | /// This property sets the maximum in between two reconnection attempts. 45 | final int? maxReconnectGapInSeconds; 46 | 47 | PusherOptions({ 48 | this.auth, 49 | this.cluster, 50 | this.host = "ws.pusherapp.com", 51 | this.wsPort = 80, 52 | this.wssPort = 443, 53 | this.encrypted = true, 54 | this.activityTimeout = 120000, 55 | this.pongTimeout = 30000, 56 | this.maxReconnectionAttempts = 6, 57 | this.maxReconnectGapInSeconds = 30, 58 | }); 59 | 60 | factory PusherOptions.fromJson(Map json) => 61 | _$PusherOptionsFromJson(json); 62 | Map toJson() => _$PusherOptionsToJson(this); 63 | } 64 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/pusher/listeners/FlutterPresenceChannelEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.pusher.listeners 2 | 3 | import com.github.chinloyal.pusher_client.core.utils.Constants 4 | import com.github.chinloyal.pusher_client.pusher.PusherService 5 | import com.pusher.client.channel.PresenceChannelEventListener 6 | import com.pusher.client.channel.PusherEvent 7 | import com.pusher.client.channel.User 8 | import java.lang.Exception 9 | 10 | class FlutterPresenceChannelEventListener: FlutterBaseChannelEventListener(), PresenceChannelEventListener { 11 | companion object { 12 | val instance = FlutterPresenceChannelEventListener() 13 | } 14 | 15 | override fun onUsersInformationReceived(channelName: String, users: MutableSet) { 16 | this.onEvent(PusherEvent(mapOf( 17 | "event" to Constants.SUBSCRIPTION_SUCCEEDED.value, 18 | "channel" to channelName, 19 | "user_id" to null, 20 | "data" to users.toString() 21 | ))) 22 | } 23 | 24 | override fun userUnsubscribed(channelName: String, user: User) { 25 | this.onEvent(PusherEvent(mapOf( 26 | "event" to Constants.MEMBER_REMOVED.value, 27 | "channel" to channelName, 28 | "user_id" to user.id, 29 | "data" to null 30 | ))) 31 | } 32 | 33 | override fun userSubscribed(channelName: String, user: User) { 34 | this.onEvent(PusherEvent(mapOf( 35 | "event" to Constants.MEMBER_ADDED.value, 36 | "channel" to channelName, 37 | "user_id" to user.id, 38 | "data" to null 39 | ))) 40 | } 41 | 42 | override fun onAuthenticationFailure(message: String, e: Exception) { 43 | PusherService.errorLog(message) 44 | if(PusherService.enableLogging) e.printStackTrace() 45 | } 46 | 47 | override fun onSubscriptionSucceeded(channelName: String) { 48 | PusherService.debugLog("[PRESENCE] Subscribed: $channelName") 49 | 50 | this.onEvent(PusherEvent(mapOf( 51 | "event" to Constants.SUBSCRIPTION_SUCCEEDED.value, 52 | "channel" to channelName, 53 | "user_id" to null, 54 | "data" to null 55 | ))) 56 | } 57 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/pusher/listeners/ConnectionListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.pusher.listeners 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import com.github.chinloyal.pusher_client.pusher.PusherService 6 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.debugLog 7 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.enableLogging 8 | import com.github.chinloyal.pusher_client.pusher.PusherService.Companion.errorLog 9 | import com.pusher.client.connection.ConnectionEventListener 10 | import com.pusher.client.connection.ConnectionStateChange 11 | import org.json.JSONObject 12 | import java.lang.Exception 13 | 14 | class ConnectionListener: ConnectionEventListener { 15 | val eventStreamJson = JSONObject(); 16 | 17 | override fun onConnectionStateChange(change: ConnectionStateChange) { 18 | Handler(Looper.getMainLooper()).post { 19 | try { 20 | val connectionStateChange = JSONObject(mapOf( 21 | "currentState" to change.currentState.toString(), 22 | "previousState" to change.previousState.toString() 23 | )) 24 | 25 | debugLog("[${change.currentState.toString()}]") 26 | 27 | eventStreamJson.put("connectionStateChange", connectionStateChange) 28 | 29 | PusherService.eventSink?.success(eventStreamJson.toString()) 30 | } catch (e: Exception) { 31 | e.message?.let { errorLog(it) } 32 | if (enableLogging) { 33 | e.printStackTrace() 34 | } 35 | } 36 | } 37 | } 38 | 39 | override fun onError(message: String, code: String?, ex: Exception?) { 40 | Handler(Looper.getMainLooper()).post { 41 | try { 42 | val connectionError = JSONObject(mapOf( 43 | "message" to message, 44 | "code" to code, 45 | "exception" to ex?.message 46 | )) 47 | 48 | debugLog("[ON_ERROR]: message: $message, code: $code") 49 | 50 | eventStreamJson.put("connectionError", connectionError) 51 | PusherService.eventSink?.success(eventStreamJson.toString()) 52 | } catch (e: Exception) { 53 | e.message?.let { errorLog(it) } 54 | if (PusherService.enableLogging) { 55 | e.printStackTrace() 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pusher_client_example 2 | description: Demonstrates how to use the pusher_client plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.7.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | pusher_client: 16 | # When depending on this package from a real application you should use: 17 | # pusher_client: ^x.y.z 18 | # See https://dart.dev/tools/pub/dependencies#version-constraints 19 | # The example app is bundled with the plugin so we use a path dependency on 20 | # the parent directory to use the current plugin's version. 21 | path: ../ 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.0 26 | 27 | dev_dependencies: 28 | flutter_test: 29 | sdk: flutter 30 | 31 | # For information on the generic Dart part of this file, see the 32 | # following page: https://dart.dev/tools/pub/pubspec 33 | 34 | # The following section is specific to Flutter. 35 | flutter: 36 | 37 | # The following line ensures that the Material Icons font is 38 | # included with your application, so that you can use the icons in 39 | # the material Icons class. 40 | uses-material-design: true 41 | 42 | # To add assets to your application, add an assets section, like this: 43 | # assets: 44 | # - images/a_dot_burr.jpeg 45 | # - images/a_dot_ham.jpeg 46 | 47 | # An image asset can refer to one or more resolution-specific "variants", see 48 | # https://flutter.dev/assets-and-images/#resolution-aware. 49 | 50 | # For details regarding adding assets from package dependencies, see 51 | # https://flutter.dev/assets-and-images/#from-packages 52 | 53 | # To add custom fonts to your application, add a fonts section here, 54 | # in this "flutter" section. Each entry in this list should have a 55 | # "family" key with the font family name, and a "fonts" key with a 56 | # list giving the asset and other descriptors for the font. For 57 | # example: 58 | # fonts: 59 | # - family: Schyler 60 | # fonts: 61 | # - asset: fonts/Schyler-Regular.ttf 62 | # - asset: fonts/Schyler-Italic.ttf 63 | # style: italic 64 | # - family: Trajan Pro 65 | # fonts: 66 | # - asset: fonts/TrajanPro.ttf 67 | # - asset: fonts/TrajanPro_Bold.ttf 68 | # weight: 700 69 | # 70 | # For details regarding fonts from package dependencies, 71 | # see https://flutter.dev/custom-fonts/#from-packages 72 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/Classes/Models.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Models.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/27/20. 6 | // 7 | 8 | struct PusherArgs: Codable { 9 | var appKey: String 10 | var pusherOptions: PusherOptions 11 | var initArgs: InitArgs 12 | } 13 | 14 | struct PusherOptions: Codable { 15 | var auth: PusherAuth? 16 | var cluster: String? 17 | var host: String 18 | var encrypted: Bool 19 | var activityTimeout: Int 20 | var pongTimeout: Int 21 | var wsPort: Int 22 | var wssPort: Int 23 | var maxReconnectionAttempts: Int 24 | var maxReconnectGapInSeconds: Int 25 | } 26 | 27 | struct PusherAuth: Codable { 28 | var endpoint: String 29 | var headers: [String: String] 30 | } 31 | 32 | struct InitArgs: Codable { 33 | var enableLogging: Bool 34 | } 35 | 36 | struct AuthRequest: Codable { 37 | var socket_id: String 38 | var channel_name: String 39 | var valueUrlEncoded: String? 40 | 41 | init(socket_id: String, channel_name: String) { 42 | self.socket_id = socket_id 43 | self.channel_name = channel_name 44 | self.valueUrlEncoded = "socket_id=\(socket_id)&channel_name=\(channel_name)" 45 | } 46 | 47 | private enum CodingKeys: String, CodingKey { 48 | case socket_id, channel_name 49 | } 50 | } 51 | 52 | struct EventStreamResult: Codable { 53 | var connectionStateChange: ConnectionStateChange? 54 | var connectionError: ConnectionError? 55 | var pusherEvent: Event? 56 | init(connectionStateChange: ConnectionStateChange) { 57 | self.connectionStateChange = connectionStateChange 58 | } 59 | 60 | init(connectionError: ConnectionError) { 61 | self.connectionError = connectionError 62 | } 63 | 64 | init(pusherEvent: Event) { 65 | self.pusherEvent = pusherEvent 66 | } 67 | } 68 | 69 | struct ConnectionStateChange: Codable { 70 | var currentState: String 71 | var previousState: String 72 | 73 | init(currentState: String, previousState: String) { 74 | self.currentState = currentState 75 | self.previousState = previousState 76 | } 77 | } 78 | 79 | struct ConnectionError: Codable { 80 | var message: String 81 | var code: String 82 | var exception: String 83 | 84 | init(message: String, code: String, exception: String) { 85 | self.message = message 86 | self.code = code 87 | self.exception = exception 88 | } 89 | } 90 | 91 | struct Event: Codable { 92 | var eventName: String 93 | var channelName: String? 94 | var userId: String?; 95 | var data: String?; 96 | } 97 | 98 | struct ClientEvent: Codable { 99 | var eventName: String 100 | var channelName: String 101 | var data: String?; 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/pusher/channel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:pusher_client/src/contracts/stream_handler.dart'; 6 | import 'package:pusher_client/src/models/event_stream_result.dart'; 7 | import 'package:pusher_client/src/pusher/pusher_event.dart'; 8 | 9 | class Channel extends StreamHandler { 10 | static const MethodChannel _mChannel = 11 | const MethodChannel('com.github.chinloyal/pusher_client'); 12 | static const classId = 'Channel'; 13 | 14 | static Map _eventCallbacks = 15 | Map(); 16 | 17 | final String name; 18 | 19 | Channel(this.name) { 20 | _subscribe(); 21 | } 22 | 23 | void _subscribe() { 24 | _mChannel.invokeMethod('subscribe', { 25 | 'channelName': name, 26 | }); 27 | } 28 | 29 | /// Binds a callback ([onEvent]) to an event. The 30 | /// callback will be notified whenever the specified 31 | /// [eventName] is received on this channel. 32 | Future bind( 33 | String eventName, 34 | void Function(PusherEvent? event) onEvent, 35 | ) async { 36 | registerListener(classId, _eventHandler); 37 | _eventCallbacks[this.name + eventName] = onEvent; 38 | 39 | await _mChannel.invokeMethod('bind', { 40 | 'channelName': this.name, 41 | 'eventName': eventName, 42 | }); 43 | } 44 | 45 | /// Unbinds the callback from the given [eventName], in the scope 46 | /// of the channel being acted upon 47 | Future unbind(String eventName) async { 48 | _eventCallbacks.remove(this.name + eventName); 49 | 50 | await _mChannel.invokeMethod('unbind', { 51 | 'channelName': this.name, 52 | 'eventName': eventName, 53 | }); 54 | } 55 | 56 | /// Once subscribed it is possible to trigger client events on a private 57 | /// channel as long as client events have been activated for the a Pusher 58 | /// application. There are a number of restrictions enforced with client 59 | /// events. For full details see the 60 | /// [documentation](http://pusher.com/docs/client_events) 61 | /// 62 | /// The [eventName] to trigger must have a `client-` prefix. 63 | Future trigger(String eventName, dynamic data) async { 64 | if (!eventName.startsWith('client-')) { 65 | eventName = "client-$eventName"; 66 | } 67 | 68 | await _mChannel.invokeMethod( 69 | 'trigger', 70 | jsonEncode({ 71 | 'eventName': eventName, 72 | 'data': data.toString(), 73 | 'channelName': this.name, 74 | }), 75 | ); 76 | } 77 | 78 | Future _eventHandler(event) async { 79 | var result = EventStreamResult.fromJson(jsonDecode(event.toString())); 80 | 81 | if (result.isPusherEvent) { 82 | var callback = _eventCallbacks[ 83 | result.pusherEvent!.channelName! + result.pusherEvent!.eventName!]; 84 | if (callback != null) { 85 | callback(result.pusherEvent); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /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/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:pusher_client/pusher_client.dart'; 5 | 6 | void main() { 7 | runApp(MyApp()); 8 | } 9 | 10 | class MyApp extends StatefulWidget { 11 | @override 12 | _MyAppState createState() => _MyAppState(); 13 | } 14 | 15 | class _MyAppState extends State { 16 | PusherClient pusher; 17 | Channel channel; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | 23 | String token = getToken(); 24 | 25 | pusher = new PusherClient( 26 | "app-key", 27 | PusherOptions( 28 | // if local on android use 10.0.2.2 29 | host: 'localhost', 30 | encrypted: false, 31 | auth: PusherAuth( 32 | 'http://example.com/broadcasting/auth', 33 | headers: { 34 | 'Authorization': 'Bearer $token', 35 | }, 36 | ), 37 | ), 38 | enableLogging: true, 39 | ); 40 | 41 | channel = pusher.subscribe("private-orders"); 42 | 43 | pusher.onConnectionStateChange((state) { 44 | log("previousState: ${state.previousState}, currentState: ${state.currentState}"); 45 | }); 46 | 47 | pusher.onConnectionError((error) { 48 | log("error: ${error.message}"); 49 | }); 50 | 51 | channel.bind('status-update', (event) { 52 | log(event.data); 53 | }); 54 | 55 | channel.bind('order-filled', (event) { 56 | log("Order Filled Event" + event.data.toString()); 57 | }); 58 | } 59 | 60 | String getToken() => "super-secret-token"; 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | return MaterialApp( 65 | home: Scaffold( 66 | appBar: AppBar( 67 | title: const Text('Example Pusher App'), 68 | ), 69 | body: Center( 70 | child: Column( 71 | children: [ 72 | ElevatedButton( 73 | child: Text('Unsubscribe Private Orders'), 74 | onPressed: () { 75 | pusher.unsubscribe('private-orders'); 76 | }, 77 | ), 78 | ElevatedButton( 79 | child: Text('Unbind Status Update'), 80 | onPressed: () { 81 | channel.unbind('status-update'); 82 | }, 83 | ), 84 | ElevatedButton( 85 | child: Text('Unbind Order Filled'), 86 | onPressed: () { 87 | channel.unbind('order-filled'); 88 | }, 89 | ), 90 | ElevatedButton( 91 | child: Text('Bind Status Update'), 92 | onPressed: () { 93 | channel.bind('status-update', (PusherEvent event) { 94 | log("Status Update Event" + event.data.toString()); 95 | }); 96 | }, 97 | ), 98 | ElevatedButton( 99 | child: Text('Trigger Client Typing'), 100 | onPressed: () { 101 | channel.trigger('client-istyping', {'name': 'Bob'}); 102 | }, 103 | ), 104 | ], 105 | )), 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.10" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.3.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.8.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "1.8.0" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.1.0" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.2.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.2.19" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.3.0" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.1.0" 145 | sdks: 146 | dart: ">=2.12.0 <3.0.0" 147 | flutter: ">=1.20.0" 148 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.0" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.2.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | matcher: 71 | dependency: transitive 72 | description: 73 | name: matcher 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.12.10" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.3.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.8.0" 91 | pusher_client: 92 | dependency: "direct main" 93 | description: 94 | path: ".." 95 | relative: true 96 | source: path 97 | version: "2.0.0" 98 | sky_engine: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.99" 103 | source_span: 104 | dependency: transitive 105 | description: 106 | name: source_span 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.8.0" 110 | stack_trace: 111 | dependency: transitive 112 | description: 113 | name: stack_trace 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.10.0" 117 | stream_channel: 118 | dependency: transitive 119 | description: 120 | name: stream_channel 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "2.1.0" 124 | string_scanner: 125 | dependency: transitive 126 | description: 127 | name: string_scanner 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.1.0" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.2.0" 138 | test_api: 139 | dependency: transitive 140 | description: 141 | name: test_api 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "0.2.19" 145 | typed_data: 146 | dependency: transitive 147 | description: 148 | name: typed_data 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.3.0" 152 | vector_math: 153 | dependency: transitive 154 | description: 155 | name: vector_math 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "2.1.0" 159 | sdks: 160 | dart: ">=2.12.0 <3.0.0" 161 | flutter: ">=1.20.0" 162 | -------------------------------------------------------------------------------- /lib/src/pusher/pusher_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:pusher_client/pusher_client.dart'; 6 | import 'package:pusher_client/src/contracts/stream_handler.dart'; 7 | import 'package:pusher_client/src/models/connection_error.dart'; 8 | import 'package:pusher_client/src/models/connection_state_change.dart'; 9 | import 'package:pusher_client/src/models/event_stream_result.dart'; 10 | import 'package:pusher_client/src/pusher/channel.dart'; 11 | 12 | part 'pusher_client.g.dart'; 13 | 14 | /// This class is the main entry point for accessing Pusher. 15 | /// By creating a new [PusherClient] instance a connection is 16 | /// created automatically unless [autoConnect] is set to false, 17 | /// if auto connect is disabled this means you can call 18 | /// `connect()` at a later point. 19 | class PusherClient extends StreamHandler { 20 | static const MethodChannel _channel = 21 | const MethodChannel('com.github.chinloyal/pusher_client'); 22 | static const classId = 'PusherClient'; 23 | 24 | static PusherClient? _singleton; 25 | void Function(ConnectionStateChange?)? _onConnectionStateChange; 26 | void Function(ConnectionError?)? _onConnectionError; 27 | String? _socketId; 28 | 29 | PusherClient._( 30 | String appKey, 31 | PusherOptions options, { 32 | bool enableLogging = true, 33 | bool autoConnect = true, 34 | }); 35 | 36 | /// Creates a [PusherClient] -- returns the instance if it's already be called. 37 | factory PusherClient( 38 | String appKey, 39 | PusherOptions options, { 40 | bool enableLogging = true, 41 | bool autoConnect = true, 42 | }) { 43 | _singleton ??= PusherClient._( 44 | appKey, 45 | options, 46 | enableLogging: enableLogging, 47 | autoConnect: autoConnect, 48 | ); 49 | 50 | final initArgs = InitArgs(enableLogging: enableLogging); 51 | 52 | _singleton!._init(appKey, options, initArgs); 53 | 54 | if (autoConnect) _singleton!.connect(); 55 | 56 | return _singleton!; 57 | } 58 | 59 | Future _init(String appKey, PusherOptions options, InitArgs initArgs) async { 60 | registerListener(classId, _eventHandler); 61 | await _channel.invokeMethod( 62 | 'init', 63 | jsonEncode({ 64 | 'appKey': appKey, 65 | 'pusherOptions': options, 66 | 'initArgs': initArgs, 67 | }), 68 | ); 69 | } 70 | 71 | /// Subscribes the client to a new channel 72 | /// 73 | /// Note that subscriptions should be registered only once with a Pusher 74 | /// instance. Subscriptions are persisted over disconnection and 75 | /// re-registered with the server automatically on reconnection. This means 76 | /// that subscriptions may also be registered before `connect()` is called, 77 | /// they will be initiated on connection. 78 | Channel subscribe(String channelName) { 79 | return Channel(channelName); 80 | } 81 | 82 | /// Unsubscribes from a channel using the name of the channel. 83 | Future unsubscribe(String channelName) async { 84 | await _channel.invokeMethod('unsubscribe', { 85 | 'channelName': channelName, 86 | }); 87 | } 88 | 89 | /// Initiates a connection attempt using the client's 90 | /// existing connection details 91 | Future connect() async { 92 | await _channel.invokeMethod('connect'); 93 | } 94 | 95 | /// Disconnects the client's connection 96 | Future disconnect() async { 97 | await _channel.invokeMethod('disconnect'); 98 | 99 | cancelEventChannelStream(); 100 | } 101 | 102 | /// The id of the current connection 103 | String? getSocketId() => _socketId; 104 | 105 | /// Callback that is fired whenever the connection state of the 106 | /// connection changes. The state typically changes during connection 107 | /// to Pusher and during disconnection and reconnection. 108 | void onConnectionStateChange( 109 | void Function(ConnectionStateChange? state) callback) { 110 | _onConnectionStateChange = callback; 111 | } 112 | 113 | /// Callback that indicates either: 114 | /// - An error message has been received from Pusher, or 115 | /// - An error has occurred in the client library. 116 | void onConnectionError(void Function(ConnectionError? error) callback) { 117 | _onConnectionError = callback; 118 | } 119 | 120 | Future _eventHandler(event) async { 121 | var result = EventStreamResult.fromJson(jsonDecode(event.toString())); 122 | 123 | if (result.isConnectionStateChange) { 124 | _socketId = await _channel.invokeMethod('getSocketId'); 125 | 126 | if (_onConnectionStateChange != null) 127 | _onConnectionStateChange!(result.connectionStateChange); 128 | } 129 | 130 | if (result.isConnectionError) { 131 | if (_onConnectionError != null) 132 | _onConnectionError!(result.connectionError); 133 | } 134 | } 135 | } 136 | 137 | class InitArgs { 138 | final bool? enableLogging; 139 | 140 | InitArgs({ 141 | this.enableLogging, 142 | }); 143 | 144 | Map toJson() => _$InitArgsToJson(this); 145 | } 146 | -------------------------------------------------------------------------------- /ios/Classes/PusherService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PusherService.swift 3 | // pusher_client 4 | // 5 | // Created by Romario Chinloy on 10/26/20. 6 | // 7 | 8 | import Flutter 9 | import PusherSwiftWithEncryption 10 | 11 | class PusherService: MChannel { 12 | static let CHANNEL_NAME = "com.github.chinloyal/pusher_client" 13 | static let EVENT_STREAM = "com.github.chinloyal/pusher_client_stream" 14 | static let LOG_TAG = "PusherClientPlugin" 15 | static let PRIVATE_PREFIX = "private-" 16 | static let PRIVATE_ENCRYPTED_PREFIX = "private-encrypted-" 17 | static let PRESENCE_PREFIX = "presence-" 18 | 19 | private var _pusherInstance: Pusher! 20 | private var bindedEvents = Dictionary() 21 | 22 | struct Utils { 23 | static var enableLogging = true 24 | static func debugLog(msg: String) { 25 | if(enableLogging) { 26 | debugPrint("D/\(PusherService.LOG_TAG): \(msg)") 27 | } 28 | } 29 | static func errorLog(msg: String) { 30 | if(enableLogging) { 31 | debugPrint("E/\(PusherService.LOG_TAG): \(msg)") 32 | } 33 | } 34 | } 35 | 36 | func register(messenger: FlutterBinaryMessenger) { 37 | let methodChannel = FlutterMethodChannel(name: PusherService.CHANNEL_NAME, binaryMessenger: messenger) 38 | 39 | methodChannel.setMethodCallHandler { (_ call:FlutterMethodCall, result:@escaping FlutterResult) in 40 | switch call.method { 41 | case "init": 42 | self.`init`(call, result: result) 43 | case "connect": 44 | self.connect(result: result) 45 | case "disconnect": 46 | self.disconnect(result: result) 47 | case "getSocketId": 48 | self.getSocketId(result: result) 49 | case "subscribe": 50 | self.subscribe(call, result: result) 51 | case "unsubscribe": 52 | self.unsubscribe(call, result: result) 53 | case "bind": 54 | self.bind(call, result: result) 55 | case "unbind": 56 | self.unbind(call, result: result) 57 | case "trigger": 58 | self.trigger(call, result: result) 59 | default: 60 | result(FlutterMethodNotImplemented) 61 | } 62 | } 63 | 64 | let eventChannel = FlutterEventChannel(name: PusherService.EVENT_STREAM, binaryMessenger: messenger) 65 | 66 | eventChannel.setStreamHandler(StreamHandler.default) 67 | } 68 | 69 | func `init`(_ call:FlutterMethodCall, result:@escaping FlutterResult) { 70 | do { 71 | let json = call.arguments as! String 72 | let pusherArgs: PusherArgs = try JSONDecoder().decode(PusherArgs.self, from: json.data(using: .utf8)!) 73 | 74 | Utils.enableLogging = pusherArgs.initArgs.enableLogging 75 | 76 | if(_pusherInstance == nil) { 77 | let pusherOptions = PusherClientOptions( 78 | authMethod: pusherArgs.pusherOptions.auth == nil ? .noMethod : AuthMethod.authRequestBuilder(authRequestBuilder: AuthRequestBuilder(pusherAuth: pusherArgs.pusherOptions.auth!)), 79 | host: pusherArgs.pusherOptions.cluster != nil ? .cluster(pusherArgs.pusherOptions.cluster!) : .host(pusherArgs.pusherOptions.host), 80 | port: pusherArgs.pusherOptions.encrypted ? pusherArgs.pusherOptions.wssPort : pusherArgs.pusherOptions.wsPort, 81 | useTLS: pusherArgs.pusherOptions.encrypted, 82 | activityTimeout: Double(pusherArgs.pusherOptions.activityTimeout) / 1000 83 | 84 | ) 85 | 86 | _pusherInstance = Pusher(key: pusherArgs.appKey, options: pusherOptions) 87 | _pusherInstance.connection.reconnectAttemptsMax = pusherArgs.pusherOptions.maxReconnectionAttempts 88 | _pusherInstance.connection.maxReconnectGapInSeconds = Double(pusherArgs.pusherOptions.maxReconnectGapInSeconds) 89 | _pusherInstance.connection.pongResponseTimeoutInterval = Double(pusherArgs.pusherOptions.pongTimeout) / 1000 90 | _pusherInstance.connection.delegate = ConnectionListener.default 91 | Utils.debugLog(msg: "Pusher initialized") 92 | 93 | result(nil) 94 | } 95 | } catch let err { 96 | Utils.errorLog(msg: err.localizedDescription) 97 | result(FlutterError(code: "INIT_ERROR", message: err.localizedDescription, details: err)) 98 | } 99 | 100 | } 101 | 102 | func connect(result:@escaping FlutterResult) { 103 | _pusherInstance.connect() 104 | result(nil) 105 | } 106 | 107 | func disconnect(result:@escaping FlutterResult) { 108 | _pusherInstance.disconnect() 109 | Utils.debugLog(msg: "Disconnect") 110 | result(nil) 111 | } 112 | 113 | func getSocketId(result:@escaping FlutterResult) { 114 | result(_pusherInstance.connection.socketId) 115 | } 116 | 117 | func subscribe(_ call:FlutterMethodCall, result:@escaping FlutterResult) { 118 | let channelMap = call.arguments as! [String: String] 119 | let channelName: String = channelMap["channelName"]! 120 | var channel: PusherChannel 121 | 122 | if(!channelName.starts(with: PusherService.PRESENCE_PREFIX)) { 123 | channel = _pusherInstance.subscribe(channelName) 124 | } else { 125 | channel = _pusherInstance.subscribeToPresenceChannel(channelName: channelName) 126 | for pEvent in Constants.PresenceEvents.allCases { 127 | channel.bind(eventName: pEvent.rawValue, eventCallback: ChannelEventListener.default.onEvent) 128 | } 129 | } 130 | 131 | for pEvent in Constants.Events.allCases { 132 | channel.bind(eventName: pEvent.rawValue, eventCallback: ChannelEventListener.default.onEvent) 133 | } 134 | 135 | Utils.debugLog(msg: "Subscribed: \(channelName)") 136 | result(nil) 137 | } 138 | 139 | func unsubscribe(_ call:FlutterMethodCall, result:@escaping FlutterResult) { 140 | let channelMap = call.arguments as! [String: String] 141 | let channelName: String = channelMap["channelName"]! 142 | 143 | _pusherInstance.unsubscribe(channelName) 144 | Utils.debugLog(msg: "Unsubscribed: \(channelName)") 145 | 146 | result(nil) 147 | } 148 | 149 | /** 150 | * Note binding can happen before the channel has been subscribed to 151 | */ 152 | func bind(_ call:FlutterMethodCall, result:@escaping FlutterResult) { 153 | let map = call.arguments as! [String: String] 154 | let channelName: String = map["channelName"]! 155 | let eventName: String = map["eventName"]! 156 | var channel: PusherChannel 157 | 158 | if(!channelName.starts(with: PusherService.PRESENCE_PREFIX)) { 159 | channel = _pusherInstance.connection.channels.find(name: channelName)! 160 | bindedEvents[channelName + eventName] = channel.bind(eventName: eventName, eventCallback: ChannelEventListener.default.onEvent) 161 | } else { 162 | channel = _pusherInstance.connection.channels.findPresence(name: channelName)! 163 | bindedEvents[channelName + eventName] = channel.bind(eventName: eventName, eventCallback: ChannelEventListener.default.onEvent) 164 | } 165 | 166 | Utils.debugLog(msg: "[BIND] \(eventName)") 167 | result(nil) 168 | } 169 | 170 | func unbind(_ call:FlutterMethodCall, result:@escaping FlutterResult) { 171 | let map = call.arguments as! [String: String] 172 | let channelName: String = map["channelName"]! 173 | let eventName: String = map["eventName"]! 174 | var channel: PusherChannel 175 | let callBackId = bindedEvents[channelName + eventName] 176 | 177 | if(callBackId != nil) { 178 | if(!channelName.starts(with: PusherService.PRESENCE_PREFIX)) { 179 | channel = _pusherInstance.connection.channels.find(name: channelName)! 180 | channel.unbind(eventName: eventName, callbackId: callBackId!) 181 | } else { 182 | channel = _pusherInstance.connection.channels.findPresence(name: channelName)! 183 | channel.unbind(eventName: eventName, callbackId: callBackId!) 184 | } 185 | } 186 | 187 | Utils.debugLog(msg: "[UNBIND] \(eventName)") 188 | result(nil) 189 | } 190 | 191 | func trigger(_ call:FlutterMethodCall, result:@escaping FlutterResult) { 192 | do { 193 | let json = call.arguments as! String 194 | let clientEvent: ClientEvent = try JSONDecoder().decode(ClientEvent.self, from: json.data(using: .utf8)!) 195 | let channelName = clientEvent.channelName 196 | let eventName = clientEvent.eventName 197 | let data: String = clientEvent.data ?? "" 198 | let errorMessage = "Trigger can only be called on private and presence channels." 199 | 200 | switch clientEvent.channelName { 201 | case _ where channelName.starts(with: PusherService.PRIVATE_ENCRYPTED_PREFIX): 202 | result(FlutterError(code: "TRIGGER_ERROR", message: errorMessage, details: nil)) 203 | case _ where channelName.starts(with: PusherService.PRIVATE_PREFIX): 204 | let channel: PusherChannel = _pusherInstance.connection.channels.find(name: channelName)! 205 | channel.trigger(eventName: eventName, data: data) 206 | case _ where channelName.starts(with: PusherService.PRESENCE_PREFIX): 207 | let channel: PusherPresenceChannel = _pusherInstance.connection.channels.findPresence(name: channelName)! 208 | channel.trigger(eventName: eventName, data: data) 209 | default: 210 | result(FlutterError(code: "TRIGGER_ERROR", message: errorMessage, details: nil)) 211 | } 212 | 213 | Utils.debugLog(msg: "[TRIGGER] \(eventName)") 214 | result(nil) 215 | } catch let err { 216 | Utils.errorLog(msg: err.localizedDescription) 217 | result(FlutterError(code: "TRIGGER_ERROR", message: err.localizedDescription, details: err)) 218 | } 219 | 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/github/chinloyal/pusher_client/pusher/PusherService.kt: -------------------------------------------------------------------------------- 1 | package com.github.chinloyal.pusher_client.pusher 2 | 3 | import android.util.Log 4 | import com.github.chinloyal.pusher_client.core.contracts.MChannel 5 | import com.github.chinloyal.pusher_client.core.utils.JsonEncodedConnectionFactory 6 | import com.github.chinloyal.pusher_client.pusher.listeners.* 7 | import com.google.gson.Gson 8 | import com.pusher.client.Pusher 9 | import com.pusher.client.PusherOptions 10 | import com.pusher.client.connection.ConnectionState 11 | import com.pusher.client.util.HttpAuthorizer 12 | import com.pusher.client.util.UrlEncodedConnectionFactory 13 | import io.flutter.plugin.common.BinaryMessenger 14 | import io.flutter.plugin.common.EventChannel 15 | import io.flutter.plugin.common.EventChannel.EventSink 16 | import io.flutter.plugin.common.MethodCall 17 | import io.flutter.plugin.common.MethodChannel 18 | import io.flutter.plugin.common.MethodChannel.Result 19 | import org.json.JSONObject 20 | import java.lang.Exception 21 | 22 | 23 | const val CHANNEL_NAME = "com.github.chinloyal/pusher_client" 24 | const val EVENT_STREAM = "com.github.chinloyal/pusher_client_stream" 25 | const val LOG_TAG = "PusherClientPlugin" 26 | const val PRIVATE_PREFIX = "private-" 27 | const val PRIVATE_ENCRYPTED_PREFIX = "private-encrypted-" 28 | const val PRESENCE_PREFIX = "presence-" 29 | 30 | class PusherService : MChannel { 31 | private var _pusherInstance: Pusher? = null 32 | 33 | companion object { 34 | var enableLogging: Boolean = false 35 | var eventSink: EventSink? = null 36 | fun debugLog(msg: String) { 37 | if(enableLogging) { 38 | Log.d(LOG_TAG, msg) 39 | } 40 | } 41 | 42 | fun errorLog(msg: String) { 43 | if(enableLogging) { 44 | Log.e(LOG_TAG, msg) 45 | } 46 | } 47 | } 48 | 49 | override fun register(messenger: BinaryMessenger) { 50 | MethodChannel(messenger, CHANNEL_NAME).setMethodCallHandler { call, result -> 51 | when (call.method) { 52 | "init" -> init(call, result) 53 | "connect" -> connect(result) 54 | "disconnect" -> disconnect(result) 55 | "getSocketId" -> getSocketId(result) 56 | "subscribe" -> subscribe(call, result) 57 | "unsubscribe" -> unsubscribe(call, result) 58 | "bind" -> bind(call, result) 59 | "unbind" -> unbind(call, result) 60 | "trigger" -> trigger(call, result) 61 | else -> result.notImplemented() 62 | } 63 | } 64 | 65 | EventChannel(messenger, EVENT_STREAM).setStreamHandler(object : EventChannel.StreamHandler { 66 | override fun onListen(args: Any?, eventSink: EventSink) { 67 | PusherService.eventSink = eventSink 68 | debugLog("Event stream listening...") 69 | } 70 | 71 | override fun onCancel(args: Any?) { 72 | debugLog("Event stream cancelled.") 73 | } 74 | }) 75 | } 76 | 77 | private fun init(call: MethodCall, result: Result) { 78 | // toString works because this is json encoded in dart 79 | val args = JSONObject(call.arguments.toString()) 80 | val initArgs: JSONObject = args.getJSONObject("initArgs") 81 | enableLogging = initArgs.getBoolean("enableLogging") 82 | 83 | if(_pusherInstance == null) { 84 | val options: JSONObject = args.getJSONObject("pusherOptions") 85 | val pusherOptions = PusherOptions() 86 | 87 | if (!options.isNull("auth")) { 88 | val auth: JSONObject = options.getJSONObject("auth") 89 | val endpoint: String = auth.getString("endpoint") 90 | val headersMap: Map = Gson().fromJson>(auth.getString("headers"), Map::class.java) 91 | val encodedConnectionFactory = if (headersMap.containsValue("application/json")) 92 | JsonEncodedConnectionFactory() else UrlEncodedConnectionFactory() 93 | 94 | val authorizer = HttpAuthorizer(endpoint, encodedConnectionFactory) 95 | authorizer.setHeaders(headersMap) 96 | 97 | pusherOptions.authorizer = authorizer 98 | } 99 | 100 | pusherOptions.setHost(options.getString("host")) 101 | 102 | if(!options.isNull("cluster")) { 103 | pusherOptions.setCluster(options.getString("cluster")) 104 | } 105 | 106 | pusherOptions.activityTimeout = options.getLong("activityTimeout") 107 | pusherOptions.pongTimeout = options.getLong("pongTimeout") 108 | pusherOptions.maxReconnectionAttempts = options.getInt("maxReconnectionAttempts") 109 | pusherOptions.maxReconnectGapInSeconds = options.getInt("maxReconnectGapInSeconds") 110 | pusherOptions.setWsPort(options.getInt("wsPort")) 111 | pusherOptions.setWssPort(options.getInt("wssPort")) 112 | pusherOptions.isUseTLS = options.getBoolean("encrypted") 113 | 114 | _pusherInstance = Pusher(args.getString("appKey"), pusherOptions) 115 | 116 | debugLog("Pusher initialized") 117 | } 118 | 119 | result.success(null) 120 | } 121 | 122 | private fun connect(result: Result) { 123 | _pusherInstance?.connect(ConnectionListener(), ConnectionState.ALL) 124 | result.success(null) 125 | } 126 | 127 | private fun disconnect(result: Result) { 128 | _pusherInstance?.disconnect() 129 | debugLog("Disconnect") 130 | result.success(null) 131 | } 132 | 133 | private fun getSocketId(result: Result) { 134 | result.success(_pusherInstance?.connection?.socketId) 135 | } 136 | 137 | private fun subscribe(call: MethodCall, result: Result) { 138 | try { 139 | val src = call.arguments as Map 140 | val args = JSONObject(src) 141 | val channelName: String = args.getString("channelName") 142 | 143 | when { 144 | channelName.startsWith(PRIVATE_ENCRYPTED_PREFIX) -> { 145 | val channel = _pusherInstance?.getPrivateEncryptedChannel(channelName) 146 | if (channel == null || !channel.isSubscribed) 147 | _pusherInstance?.subscribePrivateEncrypted(channelName, FlutterPrivateEncryptedChannelEventListener.instance) 148 | } 149 | channelName.startsWith(PRIVATE_PREFIX) -> { 150 | val channel = _pusherInstance?.getPrivateChannel(channelName) 151 | if (channel == null || !channel.isSubscribed) 152 | _pusherInstance?.subscribePrivate(channelName, FlutterPrivateChannelEventListener.instance) 153 | } 154 | channelName.startsWith(PRESENCE_PREFIX) -> { 155 | val channel = _pusherInstance?.getPresenceChannel(channelName) 156 | if (channel == null || !channel.isSubscribed) 157 | _pusherInstance?.subscribePresence(channelName, FlutterPresenceChannelEventListener.instance) 158 | } 159 | else -> { 160 | val channel = _pusherInstance?.getChannel(channelName) 161 | if (channel == null || !channel.isSubscribed) 162 | _pusherInstance?.subscribe(channelName, FlutterChannelEventListener.instance) 163 | } 164 | } 165 | 166 | result.success(null) 167 | } catch (e: Exception) { 168 | e.message?.let { errorLog(it) } 169 | if (enableLogging) e.printStackTrace() 170 | result.error("SUBSCRIBE_ERROR", e.message, e) 171 | } 172 | } 173 | 174 | private fun unsubscribe(call: MethodCall, result: Result) { 175 | try { 176 | val src = call.arguments as Map 177 | val args = JSONObject(src); 178 | val channelName = args.getString("channelName") 179 | 180 | _pusherInstance?.unsubscribe(channelName) 181 | 182 | debugLog("Unsubscribed: $channelName") 183 | result.success(null) 184 | } catch (e: Exception) { 185 | e.message?.let { errorLog(it) } 186 | if (enableLogging) e.printStackTrace() 187 | result.error("UNSUBSCRIBE_ERROR", e.message, e) 188 | } 189 | } 190 | 191 | /** 192 | * Note binding can happen before the channel has been subscribed to 193 | */ 194 | private fun bind(call: MethodCall, result: Result) { 195 | try { 196 | val src = call.arguments as Map 197 | val args = JSONObject(src) 198 | val channelName: String = args.getString("channelName") 199 | val eventName: String = args.getString("eventName") 200 | 201 | when { 202 | channelName.startsWith(PRIVATE_ENCRYPTED_PREFIX) -> { 203 | val channel = _pusherInstance?.getPrivateEncryptedChannel(channelName) 204 | channel?.bind(eventName, FlutterPrivateEncryptedChannelEventListener.instance) 205 | } 206 | channelName.startsWith(PRIVATE_PREFIX) -> { 207 | val channel = _pusherInstance?.getPrivateChannel(channelName) 208 | channel?.bind(eventName, FlutterPrivateChannelEventListener.instance) 209 | } 210 | channelName.startsWith(PRESENCE_PREFIX) -> { 211 | val channel = _pusherInstance?.getPresenceChannel(channelName) 212 | channel?.bind(eventName, FlutterPresenceChannelEventListener.instance) 213 | } 214 | else -> { 215 | val channel = _pusherInstance?.getChannel(channelName) 216 | channel?.bind(eventName, FlutterChannelEventListener.instance) 217 | } 218 | } 219 | 220 | debugLog("[BIND] $eventName") 221 | result.success(null) 222 | } catch (e: Exception) { 223 | e.message?.let { errorLog(it) } 224 | if (enableLogging) e.printStackTrace() 225 | result.error("BIND_ERROR", e.message, e) 226 | } 227 | } 228 | 229 | private fun unbind(call: MethodCall, result: Result) { 230 | try { 231 | val src = call.arguments as Map 232 | val args = JSONObject(src) 233 | val channelName: String = args.getString("channelName") 234 | val eventName: String = args.getString("eventName") 235 | 236 | when { 237 | channelName.startsWith(PRIVATE_ENCRYPTED_PREFIX) -> { 238 | val channel = _pusherInstance?.getPrivateEncryptedChannel(channelName) 239 | channel?.unbind(eventName, FlutterPrivateEncryptedChannelEventListener.instance) 240 | } 241 | channelName.startsWith(PRIVATE_PREFIX) -> { 242 | val channel = _pusherInstance?.getPrivateChannel(channelName) 243 | channel?.unbind(eventName, FlutterPrivateChannelEventListener.instance) 244 | } 245 | channelName.startsWith(PRESENCE_PREFIX) -> { 246 | val channel = _pusherInstance?.getPresenceChannel(channelName) 247 | channel?.unbind(eventName, FlutterPresenceChannelEventListener.instance) 248 | } 249 | else -> { 250 | val channel = _pusherInstance?.getChannel(channelName) 251 | channel?.unbind(eventName, FlutterChannelEventListener.instance) 252 | } 253 | } 254 | 255 | debugLog("[UNBIND] $eventName") 256 | result.success(null) 257 | } catch (e: Exception) { 258 | e.message?.let { errorLog(it) } 259 | if (enableLogging) e.printStackTrace() 260 | result.error("UNBIND_ERROR", e.message, e) 261 | } 262 | } 263 | 264 | private fun trigger(call: MethodCall, result: Result) { 265 | try { 266 | // toString works because this is json encoded in dart 267 | val args = JSONObject(call.arguments.toString()) 268 | val eventName: String = args.getString("eventName") 269 | val data: String = args.getString("data") 270 | val channelName: String = args.getString("channelName") 271 | val errorMessage = "Trigger can only be called on private and presence channels." 272 | when { 273 | channelName.startsWith(PRIVATE_ENCRYPTED_PREFIX) -> { 274 | result.error("TRIGGER_ERROR", errorMessage, null) 275 | } 276 | channelName.startsWith(PRIVATE_PREFIX) -> { 277 | val channel = _pusherInstance?.getPrivateChannel(channelName) 278 | channel?.trigger(eventName, data) 279 | } 280 | channelName.startsWith(PRESENCE_PREFIX) -> { 281 | val channel = _pusherInstance?.getPresenceChannel(channelName) 282 | channel?.trigger(eventName, data) 283 | } 284 | else -> result.error("TRIGGER_ERROR", errorMessage, null) 285 | } 286 | 287 | debugLog("[TRIGGER] $eventName") 288 | result.success(null) 289 | } catch (e: Exception) { 290 | e.message?.let { errorLog(it) } 291 | if (enableLogging) e.printStackTrace() 292 | result.error("TRIGGER_ERROR", e.message, e) 293 | } 294 | } 295 | 296 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pusher Channels Flutter Client 2 | 3 | [![pub version](https://img.shields.io/pub/v/pusher_client.svg?logo=dart)](https://pub.dartlang.org/packages/pusher_client) 4 | [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/chinloyal/pusher_client/master/LICENSE) 5 | ![Languages](https://img.shields.io/badge/languages-dart%20%7C%20kotlin%20%7C%20swift-blueviolet.svg) 6 | [![Twitter](https://img.shields.io/badge/twitter-@chinloyal-blue.svg?style=flat&logo=twitter)](https://twitter.com/chinloyal) 7 | 8 | A Pusher Channels client plugin for Flutter targeting Android and iOS. It wraps 9 | [pusher-websocket-java](https://github.com/pusher/pusher-websocket-java) v2.2.5 and [pusher-websocket-swift](https://github.com/pusher/pusher-websocket-swift) v8.0.0. 10 | 11 | For tutorials and more in-depth information about Pusher Channels, visit the [official docs](https://pusher.com/docs/channels). 12 | 13 | This client works with official pusher servers and laravel self hosted pusher websocket server ([laravel-websockets](https://github.com/beyondcode/laravel-websockets)). 14 | 15 | ## Supported Platforms & Deployment Targets 16 | 17 | - Android API 16 and above 18 | - iOS 9.0 and above 19 | 20 | ## Table of Contents 21 | 22 | - [Installation](#installation) 23 | - [Configuration](#configuration) 24 | - [For iOS](#for-ios) 25 | - [For Android](#for-android) 26 | - [API Overview](#api-overview) 27 | - [The Pusher Constructor](#the-pusher-constructor) 28 | - [Pusher Options Config](#pusher-options-config) 29 | - [Reconnecting](#reconnecting) 30 | - [Disconnecting](#disconnecting) 31 | - [Subscribing To Channels](#subscribing-to-channels) 32 | - [Public Channels](#public-channels) 33 | - [Private Channels](#private-channels) 34 | - [Private Encrypted Channels](#private-encrypted-channels) 35 | - [Presence Channels](#presence-channels) 36 | - [Binding To Events](#bindings-to-events) 37 | - [Callback Parameters](#callback-parameters) 38 | - [Unbind Channel Events](#unbind-channel-events) 39 | - [Triggering Client Events](#triggering-client-events) 40 | - [Accessing The Connection Socket ID](#accessing-the-connection-socket-id) 41 | - [Resolve Common Issues](#resolve-common-issues) 42 | ## Installation 43 | 44 | Add to your pubspec.yaml 45 | 46 | ```yaml 47 | dependencies: 48 | pusher_client: ^2.0.0 49 | ``` 50 | 51 | ## Configuration 52 | 53 | ### For iOS 54 | 55 | Set the minimum deployment target in the Podfile to 9.0. Go to `ios/Podfile`, then uncomment this line: 56 | 57 | ```Podfile 58 | # platform :ios, '8.0' 59 | ``` 60 | 61 | Change it to: 62 | 63 | ```Podfile 64 | platform :ios, '9.0' 65 | ``` 66 | 67 | You may have an issue subscribing to private channels if you're using a local pusher server like [laravel-websockets](https://github.com/beyondcode/laravel-websockets), to fix this go to `ios/Runner/Info.plist` and add: 68 | 69 | ```xml 70 | NSAppTransportSecurity 71 | 72 | NSAllowsArbitraryLoads 73 | 74 | 75 | ``` 76 | 77 | If you know which domains you will connect to add: 78 | 79 | ```xml 80 | NSAppTransportSecurity 81 | 82 | NSExceptionDomains 83 | 84 | example.com 85 | 86 | NSExceptionAllowsInsecureHTTPLoads 87 | 88 | NSIncludesSubdomains 89 | 90 | 91 | 92 | 93 | ``` 94 | 95 | ### For Android 96 | 97 | If you have enabled code obfuscation with R8 or proguard, you need to add the following rule in `android/app/build.gradle`: 98 | 99 | ```gradle 100 | buildTypes { 101 | release { 102 | minifyEnabled true 103 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 104 | } 105 | } 106 | ``` 107 | 108 | Then in `android/app/proguard-rules.pro`: 109 | 110 | ```pro 111 | -keep class com.github.chinloyal.pusher_client.** { *; } 112 | ``` 113 | 114 | 115 | ## API Overview 116 | 117 | Here's the API in a nutshell. 118 | 119 | ```dart 120 | PusherOptions options = PusherOptions( 121 | host: 'example.com', 122 | wsPort: 6001, 123 | encrypted: false, 124 | auth: PusherAuth( 125 | 'http://example.com/auth', 126 | headers: { 127 | 'Authorization': 'Bearer $token', 128 | }, 129 | ), 130 | ); 131 | 132 | PusherClient pusher = PusherClient( 133 | YOUR_APP_KEY, 134 | options, 135 | autoConnect: false 136 | ); 137 | 138 | // connect at a later time than at instantiation. 139 | pusher.connect(); 140 | 141 | pusher.onConnectionStateChange((state) { 142 | print("previousState: ${state.previousState}, currentState: ${state.currentState}"); 143 | }); 144 | 145 | pusher.onConnectionError((error) { 146 | print("error: ${error.message}"); 147 | }); 148 | 149 | // Subscribe to a private channel 150 | Channel channel = pusher.subscribe("private-orders"); 151 | 152 | // Bind to listen for events called "order-status-updated" sent to "private-orders" channel 153 | channel.bind("order-status-updated", (PusherEvent event) { 154 | print(event.data); 155 | }); 156 | 157 | // Unsubscribe from channel 158 | pusher.unsubscribe("private-orders"); 159 | 160 | // Disconnect from pusher service 161 | pusher.disconnect(); 162 | 163 | ``` 164 | More information in reference format can be found below. 165 | 166 | 167 | ## The Pusher Constructor 168 | 169 | The constructor takes an application key which you can get from the app's API Access section in the Pusher Channels dashboard, and a pusher options object. 170 | 171 | ```dart 172 | PusherClient pusher = PusherClient(YOUR_APP_KEY, PusherOptions()); 173 | ``` 174 | 175 | 176 | If you are going to use [private](https://pusher.com/docs/channels/using_channels/private-channels), [presence](https://pusher.com/docs/channels/using_channels/presence-channels) or [encrypted](https://pusher.com/docs/channels/using_channels/encrypted-channels) channels then you will need to provide a `PusherAuth` to be used when authenticating subscriptions. In order to do this you need to pass in a `PusherOptions` object which has had an `auth` set. 177 | 178 | ```dart 179 | PusherAuth auth = PusherAuth( 180 | // for auth endpoint use full url 181 | 'http://example.com/auth', 182 | headers: { 183 | 'Authorization': 'Bearer $token', 184 | }, 185 | ); 186 | 187 | PusherOptions options = PusherOptions( 188 | auth: auth 189 | ); 190 | 191 | PusherClient pusher = PusherClient(YOUR_APP_KEY, options); 192 | ``` 193 | 194 | To disable logging and auto connect do this: 195 | 196 | ```dart 197 | PusherClient pusher = PusherClient( 198 | YOUR_APP_KEY, 199 | options, 200 | enableLogging: false, 201 | autoConnect: false, 202 | ); 203 | ``` 204 | 205 | If auto connect is disabled then you can manually connect using `connect()` on the pusher instance. 206 | 207 | ## Pusher Options Config 208 | 209 | Most of the functionality of this plugin is configured through the PusherOptions object. You configure it by setting parameters on the object before passing it to the Pusher client. Below is a table containing all of the properties you can set. 210 | 211 | | Method | Parameter | Description | 212 | |-----------------------------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| 213 | | encrypted | bool | Whether the connection should be made with TLS or not. | 214 | | auth | PusherAuth | Sets the authorization options to be used when authenticating private, private-encrypted and presence channels. | 215 | | host | String | The host to which connections will be made. | 216 | | wsPort | int | The port to which unencrypted connections will be made. Automatically set correctly. | 217 | | wssPort | int | The port to which encrypted connections will be made. Automatically set correctly. | 218 | | cluster | String | Sets the cluster the client will connect to, thereby setting the Host and Port correctly. | 219 | | activityTimeout | int | The number of milliseconds of inactivity at which a "ping" will be triggered to check the connection. The default value is 120,000. | 220 | | pongTimeout | int | The number of milliseconds the client waits to receive a "pong" response from the server before disconnecting. The default value is 30,000. | 221 | | maxReconnectionAttempts | int | Number of reconnection attempts that will be made when `pusher.connect()` is called, after which the client will give up. | 222 | | maxReconnectGapInSeconds | int | The delay in two reconnection extends exponentially (1, 2, 4, .. seconds) This property sets the maximum inbetween two reconnection attempts. | 223 | 224 | ## Reconnecting 225 | 226 | The `connect()` method is also used to re-connect in case the connection has been lost, for example if a device loses reception. Note that the state of channel subscriptions and event bindings will be preserved while disconnected and re-negotiated with the server once a connection is re-established. 227 | 228 | ## Disconnecting 229 | 230 | ```dart 231 | pusher.disconnect(); 232 | ``` 233 | 234 | After disconnection the `PusherClient` instance will release any internally allocated resources (threads and network connections) 235 | 236 | ## Subscribing To Channels 237 | 238 | Channels use the concept of [channels](https://pusher.com/docs/channels/using_channels/channels) as a way of subscribing to data. They are identified and subscribed to by a simple name. Events are bound to a channel and are also identified by name. 239 | 240 | As mentioned above, channel subscriptions need only be registered once by the `PusherClient` instance. They are preserved across disconnection and re-established with the server on reconnect. They should NOT be re-registered. They may, however, be registered with a `PusherClient` instance before the first call to `connect` - they will be completed with the server as soon as a connection becomes available. 241 | 242 | ### Public Channels 243 | 244 | The default method for subscribing to a channel involves invoking the subscribe method of your client object: 245 | 246 | ```dart 247 | Channel channel = pusher.subscribe("my-channel"); 248 | ``` 249 | This returns a `Channel` object, which events can be bound to. 250 | 251 | ### Private Channels 252 | 253 | Private channels are created in exactly the same way as public channels, except that they reside in the _'private-'_ namespace. This means prefixing the channel name: 254 | 255 | ```dart 256 | Channel privateChannel = pusher.subscribe("private-status-update"); 257 | ``` 258 | 259 | Subscribing to private channels involves the client being authenticated. See [The Pusher Constructor](#the-pusher-constructor) section for the authenticated channel example for more information. 260 | 261 | ### Private Encrypted Channels 262 | 263 | Similar to Private channels, you can also subscribe to a private encrypted channel. This plugin fully supports end-to-end encryption. This means that only you and your connected clients will be able to read your messages. Pusher cannot decrypt them. These channels must be prefixed with _'private-encrypted-'_ 264 | 265 | Like with private channels, you must provide an authentication endpoint. That endpoint must be using a [server client that supports end-to-end encryption](https://pusher.com/docs/channels/using_channels/encrypted-channels#server). There is a [demonstration endpoint to look at using nodejs](https://github.com/pusher/pusher-channels-auth-example#using-e2e-encryption). 266 | 267 | ### Presence Channels 268 | 269 | Presence channels are channels whose names are prefixed by _'presence-'_. Presence channels also need to be authenticated. 270 | 271 | ```dart 272 | Channel presenceChannel = pusher.subscribe("presence-another-channel"); 273 | ``` 274 | 275 | ## Binding To Events 276 | 277 | There are two types of events that occur on channel subscriptions. 278 | 279 | 1. Protocol related events such as those triggered when a subscription succeeds, for example "pusher:subscription_succeeded" 280 | 2. Application events that have been triggered by code within your app 281 | 282 | ```dart 283 | Channel channel = pusher.subscribe("private-orders"); 284 | 285 | channel.bind("order-status-updated", (PusherEvent event) { 286 | print(event.data); 287 | }); 288 | ``` 289 | 290 | ### Callback Parameters 291 | 292 | The callbacks you bind receive a `PusherEvent`: 293 | 294 | | Property | Type | Description | 295 | | ------------------ |--------------| ------------ 296 | | `eventName` | `String` | The name of the event. | 297 | | `channelName` | `String` | The name of the channel that the event was triggered on. (Optional) | 298 | | `data` | `String` | The data that was passed to `trigger`, encoded as a string. If you passed an object then that will have been serialized to a JSON string which you can parse as necessary. (Optional)| 299 | | `userId` | `String` | The ID of the user who triggered the event. This is only available for client events triggered on presence channels. (Optional)| 300 | 301 | ### Unbind Channel Events 302 | 303 | You can unbind from an event by doing: 304 | 305 | ```dart 306 | channel.unbind("order-status-updated"); 307 | ``` 308 | 309 | ## Triggering Client Events 310 | 311 | Once a private or presence subscription has been authorized and the subscription has succeeded, it is possible to trigger events on those channels. 312 | 313 | Events triggered by clients are called [client events](https://pusher.com/docs/channels/using_channels/events#triggering-client-events). Because they are being triggered from a client which may not be trusted there are a number of enforced rules when using them. Some of these rules include: 314 | 315 | - Event names must have a _'client-'_ prefix 316 | - Rate limits 317 | - You can only trigger an event when the subscription has succeeded 318 | 319 | ```dart 320 | channel.bind("pusher:subscription_succeeded", (PusherEvent event) { 321 | channel.trigger("client-istyping", {"name": "Bob"}); 322 | }); 323 | ``` 324 | 325 | For full details see the [client events documentation](https://pusher.com/docs/channels/using_channels/events#triggering-client-events). 326 | 327 | 328 | ## Accessing The Connection Socket ID 329 | 330 | Once connected you can access a unique identifier for the current client's connection. This is known as the **socket Id**. You can access the value once the connection has been established as follows: 331 | 332 | ```dart 333 | String socketId = pusher.getSocketId(); 334 | ``` 335 | 336 | For more information on how and why there is a socket Id see the documentation on authenticating users and [excluding recipients](https://pusher.com/docs/channels/server_api/excluding-event-recipients). 337 | 338 | ## Resolve Common Issues 339 | 340 | ### iOS doesn't log when enableLogging is set to true 341 | 342 | iOS logging doesn't seem to output to flutter console, however if you run the 343 | app from Xcode you should be able to see the logs. 344 | 345 | 346 | ### Subscribing to private channels with iOS 347 | 348 | If using a local pusher server but are unable to subscribe to a private channel 349 | then add this to your ios/Runner/Info.plist: 350 | 351 | ```xml 352 | NSAppTransportSecurity 353 | 354 | NSAllowsArbitraryLoads 355 | 356 | 357 | ``` 358 | 359 | If you know which domains you will connect to add: 360 | 361 | ```xml 362 | NSAppTransportSecurity 363 | 364 | NSExceptionDomains 365 | 366 | example.com 367 | 368 | NSExceptionAllowsInsecureHTTPLoads 369 | 370 | NSIncludesSubdomains 371 | 372 | 373 | 374 | 375 | ``` 376 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | 97F131E2C83FBF01E22B026A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3B8EFF3B144BD3E0F2EBA3A /* Pods_Runner.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 36 | 4E31BE8264BCB21079612752 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 37 | 586BC06C935CDA1810AF478C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 38 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 39 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | A3B8EFF3B144BD3E0F2EBA3A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | A43B87E7D061F4DC292BA84B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 97F131E2C83FBF01E22B026A /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 228725705900A23A2E532A52 /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | A3B8EFF3B144BD3E0F2EBA3A /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 3785AD25C8DE0DDC8BBCB2E4 /* Pods */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 4E31BE8264BCB21079612752 /* Pods-Runner.debug.xcconfig */, 76 | A43B87E7D061F4DC292BA84B /* Pods-Runner.release.xcconfig */, 77 | 586BC06C935CDA1810AF478C /* Pods-Runner.profile.xcconfig */, 78 | ); 79 | path = Pods; 80 | sourceTree = ""; 81 | }; 82 | 9740EEB11CF90186004384FC /* Flutter */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 86 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 87 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 88 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 89 | ); 90 | name = Flutter; 91 | sourceTree = ""; 92 | }; 93 | 97C146E51CF9000F007C117D = { 94 | isa = PBXGroup; 95 | children = ( 96 | 9740EEB11CF90186004384FC /* Flutter */, 97 | 97C146F01CF9000F007C117D /* Runner */, 98 | 97C146EF1CF9000F007C117D /* Products */, 99 | 3785AD25C8DE0DDC8BBCB2E4 /* Pods */, 100 | 228725705900A23A2E532A52 /* Frameworks */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 97C146EF1CF9000F007C117D /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 97C146EE1CF9000F007C117D /* Runner.app */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | 97C146F01CF9000F007C117D /* Runner */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 116 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 117 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 118 | 97C147021CF9000F007C117D /* Info.plist */, 119 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 120 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 121 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 122 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 123 | ); 124 | path = Runner; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 97C146ED1CF9000F007C117D /* Runner */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 133 | buildPhases = ( 134 | DC132D9F9284A0E5E9F02590 /* [CP] Check Pods Manifest.lock */, 135 | 9740EEB61CF901F6004384FC /* Run Script */, 136 | 97C146EA1CF9000F007C117D /* Sources */, 137 | 97C146EB1CF9000F007C117D /* Frameworks */, 138 | 97C146EC1CF9000F007C117D /* Resources */, 139 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 140 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 141 | C3B00070C266A1CE0C53ECC3 /* [CP] Embed Pods Frameworks */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = Runner; 148 | productName = Runner; 149 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 97C146E61CF9000F007C117D /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastUpgradeCheck = 1200; 159 | ORGANIZATIONNAME = ""; 160 | TargetAttributes = { 161 | 97C146ED1CF9000F007C117D = { 162 | CreatedOnToolsVersion = 7.3.1; 163 | LastSwiftMigration = 1100; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 168 | compatibilityVersion = "Xcode 9.3"; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = 97C146E51CF9000F007C117D; 176 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | 97C146ED1CF9000F007C117D /* Runner */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | 97C146EC1CF9000F007C117D /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 191 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 192 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 193 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXShellScriptBuildPhase section */ 200 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputPaths = ( 206 | ); 207 | name = "Thin Binary"; 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 213 | }; 214 | 9740EEB61CF901F6004384FC /* Run Script */ = { 215 | isa = PBXShellScriptBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | inputPaths = ( 220 | ); 221 | name = "Run Script"; 222 | outputPaths = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | shellPath = /bin/sh; 226 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 227 | }; 228 | C3B00070C266A1CE0C53ECC3 /* [CP] Embed Pods Frameworks */ = { 229 | isa = PBXShellScriptBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | inputFileListPaths = ( 234 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 235 | ); 236 | name = "[CP] Embed Pods Frameworks"; 237 | outputFileListPaths = ( 238 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | shellPath = /bin/sh; 242 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 243 | showEnvVarsInLog = 0; 244 | }; 245 | DC132D9F9284A0E5E9F02590 /* [CP] Check Pods Manifest.lock */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputFileListPaths = ( 251 | ); 252 | inputPaths = ( 253 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 254 | "${PODS_ROOT}/Manifest.lock", 255 | ); 256 | name = "[CP] Check Pods Manifest.lock"; 257 | outputFileListPaths = ( 258 | ); 259 | outputPaths = ( 260 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | shellPath = /bin/sh; 264 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 265 | showEnvVarsInLog = 0; 266 | }; 267 | /* End PBXShellScriptBuildPhase section */ 268 | 269 | /* Begin PBXSourcesBuildPhase section */ 270 | 97C146EA1CF9000F007C117D /* Sources */ = { 271 | isa = PBXSourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 275 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXSourcesBuildPhase section */ 280 | 281 | /* Begin PBXVariantGroup section */ 282 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 283 | isa = PBXVariantGroup; 284 | children = ( 285 | 97C146FB1CF9000F007C117D /* Base */, 286 | ); 287 | name = Main.storyboard; 288 | sourceTree = ""; 289 | }; 290 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 291 | isa = PBXVariantGroup; 292 | children = ( 293 | 97C147001CF9000F007C117D /* Base */, 294 | ); 295 | name = LaunchScreen.storyboard; 296 | sourceTree = ""; 297 | }; 298 | /* End PBXVariantGroup section */ 299 | 300 | /* Begin XCBuildConfiguration section */ 301 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ALWAYS_SEARCH_USER_PATHS = NO; 305 | CLANG_ANALYZER_NONNULL = YES; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 311 | CLANG_WARN_BOOL_CONVERSION = YES; 312 | CLANG_WARN_COMMA = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | SUPPORTED_PLATFORMS = iphoneos; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | VALIDATE_PRODUCT = YES; 349 | }; 350 | name = Profile; 351 | }; 352 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 353 | isa = XCBuildConfiguration; 354 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | CLANG_ENABLE_MODULES = YES; 358 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 359 | ENABLE_BITCODE = NO; 360 | FRAMEWORK_SEARCH_PATHS = ( 361 | "$(inherited)", 362 | "$(PROJECT_DIR)/Flutter", 363 | ); 364 | INFOPLIST_FILE = Runner/Info.plist; 365 | LD_RUNPATH_SEARCH_PATHS = ( 366 | "$(inherited)", 367 | "@executable_path/Frameworks", 368 | ); 369 | LIBRARY_SEARCH_PATHS = ( 370 | "$(inherited)", 371 | "$(PROJECT_DIR)/Flutter", 372 | ); 373 | PRODUCT_BUNDLE_IDENTIFIER = com.github.chinloyal.pusherClientExample; 374 | PRODUCT_NAME = "$(TARGET_NAME)"; 375 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 376 | SWIFT_VERSION = 5.0; 377 | VERSIONING_SYSTEM = "apple-generic"; 378 | }; 379 | name = Profile; 380 | }; 381 | 97C147031CF9000F007C117D /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ALWAYS_SEARCH_USER_PATHS = NO; 385 | CLANG_ANALYZER_NONNULL = YES; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 391 | CLANG_WARN_BOOL_CONVERSION = YES; 392 | CLANG_WARN_COMMA = YES; 393 | CLANG_WARN_CONSTANT_CONVERSION = YES; 394 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 396 | CLANG_WARN_EMPTY_BODY = YES; 397 | CLANG_WARN_ENUM_CONVERSION = YES; 398 | CLANG_WARN_INFINITE_RECURSION = YES; 399 | CLANG_WARN_INT_CONVERSION = YES; 400 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 401 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 402 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 404 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 405 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 406 | CLANG_WARN_STRICT_PROTOTYPES = YES; 407 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 411 | COPY_PHASE_STRIP = NO; 412 | DEBUG_INFORMATION_FORMAT = dwarf; 413 | ENABLE_STRICT_OBJC_MSGSEND = YES; 414 | ENABLE_TESTABILITY = YES; 415 | GCC_C_LANGUAGE_STANDARD = gnu99; 416 | GCC_DYNAMIC_NO_PIC = NO; 417 | GCC_NO_COMMON_BLOCKS = YES; 418 | GCC_OPTIMIZATION_LEVEL = 0; 419 | GCC_PREPROCESSOR_DEFINITIONS = ( 420 | "DEBUG=1", 421 | "$(inherited)", 422 | ); 423 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 424 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 425 | GCC_WARN_UNDECLARED_SELECTOR = YES; 426 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 427 | GCC_WARN_UNUSED_FUNCTION = YES; 428 | GCC_WARN_UNUSED_VARIABLE = YES; 429 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 430 | MTL_ENABLE_DEBUG_INFO = YES; 431 | ONLY_ACTIVE_ARCH = YES; 432 | SDKROOT = iphoneos; 433 | TARGETED_DEVICE_FAMILY = "1,2"; 434 | }; 435 | name = Debug; 436 | }; 437 | 97C147041CF9000F007C117D /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | ALWAYS_SEARCH_USER_PATHS = NO; 441 | CLANG_ANALYZER_NONNULL = YES; 442 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 443 | CLANG_CXX_LIBRARY = "libc++"; 444 | CLANG_ENABLE_MODULES = YES; 445 | CLANG_ENABLE_OBJC_ARC = YES; 446 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 447 | CLANG_WARN_BOOL_CONVERSION = YES; 448 | CLANG_WARN_COMMA = YES; 449 | CLANG_WARN_CONSTANT_CONVERSION = YES; 450 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 451 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 452 | CLANG_WARN_EMPTY_BODY = YES; 453 | CLANG_WARN_ENUM_CONVERSION = YES; 454 | CLANG_WARN_INFINITE_RECURSION = YES; 455 | CLANG_WARN_INT_CONVERSION = YES; 456 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 457 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 458 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 459 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 460 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 461 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 462 | CLANG_WARN_STRICT_PROTOTYPES = YES; 463 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 464 | CLANG_WARN_UNREACHABLE_CODE = YES; 465 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 466 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 467 | COPY_PHASE_STRIP = NO; 468 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 469 | ENABLE_NS_ASSERTIONS = NO; 470 | ENABLE_STRICT_OBJC_MSGSEND = YES; 471 | GCC_C_LANGUAGE_STANDARD = gnu99; 472 | GCC_NO_COMMON_BLOCKS = YES; 473 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 474 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 475 | GCC_WARN_UNDECLARED_SELECTOR = YES; 476 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 477 | GCC_WARN_UNUSED_FUNCTION = YES; 478 | GCC_WARN_UNUSED_VARIABLE = YES; 479 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 480 | MTL_ENABLE_DEBUG_INFO = NO; 481 | SDKROOT = iphoneos; 482 | SUPPORTED_PLATFORMS = iphoneos; 483 | SWIFT_COMPILATION_MODE = wholemodule; 484 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 485 | TARGETED_DEVICE_FAMILY = "1,2"; 486 | VALIDATE_PRODUCT = YES; 487 | }; 488 | name = Release; 489 | }; 490 | 97C147061CF9000F007C117D /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 493 | buildSettings = { 494 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 495 | CLANG_ENABLE_MODULES = YES; 496 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 497 | ENABLE_BITCODE = NO; 498 | FRAMEWORK_SEARCH_PATHS = ( 499 | "$(inherited)", 500 | "$(PROJECT_DIR)/Flutter", 501 | ); 502 | INFOPLIST_FILE = Runner/Info.plist; 503 | LD_RUNPATH_SEARCH_PATHS = ( 504 | "$(inherited)", 505 | "@executable_path/Frameworks", 506 | ); 507 | LIBRARY_SEARCH_PATHS = ( 508 | "$(inherited)", 509 | "$(PROJECT_DIR)/Flutter", 510 | ); 511 | PRODUCT_BUNDLE_IDENTIFIER = com.github.chinloyal.pusherClientExample; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 514 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 515 | SWIFT_VERSION = 5.0; 516 | VERSIONING_SYSTEM = "apple-generic"; 517 | }; 518 | name = Debug; 519 | }; 520 | 97C147071CF9000F007C117D /* Release */ = { 521 | isa = XCBuildConfiguration; 522 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 523 | buildSettings = { 524 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 525 | CLANG_ENABLE_MODULES = YES; 526 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 527 | ENABLE_BITCODE = NO; 528 | FRAMEWORK_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "$(PROJECT_DIR)/Flutter", 531 | ); 532 | INFOPLIST_FILE = Runner/Info.plist; 533 | LD_RUNPATH_SEARCH_PATHS = ( 534 | "$(inherited)", 535 | "@executable_path/Frameworks", 536 | ); 537 | LIBRARY_SEARCH_PATHS = ( 538 | "$(inherited)", 539 | "$(PROJECT_DIR)/Flutter", 540 | ); 541 | PRODUCT_BUNDLE_IDENTIFIER = com.github.chinloyal.pusherClientExample; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 544 | SWIFT_VERSION = 5.0; 545 | VERSIONING_SYSTEM = "apple-generic"; 546 | }; 547 | name = Release; 548 | }; 549 | /* End XCBuildConfiguration section */ 550 | 551 | /* Begin XCConfigurationList section */ 552 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | 97C147031CF9000F007C117D /* Debug */, 556 | 97C147041CF9000F007C117D /* Release */, 557 | 249021D3217E4FDB00AE95B9 /* Profile */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 97C147061CF9000F007C117D /* Debug */, 566 | 97C147071CF9000F007C117D /* Release */, 567 | 249021D4217E4FDB00AE95B9 /* Profile */, 568 | ); 569 | defaultConfigurationIsVisible = 0; 570 | defaultConfigurationName = Release; 571 | }; 572 | /* End XCConfigurationList section */ 573 | }; 574 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 575 | } 576 | --------------------------------------------------------------------------------