🧱 Websocket connection
This commit is contained in:
parent
525079a52f
commit
26093115c4
@ -1,3 +1,4 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
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:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/route.dart';
|
||||
import 'package:island/services/notify.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
@ -73,12 +75,17 @@ class IslandApp extends HookConsumerWidget {
|
||||
useEffect(() {
|
||||
// Load userinfo
|
||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||
ref.listen(websocketStateProvider, (_, state) {
|
||||
log('[WebSocket] $state');
|
||||
});
|
||||
Future(() {
|
||||
userNotifier.fetchUser().then((_) {
|
||||
final user = ref.watch(userInfoProvider);
|
||||
if (user.hasValue) {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
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/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/services/notify.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
@ -136,6 +137,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
||||
userNotifier.fetchUser().then((_) {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
subscribePushNotification(apiClient);
|
||||
final wsNotifier = ref.read(websocketStateProvider.notifier);
|
||||
wsNotifier.connect();
|
||||
});
|
||||
Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
|
@ -1273,10 +1273,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310"
|
||||
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
version: "6.1.5"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1844,7 +1844,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
|
@ -83,6 +83,7 @@ dependencies:
|
||||
firebase_messaging: ^15.2.5
|
||||
flutter_udid: ^4.0.0
|
||||
firebase_core: ^3.13.0
|
||||
web_socket_channel: ^3.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user