🧱 Websocket connection
This commit is contained in:
parent
525079a52f
commit
26093115c4
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||||
@ -13,6 +14,7 @@ import 'package:island/pods/network.dart';
|
|||||||
import 'package:island/pods/theme.dart';
|
import 'package:island/pods/theme.dart';
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@ -73,12 +75,17 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
useEffect(() {
|
useEffect(() {
|
||||||
// Load userinfo
|
// Load userinfo
|
||||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
|
ref.listen(websocketStateProvider, (_, state) {
|
||||||
|
log('[WebSocket] $state');
|
||||||
|
});
|
||||||
Future(() {
|
Future(() {
|
||||||
userNotifier.fetchUser().then((_) {
|
userNotifier.fetchUser().then((_) {
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
if (user.hasValue) {
|
if (user.hasValue) {
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
subscribePushNotification(apiClient);
|
subscribePushNotification(apiClient);
|
||||||
|
final wsNotifier = ref.read(websocketStateProvider.notifier);
|
||||||
|
wsNotifier.connect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
98
lib/pods/websocket.dart
Normal file
98
lib/pods/websocket.dart
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:web_socket_channel/io.dart';
|
||||||
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
|
||||||
|
part 'websocket.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class WebSocketState with _$WebSocketState {
|
||||||
|
const factory WebSocketState.connected() = _Connected;
|
||||||
|
const factory WebSocketState.connecting() = _Connecting;
|
||||||
|
const factory WebSocketState.disconnected() = _Disconnected;
|
||||||
|
const factory WebSocketState.error(String message) = _Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
final websocketProvider = Provider<WebSocketService>((ref) {
|
||||||
|
return WebSocketService();
|
||||||
|
});
|
||||||
|
|
||||||
|
class WebSocketService {
|
||||||
|
WebSocketChannel? _channel;
|
||||||
|
Stream<dynamic>? _broadcastStream;
|
||||||
|
|
||||||
|
Future<void> connect(String url, String atk) async {
|
||||||
|
log('[WebSocket] Trying connecting to $url');
|
||||||
|
try {
|
||||||
|
_channel = IOWebSocketChannel.connect(
|
||||||
|
Uri.parse(url),
|
||||||
|
headers: {'Authorization': 'Bearer $atk'},
|
||||||
|
);
|
||||||
|
await _channel!.ready;
|
||||||
|
_broadcastStream = _channel!.stream.asBroadcastStream();
|
||||||
|
} catch (err) {
|
||||||
|
log('[WebSocket] Failed to connect: $err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketChannel? get ws => _channel;
|
||||||
|
Stream<dynamic> get stream => _broadcastStream!;
|
||||||
|
|
||||||
|
void sendMessage(String message) {
|
||||||
|
_channel!.sink.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
_channel?.sink.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final websocketStateProvider =
|
||||||
|
StateNotifierProvider<WebSocketStateNotifier, WebSocketState>(
|
||||||
|
(ref) => WebSocketStateNotifier(ref),
|
||||||
|
);
|
||||||
|
|
||||||
|
class WebSocketStateNotifier extends StateNotifier<WebSocketState> {
|
||||||
|
final Ref ref;
|
||||||
|
|
||||||
|
WebSocketStateNotifier(this.ref) : super(const WebSocketState.disconnected());
|
||||||
|
|
||||||
|
Future<void> connect() async {
|
||||||
|
state = const WebSocketState.connecting();
|
||||||
|
try {
|
||||||
|
final service = ref.read(websocketProvider);
|
||||||
|
final baseUrl = ref.watch(serverUrlProvider);
|
||||||
|
final atk = await getFreshAtk(
|
||||||
|
ref.watch(tokenPairProvider),
|
||||||
|
baseUrl,
|
||||||
|
onRefreshed: (atk, rtk) {
|
||||||
|
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
|
||||||
|
ref.invalidate(tokenPairProvider);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (atk == null) {
|
||||||
|
state = const WebSocketState.error('Unauthorized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await service.connect('$baseUrl/ws'.replaceFirst('http', 'ws'), atk);
|
||||||
|
state = const WebSocketState.connected();
|
||||||
|
} catch (err) {
|
||||||
|
state = WebSocketState.error('Failed to connect: $err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendMessage(String message) {
|
||||||
|
final service = ref.read(websocketProvider);
|
||||||
|
service.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
final service = ref.read(websocketProvider);
|
||||||
|
service.close();
|
||||||
|
state = const WebSocketState.disconnected();
|
||||||
|
}
|
||||||
|
}
|
207
lib/pods/websocket.freezed.dart
Normal file
207
lib/pods/websocket.freezed.dart
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'websocket.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$WebSocketState {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is WebSocketState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebSocketState()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class $WebSocketStateCopyWith<$Res> {
|
||||||
|
$WebSocketStateCopyWith(WebSocketState _, $Res Function(WebSocketState) __);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _Connected implements WebSocketState {
|
||||||
|
const _Connected();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebSocketState.connected()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _Connecting implements WebSocketState {
|
||||||
|
const _Connecting();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Connecting);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebSocketState.connecting()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _Disconnected implements WebSocketState {
|
||||||
|
const _Disconnected();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Disconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebSocketState.disconnected()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _Error implements WebSocketState {
|
||||||
|
const _Error(this.message);
|
||||||
|
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
/// Create a copy of WebSocketState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$ErrorCopyWith<_Error> get copyWith => __$ErrorCopyWithImpl<_Error>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Error&&(identical(other.message, message) || other.message == message));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebSocketState.error(message: $message)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$ErrorCopyWith<$Res> implements $WebSocketStateCopyWith<$Res> {
|
||||||
|
factory _$ErrorCopyWith(_Error value, $Res Function(_Error) _then) = __$ErrorCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String message
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$ErrorCopyWithImpl<$Res>
|
||||||
|
implements _$ErrorCopyWith<$Res> {
|
||||||
|
__$ErrorCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _Error _self;
|
||||||
|
final $Res Function(_Error) _then;
|
||||||
|
|
||||||
|
/// Create a copy of WebSocketState
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') $Res call({Object? message = null,}) {
|
||||||
|
return _then(_Error(
|
||||||
|
null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
@ -11,6 +11,7 @@ import 'package:island/models/auth.dart';
|
|||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@ -136,6 +137,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
userNotifier.fetchUser().then((_) {
|
userNotifier.fetchUser().then((_) {
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
subscribePushNotification(apiClient);
|
subscribePushNotification(apiClient);
|
||||||
|
final wsNotifier = ref.read(websocketStateProvider.notifier);
|
||||||
|
wsNotifier.connect();
|
||||||
});
|
});
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -1273,10 +1273,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310"
|
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.1.5"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1844,7 +1844,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
@ -83,6 +83,7 @@ dependencies:
|
|||||||
firebase_messaging: ^15.2.5
|
firebase_messaging: ^15.2.5
|
||||||
flutter_udid: ^4.0.0
|
flutter_udid: ^4.0.0
|
||||||
firebase_core: ^3.13.0
|
firebase_core: ^3.13.0
|
||||||
|
web_socket_channel: ^3.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user