2024-06-08 13:35:50 +00:00
|
|
|
import 'dart:async';
|
2024-05-25 05:00:40 +00:00
|
|
|
import 'dart:convert';
|
2024-06-08 13:35:50 +00:00
|
|
|
import 'dart:developer';
|
2024-05-25 05:00:40 +00:00
|
|
|
import 'dart:io';
|
|
|
|
|
2024-06-08 13:35:50 +00:00
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
2024-06-06 15:28:19 +00:00
|
|
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
2024-09-15 07:55:14 +00:00
|
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
2024-05-25 05:00:40 +00:00
|
|
|
import 'package:get/get.dart';
|
2024-09-15 07:55:14 +00:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2024-08-21 07:39:29 +00:00
|
|
|
import 'package:solian/exceptions/request.dart';
|
2024-05-25 05:00:40 +00:00
|
|
|
import 'package:solian/models/notification.dart';
|
|
|
|
import 'package:solian/models/packet.dart';
|
|
|
|
import 'package:solian/models/pagination.dart';
|
2024-06-06 16:17:45 +00:00
|
|
|
import 'package:solian/platform.dart';
|
2024-05-25 05:00:40 +00:00
|
|
|
import 'package:solian/providers/auth.dart';
|
|
|
|
import 'package:solian/services.dart';
|
2024-07-06 09:39:19 +00:00
|
|
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
2024-05-25 05:00:40 +00:00
|
|
|
|
2024-07-16 11:46:53 +00:00
|
|
|
class WebSocketProvider extends GetxController {
|
2024-05-25 05:00:40 +00:00
|
|
|
RxBool isConnected = false.obs;
|
|
|
|
RxBool isConnecting = false.obs;
|
|
|
|
|
|
|
|
RxInt notificationUnread = 0.obs;
|
|
|
|
RxList<Notification> notifications =
|
|
|
|
List<Notification>.empty(growable: true).obs;
|
|
|
|
|
2024-07-06 09:39:19 +00:00
|
|
|
WebSocketChannel? websocket;
|
2024-05-25 05:00:40 +00:00
|
|
|
|
2024-07-16 11:46:53 +00:00
|
|
|
StreamController<NetworkPackage> stream = StreamController.broadcast();
|
|
|
|
|
2024-05-25 05:00:40 +00:00
|
|
|
@override
|
|
|
|
onInit() {
|
2024-09-15 07:55:14 +00:00
|
|
|
notifyPrefetch();
|
2024-05-25 05:00:40 +00:00
|
|
|
|
|
|
|
super.onInit();
|
|
|
|
}
|
|
|
|
|
2024-09-15 07:55:14 +00:00
|
|
|
void requestPermissions() {
|
|
|
|
try {
|
|
|
|
FirebaseMessaging.instance.requestPermission(
|
|
|
|
alert: true,
|
|
|
|
announcement: true,
|
|
|
|
carPlay: true,
|
|
|
|
badge: true,
|
|
|
|
sound: true);
|
|
|
|
} catch (_) {
|
|
|
|
// When firebase isn't initialized (background service)
|
|
|
|
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
|
|
|
FlutterLocalNotificationsPlugin();
|
|
|
|
flutterLocalNotificationsPlugin
|
|
|
|
.resolvePlatformSpecificImplementation<
|
|
|
|
AndroidFlutterLocalNotificationsPlugin>()
|
|
|
|
?.requestNotificationsPermission();
|
|
|
|
flutterLocalNotificationsPlugin
|
|
|
|
.resolvePlatformSpecificImplementation<
|
|
|
|
IOSFlutterLocalNotificationsPlugin>()
|
|
|
|
?.requestPermissions(
|
|
|
|
alert: true,
|
|
|
|
badge: true,
|
|
|
|
sound: true,
|
|
|
|
);
|
|
|
|
flutterLocalNotificationsPlugin
|
|
|
|
.resolvePlatformSpecificImplementation<
|
|
|
|
MacOSFlutterLocalNotificationsPlugin>()
|
|
|
|
?.requestPermissions(
|
|
|
|
alert: true,
|
|
|
|
badge: true,
|
|
|
|
sound: true,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-26 17:39:20 +00:00
|
|
|
Future<void> connect({noRetry = false}) async {
|
2024-06-03 15:36:46 +00:00
|
|
|
if (isConnected.value) {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
disconnect();
|
|
|
|
}
|
2024-06-02 15:20:34 +00:00
|
|
|
|
2024-05-25 05:00:40 +00:00
|
|
|
final AuthProvider auth = Get.find();
|
|
|
|
|
2024-08-21 07:25:50 +00:00
|
|
|
try {
|
|
|
|
await auth.ensureCredentials();
|
2024-05-25 05:00:40 +00:00
|
|
|
|
2024-08-21 07:25:50 +00:00
|
|
|
final uri = Uri.parse(ServiceFinder.buildUrl(
|
|
|
|
'dealer',
|
|
|
|
'/api/ws?tk=${auth.credentials!.accessToken}',
|
|
|
|
).replaceFirst('http', 'ws'));
|
2024-05-25 05:00:40 +00:00
|
|
|
|
2024-08-21 07:25:50 +00:00
|
|
|
isConnecting.value = true;
|
2024-05-25 05:00:40 +00:00
|
|
|
|
2024-07-06 09:39:19 +00:00
|
|
|
websocket = WebSocketChannel.connect(uri);
|
2024-05-25 05:00:40 +00:00
|
|
|
await websocket?.ready;
|
2024-08-21 07:25:50 +00:00
|
|
|
listen();
|
|
|
|
|
|
|
|
isConnected.value = true;
|
|
|
|
} catch (err) {
|
|
|
|
log('Unable connect dealer via websocket... $err');
|
2024-05-25 05:00:40 +00:00
|
|
|
if (!noRetry) {
|
|
|
|
await auth.refreshCredentials();
|
|
|
|
return connect(noRetry: true);
|
|
|
|
}
|
2024-08-21 07:25:50 +00:00
|
|
|
} finally {
|
|
|
|
isConnecting.value = false;
|
2024-05-25 05:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void disconnect() {
|
|
|
|
websocket?.sink.close(WebSocketStatus.normalClosure);
|
2024-06-02 15:20:34 +00:00
|
|
|
websocket = null;
|
2024-05-25 05:00:40 +00:00
|
|
|
isConnected.value = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void listen() {
|
|
|
|
websocket?.stream.listen(
|
|
|
|
(event) {
|
|
|
|
final packet = NetworkPackage.fromJson(jsonDecode(event));
|
2024-08-23 14:43:04 +00:00
|
|
|
log('Websocket incoming message: ${packet.method} ${packet.message}');
|
2024-07-16 11:46:53 +00:00
|
|
|
stream.sink.add(packet);
|
2024-09-15 09:19:55 +00:00
|
|
|
if (packet.method == 'notifications.new') {
|
|
|
|
notifications.add(Notification.fromJson(packet.payload!));
|
|
|
|
notificationUnread.value++;
|
|
|
|
}
|
2024-05-25 05:00:40 +00:00
|
|
|
},
|
|
|
|
onDone: () {
|
|
|
|
isConnected.value = false;
|
2024-06-08 13:35:50 +00:00
|
|
|
Future.delayed(const Duration(seconds: 1), () => connect());
|
2024-05-25 05:00:40 +00:00
|
|
|
},
|
|
|
|
onError: (err) {
|
|
|
|
isConnected.value = false;
|
2024-06-06 12:49:18 +00:00
|
|
|
Future.delayed(const Duration(seconds: 3), () => connect());
|
2024-05-25 05:00:40 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> notifyPrefetch() async {
|
|
|
|
final AuthProvider auth = Get.find();
|
2024-07-24 17:18:47 +00:00
|
|
|
if (auth.isAuthorized.isFalse) return;
|
2024-05-25 05:00:40 +00:00
|
|
|
|
2024-07-16 11:46:53 +00:00
|
|
|
final client = auth.configureClient('auth');
|
2024-05-25 05:00:40 +00:00
|
|
|
|
2024-07-16 11:46:53 +00:00
|
|
|
final resp = await client.get('/notifications?skip=0&take=100');
|
2024-05-25 05:00:40 +00:00
|
|
|
if (resp.statusCode == 200) {
|
|
|
|
final result = PaginationResult.fromJson(resp.body);
|
|
|
|
final data = result.data?.map((x) => Notification.fromJson(x)).toList();
|
|
|
|
if (data != null) {
|
|
|
|
notifications.addAll(data);
|
|
|
|
notificationUnread.value = data.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-06 15:28:19 +00:00
|
|
|
|
|
|
|
Future<void> registerPushNotifications() async {
|
2024-09-15 14:52:20 +00:00
|
|
|
if (PlatformInfo.isWeb) return;
|
|
|
|
|
2024-09-15 07:55:14 +00:00
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
if (prefs.getBool('service_background_notification') == true) {
|
|
|
|
log('Background notification service has been enabled, skip register push notifications');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-06-06 15:28:19 +00:00
|
|
|
final AuthProvider auth = Get.find();
|
2024-07-24 17:18:47 +00:00
|
|
|
if (auth.isAuthorized.isFalse) return;
|
2024-06-06 15:28:19 +00:00
|
|
|
|
2024-06-06 16:17:45 +00:00
|
|
|
late final String? token;
|
|
|
|
late final String provider;
|
2024-06-08 13:35:50 +00:00
|
|
|
final deviceUuid = await _getDeviceUuid();
|
|
|
|
|
2024-06-09 15:00:11 +00:00
|
|
|
if (deviceUuid == null || deviceUuid.isEmpty) {
|
2024-06-08 13:35:50 +00:00
|
|
|
log("Unable to active push notifications, couldn't get device uuid");
|
2024-07-21 15:43:18 +00:00
|
|
|
} else {
|
|
|
|
log('Device UUID is $deviceUuid');
|
2024-06-08 13:35:50 +00:00
|
|
|
}
|
2024-06-06 16:17:45 +00:00
|
|
|
|
|
|
|
if (PlatformInfo.isIOS || PlatformInfo.isMacOS) {
|
2024-06-23 10:51:49 +00:00
|
|
|
provider = 'apple';
|
2024-06-06 16:17:45 +00:00
|
|
|
token = await FirebaseMessaging.instance.getAPNSToken();
|
|
|
|
} else {
|
2024-06-23 10:51:49 +00:00
|
|
|
provider = 'firebase';
|
2024-06-06 16:17:45 +00:00
|
|
|
token = await FirebaseMessaging.instance.getToken();
|
|
|
|
}
|
2024-07-21 15:43:18 +00:00
|
|
|
log('Device Push Token is $token');
|
2024-06-06 15:28:19 +00:00
|
|
|
|
2024-07-16 11:46:53 +00:00
|
|
|
final client = auth.configureClient('auth');
|
2024-06-06 15:28:19 +00:00
|
|
|
|
2024-07-16 11:46:53 +00:00
|
|
|
final resp = await client.post('/notifications/subscribe', {
|
2024-06-06 16:17:45 +00:00
|
|
|
'provider': provider,
|
2024-06-06 15:28:19 +00:00
|
|
|
'device_token': token,
|
|
|
|
'device_id': deviceUuid,
|
|
|
|
});
|
2024-08-23 15:16:41 +00:00
|
|
|
if (resp.statusCode != 200 && resp.statusCode != 400) {
|
2024-08-21 07:39:29 +00:00
|
|
|
throw RequestException(resp);
|
2024-06-06 15:28:19 +00:00
|
|
|
}
|
|
|
|
}
|
2024-06-08 13:35:50 +00:00
|
|
|
|
|
|
|
Future<String?> _getDeviceUuid() async {
|
|
|
|
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
|
|
|
if (PlatformInfo.isWeb) {
|
2024-07-13 10:54:08 +00:00
|
|
|
final webInfo = await deviceInfo.webBrowserInfo;
|
2024-06-08 13:35:50 +00:00
|
|
|
return webInfo.vendor! +
|
|
|
|
webInfo.userAgent! +
|
|
|
|
webInfo.hardwareConcurrency.toString();
|
|
|
|
}
|
|
|
|
if (PlatformInfo.isAndroid) {
|
2024-07-13 10:54:08 +00:00
|
|
|
final androidInfo = await deviceInfo.androidInfo;
|
2024-06-08 13:35:50 +00:00
|
|
|
return androidInfo.id;
|
|
|
|
}
|
|
|
|
if (PlatformInfo.isIOS) {
|
2024-07-13 10:54:08 +00:00
|
|
|
final iosInfo = await deviceInfo.iosInfo;
|
2024-06-08 13:35:50 +00:00
|
|
|
return iosInfo.identifierForVendor!;
|
|
|
|
}
|
|
|
|
if (PlatformInfo.isLinux) {
|
2024-07-13 10:54:08 +00:00
|
|
|
final linuxInfo = await deviceInfo.linuxInfo;
|
2024-06-08 13:35:50 +00:00
|
|
|
return linuxInfo.machineId!;
|
|
|
|
}
|
|
|
|
if (PlatformInfo.isWindows) {
|
2024-07-13 10:54:08 +00:00
|
|
|
final windowsInfo = await deviceInfo.windowsInfo;
|
2024-06-08 13:35:50 +00:00
|
|
|
return windowsInfo.deviceId;
|
|
|
|
}
|
2024-07-13 10:54:08 +00:00
|
|
|
if (PlatformInfo.isMacOS) {
|
|
|
|
final macosInfo = await deviceInfo.macOsInfo;
|
|
|
|
return macosInfo.systemGUID;
|
|
|
|
}
|
2024-06-08 13:35:50 +00:00
|
|
|
return null;
|
|
|
|
}
|
2024-05-25 05:00:40 +00:00
|
|
|
}
|