Skip to content

Commit

Permalink
Add futures, error stream, proper timeout passing, fire disconnect on…
Browse files Browse the repository at this point in the history
… initial connect error, and more (#57)
  • Loading branch information
FZambia authored Nov 19, 2021
1 parent 77d4c2f commit cd7ce96
Show file tree
Hide file tree
Showing 34 changed files with 727 additions and 286 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## [0.8.0]

Version 0.8.0 is the next iteration of `centrifuge-dart` development. It pushes client closer to other clients in the ecosystem. It also **contains several backwards incompatible changes**.

* Return Futures from `Client.connect`, `Client.disconnect`, `Subscription.subscribe`, `Subscription.unsubscribe` methods - addresses [#31](https://github.com/centrifugal/centrifuge-dart/issues/31).
* On initial connect fire `DisconnectEvent` on connection error - this makes behavior of `centrifuge-dart` similar to all other our clients - addresses [#56](https://github.com/centrifugal/centrifuge-dart/issues/56).
* Add client error stream to consume `ErrorEvent` - each transport failure will emit error to this stream - addresses [#56](https://github.com/centrifugal/centrifuge-dart/issues/56).
* Refactor subscription statuses - add `subscribing` and `error` statuses. This change is mostly internal should not affect working with Subscriptions.
* Do not call `UnsubscribeEvent` if subscription is not successfully subscribed (i.e. in `subscribed` state). This makes behavior of `centrifuge-dart` similar to all other our clients.
* Update disconnect reasons due to failed connection and calling `Client.Disconnect` method - make it more similar to all other connector libraries in ecosystem.
* Add default transport timeout (10 sec) – on connect and subscribe timeouts client will auto reconnect, calls like publish, history, rpc can now throw `TimeoutException`. Also - properly pass timeout to the transport (was not before!). Again – this makes client behave similarly to all other connectors.
* Add `presence` and `presenceStats` methods for Subscription and on client top level (for server-side subscriptions).
* Support `streamPosition` in `SubscribeSuccessEvent`.
* Support `streamPosition` in `ServerSubscribeEvent`.
* Support `data` in `ServerSubscribeEvent`.
* Implement `send` method to send async messages to a server.
* Fix deletion during iteration over map when working with server-side subscriptions.
* Better event String representations.
* Improvements and fixes in examples.

## [0.7.1]

* Add support for `data` in `SubscribeSuccessEvent`. This is a custom data which can be sent by a server towards client connection in subscribe result. Note that due to the bug in Centrifugo server this feature only works in Centrifugo >= v3.0.3.
Expand Down
68 changes: 45 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
[![Build Status](https://travis-ci.org/centrifugal/centrifuge-dart.svg?branch=master)](https://travis-ci.org/centrifugal/centrifuge-dart)
[![Coverage Status](https://coveralls.io/repos/github/centrifugal/centrifuge-dart/badge.svg?branch=master)](https://coveralls.io/github/centrifugal/centrifuge-dart?branch=master)

This repo contains a Dart connector library to communicate with Centrifugo server or a server based on Centrifuge library for Go language. This client uses WebSocket transport with binary Protobuf protocol format for Centrifuge protocol message encoding. See feature matrix below to find out which protocol features are supported here at the moment.

## Example

Examples:
* `example\flutter_app` simple chat application
* `example\console` simple console application
* `example\chat_app` one more chat example
* `example\console` simple console application
* `example\console_server_subs` demonstrates working with server-side subscriptions

## Usage

Create client:
Create a client instance:

```dart
import 'package:centrifuge/centrifuge.dart' as centrifuge;
final client = centrifuge.createClient("ws://localhost:8000/connection/websocket?format=protobuf");
```

**Note that** `?format=protobuf` **is required because this library only works with Protobuf protocol.** While this client uses binary Protobuf protocol internally nothing stops you from sending JSON-encoded data over it.
**Note that using** `?format=protobuf` **is required for Centrifugo < v3 and can be skipped for later versions**.

Centrifuge-dart uses binary Protobuf protocol internally but nothing stops you from sending JSON-encoded data over it. Our examples demonstrate this.

Connect to a server:

Connect to server:
```dart
client.connect();
await client.connect();
```

Note that `.connect()` method is asynchronous. This means that client will be properly connected and authenticated on server at some point in future. To handle connect and disconnect events you can listen to `connectStream` and `disconnectStream`:
To handle connect and disconnect events you can listen to `connectStream` and `disconnectStream`:

```dart
client.connectStream.listen(onEvent);
client.disconnectStream.listen(onEvent);
client.connect();
await client.connect();
```

Connect and disconnect events can happen many times throughout client lifetime.

Subscribe to channel:
Subscribe to a channel:

```dart
final subscription = client.getSubscription(channel);
Expand All @@ -46,16 +52,30 @@ subscription.subscribeSuccessStream.listen(onEvent);
subscription.subscribeErrorStream.listen(onEvent);
subscription.unsubscribeStream.listen(onEvent);
subscription.subscribe();
await subscription.subscribe();
```

Publish:
Publish to a channel:

```dart
final output = jsonEncode({'input': message});
final data = utf8.encode(output);
final data = utf8.encode(jsonEncode({'input': message}));
await subscription.publish(data);
```

When using server-side subscriptions you don't need to create Subscription instances, just set appropriate event handlers on `Client` instance:

```dart
client.connectStream.listen(onEvent);
client.disconnectStream.listen(onEvent);
client.subscribeStream.listen(onEvent);
client.publishStream.listen(onEvent);
await client.connect();
```

## Usage in background

When mobile application goes to background there are many OS-specific limitations for established persistent connections. Thus in most cases you need to disconnect from a server when app moves to background and connect again when app goes to foreground.

## Feature matrix

- [ ] connect to server using JSON protocol format
Expand All @@ -69,18 +89,17 @@ await subscription.publish(data);
- [x] subscribe on channel and handle asynchronous Publications
- [x] handle Join and Leave messages
- [x] handle Unsubscribe notifications
- [ ] reconnect on subscribe timeout
- [x] reconnect on subscribe timeout
- [x] publish method of Subscription
- [x] unsubscribe method of Subscription
- [ ] presence method of Subscription
- [ ] presence stats method of Subscription
- [x] presence method of Subscription
- [x] presence stats method of Subscription
- [x] history method of Subscription
- [x] top-level publish method
- [ ] top-level presence method
- [ ] top-level presence stats method
- [ ] top-level history method
- [ ] top-level unsubscribe method
- [ ] send asynchronous messages to server
- [x] top-level presence method
- [x] top-level presence stats method
- [x] top-level history method
- [x] send asynchronous messages to server
- [x] handle asynchronous messages from server
- [x] send RPC commands
- [x] subscribe to private channels with token (JWT)
Expand All @@ -93,15 +112,18 @@ await subscription.publish(data);
- [x] server-side subscriptions
- [x] message recovery mechanism for server-side subscriptions
- [x] history stream pagination
- [ ] subscribe from the known StreamPosition

## Instructions for maintainers/contributors

## Instructions to update protobuf
### How to update protobuf definitions

1) Install `protoc` compiler
2) Install `protoc_plugin` https://pub.dev/packages/protoc_plugin (`dart pub global activate protoc_plugin`)
3) cd `lib/src/proto` and run `protoc --dart_out=. -I . client.proto`
4) cd to root and run `dartfmt -w lib/ test/` (install dartfmt with `dart pub global activate dart_style`)

## Instructions to release
### How to release

1) Update changelog
2) Bump version in `pubspec.yaml`, push, create new tag
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />

<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
18 changes: 18 additions & 0 deletions example/chat_app/android/app/src/main/res/values-night/styles.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
2 changes: 2 additions & 0 deletions example/chat_app/ios/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
Expand All @@ -18,6 +19,7 @@ Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Expand Down
4 changes: 2 additions & 2 deletions example/chat_app/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
Expand All @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>
41 changes: 41 additions & 0 deletions example/chat_app/ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}

def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
use_frameworks!
use_modular_headers!

flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
29 changes: 29 additions & 0 deletions example/chat_app/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
PODS:
- Flutter (1.0.0)
- fluttertoast (0.0.2):
- Flutter
- Toast
- Toast (4.0.0)

DEPENDENCIES:
- Flutter (from `Flutter`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)

SPEC REPOS:
trunk:
- Toast

EXTERNAL SOURCES:
Flutter:
:path: Flutter
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"

SPEC CHECKSUMS:
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196

PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c

COCOAPODS: 1.10.1
Loading

0 comments on commit cd7ce96

Please sign in to comment.