Skip to content

Commit

Permalink
v0.15.0 development (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
FZambia authored Jan 26, 2025
1 parent 1649aa3 commit fb29860
Show file tree
Hide file tree
Showing 24 changed files with 1,476 additions and 950 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ jobs:
build:
# Prevent duplicate builds on internal PRs.
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
environment: test_ci
runs-on: ubuntu-latest
strategy:
matrix:
sdk: [2.19, stable, beta]
sdk: [3.4, stable, beta] # note, not testing on 3.3 due to build_runner CI dependency problem.
steps:
- uses: actions/checkout@v4

Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Publish to pub.dev

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'

jobs:
publish:
permissions:
id-token: write # Required for authentication using OIDC
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Websocket client for [Centrifugo](https://github.com/centrifugal/centrifugo) server and [Centrifuge](https://github.com/centrifugal/centrifuge) library.

There is no v1 release of this library yet – API still evolves. At the moment patch version updates only contain backwards compatible changes, minor version updates can have backwards incompatible API changes.
Since there is no v1 release yet, patch version updates only contain backwards compatible changes, minor version updates can have backwards incompatible API changes.

Check out [client SDK API specification](https://centrifugal.dev/docs/transports/client_api) to learn how this SDK behaves. It's recommended to read that before starting to work with this SDK as the spec covers common SDK behavior - describes client and subscription state transitions, main options and methods. Also check out examples folder.

The features implemented by this SDK can be found in [SDK feature matrix](https://centrifugal.dev/docs/transports/client_sdk#sdk-feature-matrix).

Note that custom WebSocket connection Upgrade headers can only be set on platforms that support `dart:io`. So notably custom headers set in the configuration of `Client` are ignored on the web platform.
Note that custom WebSocket connection Upgrade headers are sent natively on platforms that support `dart:io` and in web context headers are sent using Centrifugo v6 [Headers Emulation](https://centrifugal.dev/blog/2025/01/16/centrifugo-v6-released#headers-emulation) feature.

> **The latest `centrifuge-dart` is compatible with [Centrifugo](https://github.com/centrifugal/centrifugo) server v6, v5 and v4 and [Centrifuge](https://github.com/centrifugal/centrifuge) >= 0.25.0. For Centrifugo v2, Centrifugo v3 and Centrifuge < 0.25.0 you should use `centrifuge-dart` v0.8.0.**
Expand All @@ -20,6 +20,10 @@ Note that custom WebSocket connection Upgrade headers can only be set on platfor

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

## Alternative SDK

See also [PlugFox/spinify](https://github.com/PlugFox/spinify) for an alternative Dart (Flutter) real-time client SDK implementation.

## Instructions for maintainers/contributors

### How to update protobuf definitions
Expand All @@ -31,9 +35,8 @@ When a mobile application goes to the background there are OS-specific limitatio

### How to release

1) Update changelog
2) Bump version in `pubspec.yaml`, push, create new tag
3) `dart pub publish`
1) Bump version in `pubspec.yaml`, push, create new tag
2) [ONLY IF THERE ARE ISSUES WITH CI PUBLISH] `dart pub publish`

## Author

Expand Down
3 changes: 0 additions & 3 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ linter:
- empty_statements
- hash_and_equals
# - invariant_booleans # https://github.com/flutter/flutter/issues/5790
- iterable_contains_unrelated_type
- list_remove_unrelated_type
# - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791
- no_adjacent_strings_in_list
- no_duplicate_case_values
Expand All @@ -51,7 +49,6 @@ linter:
# === style rules ===
- always_declare_return_types
# - always_put_control_body_on_new_line
- always_require_non_null_named_parameters
# - always_specify_types
- annotate_overrides
# - avoid_annotating_with_dynamic # not yet tested
Expand Down
4 changes: 2 additions & 2 deletions example/chat_app/lib/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class ChatClient {
url,
ClientConfig(
token: conf.userJwtToken,
headers: <String, dynamic>{
'user-id': chatUserId,
headers: <String, String>{
'user-id': chatUserId.toString(),
'user-name': chatUserName,
},
),
Expand Down
2 changes: 1 addition & 1 deletion example/chat_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 0.1.0

environment:
sdk: ">=2.19.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"

dependencies:
centrifuge:
Expand Down
16 changes: 12 additions & 4 deletions example/console/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ void main() async {
final channel = 'chat:index';
// generate user JWT token for user "dart":
// ./centrifugo gentoken --user dart
// For this example we generated token using HMAC secret key "secret". Don't forget
// that it's just an example, and tokens must NEVER be generated on client side and
// HMAC secret must NEVER be revealed to client side.
final userJwtToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkYXJ0IiwiZXhwIjoyMjc5NDQzNjgwLCJpYXQiOjE2NzQ2NDM2ODB9.XgsPZzAD4kMj7HdybJfpMGuDaRmuLvhUUxCGHs3mtXA';
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkYXJ0IiwiaWF0IjoxNzM3ODAwOTcxfQ.m30QzH9nxqMSJw3g5gL5Tjqnu-veQSn4RcH47ZozqQI';
// generate subscription JWT token for user "dart" and channel "chat:index":
// ./centrifugo gensubtoken --user dart --channel chat:index
// For this example we generated subscription token using HMAC secret key "secret". Don't forget
// that it's just an example, and tokens must NEVER be generated on client side and
// HMAC secret must NEVER be revealed to client side.
final subscriptionJwtToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkYXJ0IiwiZXhwIjoyMjc5NDQ0MDE4LCJpYXQiOjE2NzQ2NDQwMTgsImNoYW5uZWwiOiJjaGF0OmluZGV4In0.FjpnF6ofq3XCr1iqnwTZcpxCx6btuzCnn29DAIJbsBo';
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkYXJ0IiwiaWF0IjoxNzM3ODAxMDIwLCJjaGFubmVsIjoiY2hhdDppbmRleCJ9.fe3WguZKkGiTyacpUW-WJW-1yzpZTHAFO6FWo7gbbTs';

final onEvent = (dynamic event) {
print('client> $event');
Expand All @@ -24,8 +30,10 @@ void main() async {
url,
centrifuge.ClientConfig(
token: userJwtToken,
// Headers are only supported on platforms that support dart:io
headers: <String, dynamic>{'X-Example-Header': 'example'},
// If you want to use Headers in web environment – make sure your headers use
// string values, centrifuge-dart will then automatically attach them to connect
// frame (using Headers Emulation feature).
headers: <String, String>{'X-Example-Header': 'example'},
));

// State changes.
Expand Down
3 changes: 0 additions & 3 deletions example/flutter_app/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ linter:
- empty_statements
- hash_and_equals
# - invariant_booleans # https://github.com/flutter/flutter/issues/5790
- iterable_contains_unrelated_type
- list_remove_unrelated_type
# - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791
- no_adjacent_strings_in_list
- no_duplicate_case_values
Expand All @@ -52,7 +50,6 @@ linter:
# === style rules ===
- always_declare_return_types
# - always_put_control_body_on_new_line
- always_require_non_null_named_parameters
# - always_specify_types
- annotate_overrides
# - avoid_annotating_with_dynamic # not yet tested
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/usr/local/Caskroom/flutter/3.10.6/flutter"
export "FLUTTER_ROOT=/usr/local/Caskroom/flutter/3.24.4/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/fz/projects/centrifugal/centrifuge-dart/example/flutter_app"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=lib/main.dart"
Expand Down
2 changes: 1 addition & 1 deletion example/flutter_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: centrifuge_app
description: A new Flutter project.

environment:
sdk: '>=2.19.0 <4.0.0'
sdk: '>=3.3.0 <4.0.0'

dependencies:
flutter:
Expand Down
1 change: 1 addition & 0 deletions example/web_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ environment:
dependencies:
centrifuge:
path: ../../
web: ^1.1.0

publish_to: none

Expand Down
10 changes: 6 additions & 4 deletions example/web_app/web/main.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import 'dart:html';
import 'package:web/web.dart' as web;

import 'package:centrifuge/centrifuge.dart';

void main() {
querySelector('#output')?.text = 'Your Dart app is running.';
web.document.querySelector('#output')?.text = 'Your Dart app is running.';

void onEvent(dynamic event) {
querySelector('#output')?.text = 'client> $event';
web.document.querySelector('#output')?.text = 'client> $event';
}

const url = 'ws://localhost:8000/connection/websocket';
Client client = createClient(
url,
ClientConfig(),
ClientConfig(
headers: <String, String>{'Authorization': 'example'},
),
);

// State changes.
Expand Down
23 changes: 20 additions & 3 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:centrifuge/src/server_subscription.dart';
import 'package:centrifuge/src/transport.dart';
import 'package:meta/meta.dart';

import 'platform/vm.dart' if (dart.library.js_interop) 'platform/js.dart';
import 'proto/client.pb.dart' as protocol;
import 'subscription.dart';

Expand Down Expand Up @@ -50,6 +51,10 @@ abstract class Client {
///
void setToken(String token);

/// Set allows updating connection headers.
///
void setHeaders(Map<String, String> headers);

/// Ready resolves when client successfully connected.
/// Throws exceptions if called not in connecting or connected state.
Future<void> ready();
Expand Down Expand Up @@ -101,6 +106,7 @@ class ClientImpl implements Client {
ClientImpl(this._url, this._config, this._transportBuilder) {
_token = _config.token;
_data = _config.data;
_headers = Map<String, String>.of(_config.headers);
}

final TransportBuilder _transportBuilder;
Expand All @@ -113,6 +119,7 @@ class ClientImpl implements Client {
ClientConfig _config;
ClientConfig get config => _config;

Map<String, String> _headers = {};
String _token = '';
List<int>? _data;
String? _client;
Expand Down Expand Up @@ -197,6 +204,11 @@ class ClientImpl implements Client {
_token = token;
}

@override
void setHeaders(Map<String, String> headers) {
_headers = Map<String, String>.of(headers);
}

@override
Future<void> disconnect() async {
_reconnectAttempts = 0;
Expand Down Expand Up @@ -422,8 +434,8 @@ class ClientImpl implements Client {
return;
}

final transport = _transportBuilder(
url: _url, config: TransportConfig(headers: _config.headers, timeout: _config.timeout));
final transport =
_transportBuilder(url: _url, config: TransportConfig(headers: _headers, timeout: _config.timeout));

try {
await transport.open(_onPush, onError: (dynamic error) {
Expand Down Expand Up @@ -470,6 +482,11 @@ class ClientImpl implements Client {
request.name = _config.name;
request.version = _config.version;

if (isWeb) {
// Use headers emulation in web context.
request.headers.addAll(_headers);
}

if (_serverSubs.isNotEmpty) {
_serverSubs.forEach((key, value) {
final subRequest = protocol.SubscribeRequest();
Expand Down Expand Up @@ -560,7 +577,7 @@ class ClientImpl implements Client {
_inConnect = false;
return;
} else {
_processDisconnect(code: err.code, reason: err.message, reconnect: false);
_processDisconnect(code: err.code, reason: err.message, reconnect: true);
await transport.close();
_inConnect = false;
return;
Expand Down
8 changes: 5 additions & 3 deletions lib/src/client_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ClientConfig {
this.getToken,
this.data,
this.getData,
this.headers = const <String, dynamic>{},
this.headers = const <String, String>{},
this.tlsSkipVerify = false,
this.timeout = const Duration(seconds: 10),
this.minReconnectDelay = const Duration(milliseconds: 500),
Expand All @@ -41,8 +41,10 @@ class ClientConfig {

/// Headers that are set when connecting the web socket on dart:io platforms.
///
/// Note that headers are ignored on the web platform.
final Map<String, dynamic> headers;
/// Note that headers on the web platform use Headers Emulation (since Centrifugo v6)
/// – i.e. headers are sent to Centrifugo in first protocol message but then automatically
/// translated to HTTP headers by Centrifugo in the proxy request to the backend.
final Map<String, String> headers;

final bool tlsSkipVerify;
final Duration minReconnectDelay;
Expand Down
9 changes: 4 additions & 5 deletions lib/src/codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ class ProtobufCommandEncoder extends CommandEncoder {
@override
List<int> convert(Command input) {
final commandData = input.writeToBuffer();
final length = commandData.lengthInBytes;

final writer = CodedBufferWriter();
writer.writeInt32NoTag(length);

return writer.toBuffer() + commandData;
return (CodedBufferWriter()
..writeInt32NoTag(commandData.length)
..writeRawBytes(commandData))
.toBuffer();
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/src/platform/js.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const bool isWeb = true;
1 change: 1 addition & 0 deletions lib/src/platform/vm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const bool isWeb = false;
Loading

0 comments on commit fb29860

Please sign in to comment.