Line data Source code
1 : import 'dart:async'; 2 : import 'dart:convert'; 3 : import 'dart:core'; 4 : 5 : import 'package:chatwoot_client_sdk/chatwoot_callbacks.dart'; 6 : import 'package:chatwoot_client_sdk/chatwoot_client.dart'; 7 : import 'package:chatwoot_client_sdk/data/local/entity/chatwoot_user.dart'; 8 : import 'package:chatwoot_client_sdk/data/local/local_storage.dart'; 9 : import 'package:chatwoot_client_sdk/data/remote/chatwoot_client_exception.dart'; 10 : import 'package:chatwoot_client_sdk/data/remote/requests/chatwoot_action_data.dart'; 11 : import 'package:chatwoot_client_sdk/data/remote/requests/chatwoot_new_message_request.dart'; 12 : import 'package:chatwoot_client_sdk/data/remote/responses/chatwoot_event.dart'; 13 : import 'package:chatwoot_client_sdk/data/remote/service/chatwoot_client_service.dart'; 14 : import 'package:flutter/material.dart'; 15 : 16 : /// Handles interactions between chatwoot client api service[clientService] and 17 : /// [localStorage] if persistence is enabled. 18 : /// 19 : /// Results from repository operations are passed through [callbacks] to be handled 20 : /// appropriately 21 : abstract class ChatwootRepository { 22 : @protected 23 : final ChatwootClientService clientService; 24 : @protected 25 : final LocalStorage localStorage; 26 : @protected 27 : ChatwootCallbacks callbacks; 28 : List<StreamSubscription> _subscriptions = []; 29 : 30 1 : ChatwootRepository(this.clientService, this.localStorage, this.callbacks); 31 : 32 : Future<void> initialize(ChatwootUser? user); 33 : 34 : void getPersistedMessages(); 35 : 36 : Future<void> getMessages(); 37 : 38 : void listenForEvents(); 39 : 40 : Future<void> sendMessage(ChatwootNewMessageRequest request); 41 : 42 : void sendAction(ChatwootActionType action); 43 : 44 : Future<void> clear(); 45 : 46 : void dispose(); 47 : } 48 : 49 : class ChatwootRepositoryImpl extends ChatwootRepository { 50 : bool _isListeningForEvents = false; 51 : 52 1 : ChatwootRepositoryImpl( 53 : {required ChatwootClientService clientService, 54 : required LocalStorage localStorage, 55 : required ChatwootCallbacks streamCallbacks}) 56 1 : : super(clientService, localStorage, streamCallbacks); 57 : 58 : /// Fetches persisted messages. 59 : /// 60 : /// Calls [ChatwootCallbacks.onMessagesRetrieved] when [ChatwootClientService.getAllMessages] is successful 61 : /// Calls [ChatwootCallbacks.onError] when [ChatwootClientService.getAllMessages] fails 62 : @override 63 1 : Future<void> getMessages() async { 64 : try { 65 3 : final messages = await clientService.getAllMessages(); 66 4 : await localStorage.messagesDao.saveAllMessages(messages); 67 3 : callbacks.onMessagesRetrieved?.call(messages); 68 1 : } on ChatwootClientException catch (e) { 69 3 : callbacks.onError?.call(e); 70 : } 71 : } 72 : 73 : /// Fetches persisted messages. 74 : /// 75 : /// Calls [ChatwootCallbacks.onPersistedMessagesRetrieved] if persisted messages are found 76 1 : @override 77 : void getPersistedMessages() { 78 3 : final persistedMessages = localStorage.messagesDao.getMessages(); 79 1 : if (persistedMessages.isNotEmpty) { 80 3 : callbacks.onPersistedMessagesRetrieved?.call(persistedMessages); 81 : } 82 : } 83 : 84 : /// Initializes chatwoot client repository 85 1 : Future<void> initialize(ChatwootUser? user) async { 86 : try { 87 : if (user != null) { 88 4 : await localStorage.userDao.saveUser(user); 89 : } 90 : 91 : //refresh contact 92 3 : final contact = await clientService.getContact(); 93 3 : localStorage.contactDao.saveContact(contact); 94 : 95 : //refresh conversation 96 3 : final conversations = await clientService.getConversations(); 97 : final persistedConversation = 98 3 : localStorage.conversationDao.getConversation()!; 99 1 : final refreshedConversation = conversations.firstWhere( 100 4 : (element) => element.id == persistedConversation.id, 101 0 : orElse: () => 102 : persistedConversation //highly unlikely orElse will be called but still added it just in case 103 : ); 104 3 : localStorage.conversationDao.saveConversation(refreshedConversation); 105 0 : } on ChatwootClientException catch (e) { 106 0 : callbacks.onError?.call(e); 107 : } 108 : 109 1 : listenForEvents(); 110 : } 111 : 112 : ///Sends message to chatwoot inbox 113 1 : Future<void> sendMessage(ChatwootNewMessageRequest request) async { 114 : try { 115 3 : final createdMessage = await clientService.createMessage(request); 116 4 : await localStorage.messagesDao.saveMessage(createdMessage); 117 4 : callbacks.onMessageSent?.call(createdMessage, request.echoId); 118 3 : if (clientService.connection != null && !_isListeningForEvents) { 119 1 : listenForEvents(); 120 : } 121 1 : } on ChatwootClientException catch (e) { 122 3 : callbacks.onError?.call( 123 4 : ChatwootClientException(e.cause, e.type, data: request.echoId)); 124 : } 125 : } 126 : 127 : /// Connects to chatwoot websocket and starts listening for updates 128 : /// 129 : /// Received events/messages are pushed through [ChatwootClient.callbacks] 130 1 : @override 131 : void listenForEvents() { 132 4 : final token = localStorage.contactDao.getContact()?.pubsubToken; 133 : if (token == null) { 134 : return; 135 : } 136 2 : clientService.startWebSocketConnection( 137 4 : localStorage.contactDao.getContact()!.pubsubToken); 138 : 139 5 : final newSubscription = clientService.connection!.stream.listen((event) { 140 2 : ChatwootEvent chatwootEvent = ChatwootEvent.fromJson(jsonDecode(event)); 141 2 : if (chatwootEvent.type == ChatwootEventType.welcome) { 142 3 : callbacks.onWelcome?.call(); 143 2 : } else if (chatwootEvent.type == ChatwootEventType.ping) { 144 3 : callbacks.onPing?.call(); 145 2 : } else if (chatwootEvent.type == ChatwootEventType.confirm_subscription) { 146 1 : if (!_isListeningForEvents) { 147 1 : _isListeningForEvents = true; 148 : } 149 3 : callbacks.onConfirmedSubscription?.call(); 150 3 : } else if (chatwootEvent.message?.event == 151 : ChatwootEventMessageType.message_created) { 152 2 : print("here comes message: $event"); 153 3 : final message = chatwootEvent.message!.data!.getMessage(); 154 3 : localStorage.messagesDao.saveMessage(message); 155 1 : if (message.isMine) { 156 2 : callbacks.onMessageDelivered 157 4 : ?.call(message, chatwootEvent.message!.data!.echoId!); 158 : } else { 159 3 : callbacks.onMessageReceived?.call(message); 160 : } 161 3 : } else if (chatwootEvent.message?.event == 162 : ChatwootEventMessageType.conversation_typing_off) { 163 3 : callbacks.onConversationStoppedTyping?.call(); 164 3 : } else if (chatwootEvent.message?.event == 165 : ChatwootEventMessageType.conversation_typing_on) { 166 3 : callbacks.onConversationStartedTyping?.call(); 167 3 : } else if (chatwootEvent.message?.event == 168 : ChatwootEventMessageType.presence_update) { 169 : final presenceStatuses = 170 3 : (chatwootEvent.message!.data!.users as Map<dynamic, dynamic>) 171 1 : .values; 172 1 : final isOnline = presenceStatuses.contains("online"); 173 : if (isOnline) { 174 3 : callbacks.onConversationIsOnline?.call(); 175 : } else { 176 3 : callbacks.onConversationIsOffline?.call(); 177 : } 178 : } else { 179 2 : print("chatwoot unknown event: $event"); 180 : } 181 : }); 182 2 : _subscriptions.add(newSubscription); 183 : } 184 : 185 : /// Clears all data related to current chatwoot client instance 186 : @override 187 1 : Future<void> clear() async { 188 3 : await localStorage.clear(); 189 : } 190 : 191 : /// Cancels websocket stream subscriptions and disposes [localStorage] 192 1 : @override 193 : void dispose() { 194 2 : localStorage.dispose(); 195 2 : callbacks = ChatwootCallbacks(); 196 3 : _subscriptions.forEach((subs) { 197 1 : subs.cancel(); 198 : }); 199 : } 200 : 201 : ///Send actions like user started typing 202 1 : @override 203 : void sendAction(ChatwootActionType action) { 204 2 : clientService.sendAction( 205 4 : localStorage.contactDao.getContact()!.pubsubToken, action); 206 : } 207 : }