From 7c812c09614d12881655b4a194a600acb9584f35 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Tue, 7 Sep 2021 21:14:56 +0200 Subject: [PATCH] refactor(@desktop/general): move Qt logic out of src/status library Since `src/status` was Qt dependant part because of the following foundation files: - /src/status/tasks/marathon/mailserver/controller.nim - /src/status/signals/core.nim - /src/status/tasks/marathon/mailserver/events.nim - /src/status/tasks/qt.nim and because logic related classes like: - /src/status/chat.nim this commit made that part Qt independant. New layer `src/app_service` is introduced and above mentioned foundation files are moved there. As well as corresponding logic for requested services. Communication between logical parts on the high level is as follows: - `src/app` => `src/app_service` => `src/status` => `src/app` - `src/app` => `src/status` => `src/app` Fixes: #3376 --- src/app/browser/view.nim | 2 +- src/app/browser/views/bookmark_list.nim | 2 +- src/app/chat/core.nim | 29 +- src/app/chat/event_handling.nim | 11 +- src/app/chat/signal_handling.nim | 6 +- src/app/chat/view.nim | 39 +- .../chat/views/activity_notification_list.nim | 2 +- src/app/chat/views/channel.nim | 12 +- src/app/chat/views/channels_list.nim | 3 +- src/app/chat/views/chat_item.nim | 2 +- src/app/chat/views/communities.nim | 4 +- src/app/chat/views/community_list.nim | 1 + src/app/chat/views/community_members_list.nim | 2 +- src/app/chat/views/ens.nim | 10 +- src/app/chat/views/message_format.nim | 2 +- src/app/chat/views/message_item.nim | 2 +- src/app/chat/views/message_list.nim | 3 +- .../views/message_search/view_controller.nim | 17 +- src/app/chat/views/messages.nim | 19 +- src/app/chat/views/reactions.nim | 4 +- src/app/chat/views/sticker_list.nim | 2 +- src/app/chat/views/sticker_pack_list.nim | 3 +- src/app/chat/views/stickers.nim | 18 +- src/app/chat/views/user_list.nim | 3 +- src/app/login/core.nim | 4 +- src/app/login/view.nim | 5 +- src/app/node/core.nim | 10 +- src/app/node/view.nim | 14 +- src/app/onboarding/core.nim | 4 +- src/app/onboarding/view.nim | 4 +- src/app/onboarding/views/account_info.nim | 2 +- src/app/profile/core.nim | 15 +- src/app/profile/view.nim | 19 +- src/app/profile/views/contacts.nim | 9 +- src/app/profile/views/custom_networks.nim | 2 +- src/app/profile/views/device_list.nim | 2 +- src/app/profile/views/devices.nim | 2 +- src/app/profile/views/ens_manager.nim | 13 +- src/app/profile/views/fleets.nim | 1 - src/app/profile/views/mailservers.nim | 12 +- src/app/profile/views/mnemonic.nim | 2 +- src/app/profile/views/profile_info.nim | 1 - src/app/provider/core.nim | 1 - src/app/provider/view.nim | 2 +- src/app/utilsView/core.nim | 9 +- src/app/utilsView/view.nim | 11 +- src/app/wallet/v1/core.nim | 13 +- src/app/wallet/v1/view.nim | 19 +- src/app/wallet/v1/views/accounts.nim | 7 +- src/app/wallet/v1/views/balance.nim | 11 +- src/app/wallet/v1/views/collectibles.nim | 15 +- src/app/wallet/v1/views/dapp_browser.nim | 3 +- src/app/wallet/v1/views/ens.nim | 11 +- src/app/wallet/v1/views/gas.nim | 12 +- src/app/wallet/v1/views/history.nim | 14 +- src/app/wallet/v1/views/token_list.nim | 13 +- src/app/wallet/v1/views/tokens.nim | 12 +- src/app/wallet/v1/views/transactions.nim | 15 +- src/app/wallet/v2/core.nim | 13 +- src/app/wallet/v2/view.nim | 9 +- src/app/wallet/v2/views/accounts.nim | 7 +- .../buy_sell_crypto/service_controller.nim | 8 +- src/app/wallet/v2/views/collectibles.nim | 14 +- .../async_service}/chat/async_tasks.nim | 2 +- .../async_service/chat/service.nim | 111 ++ .../async_service/wallet}/async_tasks.nim | 2 +- .../async_service/wallet/service.nim | 42 + src/app_service/main.nim | 49 + .../service/os_notification/service.nim | 48 + src/app_service/signals/base.nim | 15 + src/app_service/signals/community.nim | 13 + .../signals/discovery_summary.nim} | 9 +- .../signals/envelope.nim} | 6 +- .../signals/expired.nim | 6 +- .../signals/mailserver.nim | 13 +- src/app_service/signals/messages.nim | 95 ++ .../signals/signal_controller.nim} | 16 +- src/app_service/signals/signal_type.nim | 26 + src/{status => app_service}/signals/stats.nim | 10 +- .../signals/wallet.nim | 11 +- .../signals/whisper_filter.nim} | 14 +- src/{status => app_service}/tasks/common.nim | 0 .../tasks/marathon.nim | 15 +- .../tasks/marathon/common.nim | 0 .../tasks/marathon/mailserver/controller.nim | 2 +- .../tasks/marathon/mailserver/events.nim | 0 .../tasks/marathon/mailserver/model.nim | 10 +- .../tasks/marathon/mailserver/worker.nim | 2 +- .../tasks/marathon/worker.nim | 0 src/{status => app_service}/tasks/qt.nim | 0 .../tasks/threadpool.nim | 19 +- src/nim_status_client.nim | 40 +- src/status/accounts.nim | 2 +- src/status/browser.nim | 5 +- src/status/chat.nim | 1370 ++++++++--------- src/status/chat/chat.nim | 187 +-- src/status/chat/message.nim | 90 -- src/status/chat/utils.nim | 5 +- src/status/devices.nim | 3 +- src/status/ens.nim | 2 +- src/status/fleet.nim | 5 +- src/status/libstatus/accounts.nim | 8 +- src/status/libstatus/browser.nim | 2 +- src/status/libstatus/chat.nim | 5 +- src/status/libstatus/contacts.nim | 3 +- src/status/libstatus/edn_helpers.nim | 2 +- src/status/libstatus/eth/contracts.nim | 2 +- src/status/libstatus/eth/eth.nim | 2 +- src/status/libstatus/eth/methods.nim | 2 +- src/status/libstatus/eth/transactions.nim | 2 +- src/status/libstatus/gif.nim | 3 +- src/status/libstatus/settings.nim | 6 +- src/status/libstatus/stickers.nim | 3 +- src/status/libstatus/tokens.nim | 4 +- src/status/libstatus/wallet.nim | 8 +- src/status/network.nim | 2 +- src/status/notifications/os_notifications.nim | 59 +- src/status/profile.nim | 2 +- src/status/profile/profile.nim | 47 +- src/status/provider.nim | 2 +- src/status/settings.nim | 8 +- src/status/signals/communities.nim | 10 - src/status/signals/messages.nim | 456 ------ src/status/signals/types.nim | 75 - src/status/status.nim | 12 +- src/status/stickers.nim | 3 +- src/status/tasks/task_runner_impl.nim | 28 - src/status/tokens.nim | 9 - src/status/transactions.nim | 2 - src/status/types.nim | 285 ---- src/status/types/account.nim | 46 + .../types/activity_center_notification.nim | 49 + src/status/types/bookmark.nim | 7 + src/status/types/chat.nim | 170 ++ src/status/types/chat_member.nim | 21 + src/status/types/chat_membership_event.nim | 28 + src/status/types/community.nim | 91 ++ src/status/types/community_category.nim | 6 + .../types/community_membership_request.nim | 21 + src/status/types/derived_account.nim | 6 + src/status/types/fleet.nim | 52 + src/status/types/gas_prediction.nim | 9 + src/status/types/identity_image.nim | 6 + .../devices.nim => types/installation.nim} | 0 src/status/types/message.nim | 192 +++ .../types/message_command_parameters.nim | 16 + src/status/types/message_reaction.nim | 21 + src/status/types/message_text_item.nim | 25 + src/status/types/multi_accounts.nim | 12 + src/status/types/network.nim | 15 + src/status/types/network_details.nim | 12 + src/status/types/node_config.nim | 11 + .../os_notification.nim} | 8 + src/status/types/pending_transaction_type.nim | 8 + src/status/types/profile.nim | 49 + src/status/types/removed_message.nim | 13 + src/status/types/rpc_response.nim | 29 + src/status/types/setting.nim | 31 + src/status/types/status_update.nim | 24 + src/status/types/sticker.nim | 32 + src/status/types/transaction.nim | 30 + src/status/types/upstream_config.nim | 8 + src/status/wallet.nim | 9 +- src/status/wallet/account.nim | 2 +- src/status/wallet/balance_manager.nim | 2 +- src/status/wallet/collectibles.nim | 2 +- src/status/wallet2.nim | 359 ++--- src/status/wallet2/account.nim | 2 +- src/status/wallet2/balance_manager.nim | 2 +- src/status/wallet2/collectibles.nim | 2 +- 170 files changed, 2786 insertions(+), 2477 deletions(-) rename src/{status => app_service/async_service}/chat/async_tasks.nim (98%) create mode 100644 src/app_service/async_service/chat/service.nim rename src/{status/wallet2 => app_service/async_service/wallet}/async_tasks.nim (88%) create mode 100644 src/app_service/async_service/wallet/service.nim create mode 100644 src/app_service/main.nim create mode 100644 src/app_service/service/os_notification/service.nim create mode 100644 src/app_service/signals/base.nim create mode 100644 src/app_service/signals/community.nim rename src/{status/signals/discovery.nim => app_service/signals/discovery_summary.nim} (71%) rename src/{status/signals/envelopes.nim => app_service/signals/envelope.nim} (79%) rename src/{status => app_service}/signals/expired.nim (79%) rename src/{status => app_service}/signals/mailserver.nim (73%) create mode 100644 src/app_service/signals/messages.nim rename src/{status/signals/core.nim => app_service/signals/signal_controller.nim} (81%) create mode 100644 src/app_service/signals/signal_type.nim rename src/{status => app_service}/signals/stats.nim (67%) rename src/{status => app_service}/signals/wallet.nim (75%) rename src/{status/signals/whisperFilter.nim => app_service/signals/whisper_filter.nim} (69%) rename src/{status => app_service}/tasks/common.nim (100%) rename src/{status => app_service}/tasks/marathon.nim (84%) rename src/{status => app_service}/tasks/marathon/common.nim (100%) rename src/{status => app_service}/tasks/marathon/mailserver/controller.nim (91%) rename src/{status => app_service}/tasks/marathon/mailserver/events.nim (100%) rename src/{status => app_service}/tasks/marathon/mailserver/model.nim (97%) rename src/{status => app_service}/tasks/marathon/mailserver/worker.nim (97%) rename src/{status => app_service}/tasks/marathon/worker.nim (100%) rename src/{status => app_service}/tasks/qt.nim (100%) rename src/{status => app_service}/tasks/threadpool.nim (99%) delete mode 100644 src/status/chat/message.nim delete mode 100644 src/status/signals/communities.nim delete mode 100644 src/status/signals/messages.nim delete mode 100644 src/status/signals/types.nim delete mode 100644 src/status/tasks/task_runner_impl.nim delete mode 100644 src/status/types.nim create mode 100644 src/status/types/account.nim create mode 100644 src/status/types/activity_center_notification.nim create mode 100644 src/status/types/bookmark.nim create mode 100644 src/status/types/chat.nim create mode 100644 src/status/types/chat_member.nim create mode 100644 src/status/types/chat_membership_event.nim create mode 100644 src/status/types/community.nim create mode 100644 src/status/types/community_category.nim create mode 100644 src/status/types/community_membership_request.nim create mode 100644 src/status/types/derived_account.nim create mode 100644 src/status/types/fleet.nim create mode 100644 src/status/types/gas_prediction.nim create mode 100644 src/status/types/identity_image.nim rename src/status/{profile/devices.nim => types/installation.nim} (100%) create mode 100644 src/status/types/message.nim create mode 100644 src/status/types/message_command_parameters.nim create mode 100644 src/status/types/message_reaction.nim create mode 100644 src/status/types/message_text_item.nim create mode 100644 src/status/types/multi_accounts.nim create mode 100644 src/status/types/network.nim create mode 100644 src/status/types/network_details.nim create mode 100644 src/status/types/node_config.nim rename src/status/{notifications/os_notification_details.nim => types/os_notification.nim} (89%) create mode 100644 src/status/types/pending_transaction_type.nim create mode 100644 src/status/types/profile.nim create mode 100644 src/status/types/removed_message.nim create mode 100644 src/status/types/rpc_response.nim create mode 100644 src/status/types/setting.nim create mode 100644 src/status/types/status_update.nim create mode 100644 src/status/types/sticker.nim create mode 100644 src/status/types/transaction.nim create mode 100644 src/status/types/upstream_config.nim diff --git a/src/app/browser/view.nim b/src/app/browser/view.nim index 71abce0540..e193cacb1a 100644 --- a/src/app/browser/view.nim +++ b/src/app/browser/view.nim @@ -1,6 +1,6 @@ import NimQml, json, chronicles import ../../status/[status, browser] -import ../../status/types +import ../../status/types/[bookmark] import views/bookmark_list QtObject: diff --git a/src/app/browser/views/bookmark_list.nim b/src/app/browser/views/bookmark_list.nim index 19e2f05dfa..81ee30e6e7 100644 --- a/src/app/browser/views/bookmark_list.nim +++ b/src/app/browser/views/bookmark_list.nim @@ -1,6 +1,6 @@ import NimQml, Tables, chronicles import sequtils as sequtils -import ../../../status/types +import ../../../status/types/[bookmark] type BookmarkRoles {.pure.} = enum diff --git a/src/app/chat/core.nim b/src/app/chat/core.nim index 93b289e1dc..267dcdcf63 100644 --- a/src/app/chat/core.nim +++ b/src/app/chat/core.nim @@ -1,9 +1,10 @@ import NimQml, chronicles, tables import ../../status/chat as chat_model import ../../status/messages as messages_model -import ../../status/signals/types -import ../../status/types as status_types import ../../status/[chat, contacts, status, wallet, stickers, settings] +import ../../status/types/[message, transaction, os_notification, setting] +import ../../app_service/[main] +import ../../app_service/signals/[base] import view, views/channels_list, views/message_list, views/reactions, views/stickers as stickers_view import ../../eventemitter @@ -14,17 +15,21 @@ type ChatController* = ref object view*: ChatsView status*: Status variant*: QVariant + appService: AppService -proc newController*(status: Status): ChatController = +proc newController*(status: Status, appService: AppService): ChatController = result = ChatController() result.status = status - result.view = newChatsView(status) + result.appService = appService + result.view = newChatsView(status, appService) result.variant = newQVariant(result.view) proc delete*(self: ChatController) = delete self.variant delete self.view +proc loadInitialMessagesForChannel*(self: ChatController, channelId: string) + include event_handling include signal_handling @@ -61,3 +66,19 @@ proc init*(self: ChatController) = self.status.events.on("network:connected") do(e: Args): self.view.stickers.clearStickerPacks() self.view.stickers.obtainAvailableStickerPacks() + +proc loadInitialMessagesForChannel*(self: ChatController, channelId: string) = + if (channelId.len == 0): + info "empty channel id set for loading initial messages" + return + + if(self.status.chat.isMessageCursorSet(channelId)): + return + + if(self.status.chat.isEmojiCursorSet(channelId)): + return + + if(self.status.chat.isPinnedMessageCursorSet(channelId)): + return + + self.appService.chatService.loadMoreMessagesForChannel(channelId) diff --git a/src/app/chat/event_handling.nim b/src/app/chat/event_handling.nim index de500db38f..28e979d278 100644 --- a/src/app/chat/event_handling.nim +++ b/src/app/chat/event_handling.nim @@ -3,11 +3,10 @@ import # std libs import # status-desktop libs ../../status/chat/chat as status_chat, - ../../status/notifications/os_notifications, - ../../status/tasks/marathon, - ../../status/tasks/marathon/mailserver/worker, ./views/communities, ./views/messages +import ../../app_service/tasks/[qt, threadpool] +import ../../app_service/tasks/marathon/mailserver/worker proc handleChatEvents(self: ChatController) = # Display already saved messages @@ -116,7 +115,7 @@ proc handleChatEvents(self: ChatController) = if channel.chat.chatType == status_chat.ChatType.CommunityChat: self.view.communities.updateCommunityChat(channel.chat) - self.status.chat.loadInitialMessagesForChannel(channel.chat.id) + self.loadInitialMessagesForChannel(channel.chat.id) self.status.events.on("chatsLoaded") do(e:Args): self.view.calculateUnreadMessages() @@ -141,7 +140,7 @@ proc handleChatEvents(self: ChatController) = discard self.view.channelView.chats.addChatItemToList(channel.chat) self.view.setActiveChannel(channel.chat.id) - self.status.chat.loadInitialMessagesForChannel(channel.chat.id) + self.loadInitialMessagesForChannel(channel.chat.id) self.status.chat.statusUpdates() self.status.events.on("channelLeft") do(e: Args): @@ -190,7 +189,7 @@ proc handleChatEvents(self: ChatController) = self.view.communities.markNotificationsAsRead(markAsReadProps) proc handleMailserverEvents(self: ChatController) = - let mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + let mailserverWorker = self.appService.marathon[MailserverWorker().name] # TODO: test mailserver topics when joining chat self.status.events.on("channelJoined") do(e:Args): diff --git a/src/app/chat/signal_handling.nim b/src/app/chat/signal_handling.nim index 82cbfaa24b..1a39ae8816 100644 --- a/src/app/chat/signal_handling.nim +++ b/src/app/chat/signal_handling.nim @@ -1,5 +1,7 @@ import - ../../status/tasks/marathon/mailserver/worker + ../../app_service/tasks/marathon/mailserver/worker, + ../../app_service/signals/messages as signals_messages, + ../../app_service/signals/[community, discovery_summary, envelope, expired] proc handleSignals(self: ChatController) = self.status.events.on(SignalType.Message.event) do(e:Args): @@ -10,7 +12,7 @@ proc handleSignals(self: ChatController) = ## Handle mailserver peers being added and removed var data = DiscoverySummarySignal(e) let - mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + mailserverWorker = self.appService.marathon[MailserverWorker().name] task = PeerSummaryChangeTaskArg( `method`: "peerSummaryChange", peers: data.enodes diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index 16389629d4..a5ac96cc4c 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -7,13 +7,14 @@ import ../../status/messages as status_messages import ../../status/mailservers import ../../status/contacts as status_contacts import ../../status/ens as status_ens -import ../../status/chat/[chat, message] +import ../../status/chat/[chat] import ../../status/profile/profile -import ../../status/tasks/[qt, task_runner_impl] -import ../../status/tasks/marathon/mailserver/worker -import ../../status/signals/types as signal_types -import ../../status/types -import ../../status/notifications/[os_notifications, os_notification_details] +import ../../status/types/[activity_center_notification, os_notification, rpc_response] +import ../../app_service/[main] +import ../../app_service/tasks/[qt, threadpool] +import ../../app_service/tasks/marathon/mailserver/worker +import ../../app_service/signals/[base] +import ../../status/notifications/[os_notifications] import ../utils/image_utils import web3/[conversions, ethtypes] import views/message_search/[view_controller] @@ -48,7 +49,7 @@ proc getLinkPreviewData[T](self: T, slot: string, link: string, uuid: string) = link: link, uuid: uuid ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) const asyncActivityNotificationLoadTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncActivityNotificationLoadTaskArg](argEncoded) @@ -69,12 +70,13 @@ proc asyncActivityNotificationLoad[T](self: T, slot: string) = vptr: cast[ByteAddress](self.vptr), slot: slot ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type ChatsView* = ref object of QAbstractListModel status: Status + appService: AppService formatInputView: FormatInputView ensView: EnsView channelView*: ChannelView @@ -108,16 +110,17 @@ QtObject: self.messageSearchViewController.delete self.QAbstractListModel.delete - proc newChatsView*(status: Status): ChatsView = + proc newChatsView*(status: Status, appService: AppService): ChatsView = new(result, delete) result.status = status + result.appService = appService result.formatInputView = newFormatInputView() - result.ensView = newEnsView(status) + result.ensView = newEnsView(status, appService) result.communities = newCommunitiesView(status) - result.channelView = newChannelView(status, result.communities) - result.messageView = newMessageView(status, result.channelView, result.communities) + result.channelView = newChannelView(status, appService, result.communities) + result.messageView = newMessageView(status, appService, result.channelView, result.communities) result.messageSearchViewController = newMessageSearchViewController(status, - result.channelView, result.communities) + appService, result.channelView, result.communities) result.connected = false result.activityNotificationList = newActivityNotificationList(status) result.reactions = newReactionView( @@ -126,7 +129,7 @@ QtObject: result.messageView.pinnedMessagesList.addr, result.channelView.activeChannel ) - result.stickers = newStickersView(status, result.channelView.activeChannel) + result.stickers = newStickersView(status, appService, result.channelView.activeChannel) result.gif = newGifView() result.groups = newGroupsView(status,result.channelView.activeChannel) result.transactions = newTransactionsView(status) @@ -393,7 +396,7 @@ QtObject: if isActiveMailserverAvailable: self.messageView.setLoadingMessages(true) let - mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + mailserverWorker = self.appService.marathon[MailserverWorker().name] task = RequestMessagesTaskArg(`method`: "requestMessages") mailserverWorker.start(task) @@ -443,7 +446,7 @@ QtObject: proc requestMoreMessages*(self: ChatsView, fetchRange: int) {.slot.} = self.messageView.loadingMessages = true self.messageView.loadingMessagesChanged(true) - let mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + let mailserverWorker = self.appService.marathon[MailserverWorker().name] let task = RequestMessagesTaskArg( `method`: "requestMoreMessages", chatId: self.channelView.activeChannel.id) mailserverWorker.start(task) @@ -559,5 +562,5 @@ QtObject: messageId: messageId ) - self.status.osnotifications.showNotification(title, message, details, - useOSNotifications) \ No newline at end of file + self.appService.osNotificationService.showNotification(title, message, + details, useOSNotifications) \ No newline at end of file diff --git a/src/app/chat/views/activity_notification_list.nim b/src/app/chat/views/activity_notification_list.nim index d34f9d9e2a..59173ab491 100644 --- a/src/app/chat/views/activity_notification_list.nim +++ b/src/app/chat/views/activity_notification_list.nim @@ -1,7 +1,7 @@ import NimQml, Tables, chronicles, json, sequtils, strformat -import ../../../status/chat/chat import ../../../status/status import ../../../status/accounts +import ../../../status/types/[activity_center_notification] import strutils import message_item diff --git a/src/app/chat/views/channel.nim b/src/app/chat/views/channel.nim index ce1ca38db1..714803242f 100644 --- a/src/app/chat/views/channel.nim +++ b/src/app/chat/views/channel.nim @@ -4,7 +4,7 @@ import ../../../status/[status, contacts] import ../../../status/ens as status_ens import ../../../status/chat as status_chat import ../../../status/chat/[chat] -import ../../../status/tasks/[task_runner_impl] +import ../../../app_service/[main] import communities, chat_item, channels_list, communities, community_list @@ -14,6 +14,7 @@ logScope: QtObject: type ChannelView* = ref object of QObject status: Status + appService: AppService communities*: CommunitiesView chats*: ChannelsList activeChannel*: ChatItemView @@ -27,9 +28,10 @@ QtObject: self.contextChannel.delete self.QObject.delete - proc newChannelView*(status: Status, communities: CommunitiesView): ChannelView = + proc newChannelView*(status: Status, appService: AppService, communities: CommunitiesView): ChannelView = new(result, delete) result.status = status + result.appService = appService result.chats = newChannelsList(status) result.activeChannel = newChatItemView(status) result.contextChannel = newChatItemView(status) @@ -92,13 +94,13 @@ QtObject: if (self.chats.chats.len == 0): return let selectedChannel = self.getChannel(channelIndex) if (selectedChannel == nil): return - self.status.chat.asyncMarkAllChannelMessagesRead(selectedChannel.id) + self.appService.chatService.asyncMarkAllChannelMessagesRead(selectedChannel.id) proc markChatItemAsRead*(self: ChannelView, id: string) {.slot.} = if (self.chats.chats.len == 0): return let selectedChannel = self.getChannelById(id) if (selectedChannel == nil): return - self.status.chat.asyncMarkAllChannelMessagesRead(selectedChannel.id) + self.appService.chatService.asyncMarkAllChannelMessagesRead(selectedChannel.id) proc clearUnreadIfNeeded*(self: ChannelView, channel: var Chat) = if (not channel.isNil and (channel.unviewedMessagesCount > 0 or channel.mentionsCount > 0)): @@ -165,7 +167,7 @@ QtObject: if not self.communities.activeCommunity.active: self.previousActiveChannelIndex = self.chats.chats.findIndexById(self.activeChannel.id) - self.status.chat.asyncMarkAllChannelMessagesRead(self.activeChannel.id) + self.appService.chatService.asyncMarkAllChannelMessagesRead(self.activeChannel.id) self.activeChannelChanged() diff --git a/src/app/chat/views/channels_list.nim b/src/app/chat/views/channels_list.nim index 343b8a4a01..a6c8f9e59a 100644 --- a/src/app/chat/views/channels_list.nim +++ b/src/app/chat/views/channels_list.nim @@ -1,8 +1,9 @@ import NimQml, Tables import algorithm -import ../../../status/chat/[chat, message] +import ../../../status/chat/[chat] import ../../../status/status import ../../../status/accounts +import ../../../status/types/[message] import strutils type diff --git a/src/app/chat/views/chat_item.nim b/src/app/chat/views/chat_item.nim index d91b8e32a8..eef5512bcf 100644 --- a/src/app/chat/views/chat_item.nim +++ b/src/app/chat/views/chat_item.nim @@ -1,7 +1,7 @@ import NimQml, Tables, std/wrapnils import ../../../status/[chat/chat, status, ens, accounts, settings] -from ../../../status/types import Setting import ../../../status/utils as status_utils +import ../../../status/types/[setting] import chat_members diff --git a/src/app/chat/views/communities.nim b/src/app/chat/views/communities.nim index b5f8f24052..b9e41f6cab 100644 --- a/src/app/chat/views/communities.nim +++ b/src/app/chat/views/communities.nim @@ -5,8 +5,8 @@ import ./community_list import ./community_item import ./community_membership_request_list import ../../utils/image_utils -import ../../../status/signals/types as signal_types -import ../../../status/types +import ../../../status/types/[activity_center_notification, status_update, rpc_response] +import ../../../app_service/signals/[base] logScope: topics = "communities-view" diff --git a/src/app/chat/views/community_list.nim b/src/app/chat/views/community_list.nim index ca49380a4a..b696528112 100644 --- a/src/app/chat/views/community_list.nim +++ b/src/app/chat/views/community_list.nim @@ -6,6 +6,7 @@ import # vendor libs import # status-desktop libs ../../../status/chat/chat, ../../../status/status, ../../../status/accounts +import ../../../status/types/[status_update] type CommunityRoles {.pure.} = enum diff --git a/src/app/chat/views/community_members_list.nim b/src/app/chat/views/community_members_list.nim index 5737573ad7..48f8f1d3e7 100644 --- a/src/app/chat/views/community_members_list.nim +++ b/src/app/chat/views/community_members_list.nim @@ -1,6 +1,6 @@ import NimQml, Tables -import ../../../status/types as status_types import ../../../status/[chat/chat, ens, status, settings] +import ../../../status/types/[setting, status_update] type CommunityMembersRoles {.pure.} = enum diff --git a/src/app/chat/views/ens.nim b/src/app/chat/views/ens.nim index b404e4e91b..0794cdfa9e 100644 --- a/src/app/chat/views/ens.nim +++ b/src/app/chat/views/ens.nim @@ -2,7 +2,9 @@ import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, o import ../../../status/[status, contacts] import ../../../status/ens as status_ens -import ../../../status/tasks/[qt, task_runner_impl] +import ../../../app_service/[main] +import ../../../app_service/tasks/[qt, threadpool] +import ../../../app_service/tasks/marathon/mailserver/worker logScope: topics = "ens-view" @@ -23,18 +25,20 @@ proc resolveEns[T](self: T, slot: string, ens: string) = vptr: cast[ByteAddress](self.vptr), slot: slot, ens: ens ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type EnsView* = ref object of QObject status: Status + appService: AppService proc setup(self: EnsView) = self.QObject.setup proc delete*(self: EnsView) = self.QObject.delete - proc newEnsView*(status: Status): EnsView = + proc newEnsView*(status: Status, appService: AppService): EnsView = new(result, delete) result.status = status + result.appService = appService result.setup proc isEnsVerified*(self: EnsView, id: string): bool {.slot.} = diff --git a/src/app/chat/views/message_format.nim b/src/app/chat/views/message_format.nim index d38489525d..3208df5f2c 100644 --- a/src/app/chat/views/message_format.nim +++ b/src/app/chat/views/message_format.nim @@ -2,9 +2,9 @@ import NimQml, Tables, json, re import ../../../status/status import ../../../status/accounts import ../../../status/chat -import ../../../status/chat/[message] import ../../../status/profile/profile import ../../../status/ens +import ../../../status/types/[message] import strformat, strutils, sequtils let NEW_LINE = re"\n|\r" diff --git a/src/app/chat/views/message_item.nim b/src/app/chat/views/message_item.nim index 12d982ac00..3e648a5e71 100644 --- a/src/app/chat/views/message_item.nim +++ b/src/app/chat/views/message_item.nim @@ -1,7 +1,7 @@ import NimQml, std/wrapnils, chronicles import ../../../status/status -import ../../../status/chat/message import ../../../status/chat/stickers +import ../../../status/types/[message] import message_format QtObject: diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index 8002cbd2a3..99317974ce 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -2,9 +2,10 @@ import NimQml, Tables, sets, json, sugar, chronicles, sequtils import ../../../status/status import ../../../status/accounts import ../../../status/chat as status_chat -import ../../../status/chat/[message,stickers,chat] +import ../../../status/chat/[stickers,chat] import ../../../status/profile/profile import ../../../status/ens +import ../../../status/types/[message] import strutils import message_format import user_list diff --git a/src/app/chat/views/message_search/view_controller.nim b/src/app/chat/views/message_search/view_controller.nim index 0d1c551d82..5cbf82488d 100644 --- a/src/app/chat/views/message_search/view_controller.nim +++ b/src/app/chat/views/message_search/view_controller.nim @@ -1,11 +1,13 @@ -import NimQml, Tables, json, strutils, chronicles +import NimQml, Tables, json, strutils, chronicles, json_serialization import result_model, result_item, location_menu_model, location_menu_item, location_menu_sub_item import constants as sr_constants -import ../../../../status/[status, types] -import ../../../../status/chat/[message, chat] +import ../../../../status/[status] +import ../../../../status/chat/[chat] +import ../../../../status/types/[message, setting] import ../../../../status/libstatus/[settings] +import ../../../../app_service/[main] import ../communities import ../channel import ../chat_item @@ -28,6 +30,7 @@ method isEmpty*(self: ResultItemInfo): bool {.base.} = QtObject: type MessageSearchViewController* = ref object of QObject status: Status + appService: AppService channelView: ChannelView communitiesView: CommunitiesView resultItems: Table[string, ResultItemInfo] # [resuiltItemId, ResultItemInfo] @@ -47,10 +50,12 @@ QtObject: self.resultItems.clear self.QObject.delete - proc newMessageSearchViewController*(status: Status, channelView: ChannelView, - communitiesView: CommunitiesView): MessageSearchViewController = + proc newMessageSearchViewController*(status: Status, appService: AppService, + channelView: ChannelView, communitiesView: CommunitiesView): + MessageSearchViewController = new(result, delete) result.status = status + result.appService = appService result.channelView = channelView result.communitiesView = communitiesView result.resultItems = initTable[string, ResultItemInfo]() @@ -147,7 +152,7 @@ QtObject: for co in self.communitiesView.joinedCommunityList.communities: communities.add(co.id) - self.status.chat.asyncSearchMessages(communities, chats, + self.appService.chatService.asyncSearchMessages(communities, chats, self.meassgeSearchTerm, false) proc onSearchMessagesLoaded*(self: MessageSearchViewController, diff --git a/src/app/chat/views/messages.nim b/src/app/chat/views/messages.nim index 34b3de2d43..bf45d35fe9 100644 --- a/src/app/chat/views/messages.nim +++ b/src/app/chat/views/messages.nim @@ -1,14 +1,15 @@ import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, os, strformat, algorithm -import ../../../status/[status, contacts, types, mailservers] -import ../../../status/signals/types as signal_types +import ../../../status/[status, contacts, mailservers] import ../../../status/ens as status_ens import ../../../status/messages as status_messages import ../../../status/utils as status_utils -import ../../../status/chat/[chat, message] +import ../../../status/chat/[chat] import ../../../status/profile/profile -import ../../../status/tasks/[qt, task_runner_impl] -import ../../../status/tasks/marathon/mailserver/worker +import ../../../status/types/[message] +import ../../../app_service/[main] +import ../../../app_service/tasks/[qt, threadpool] +import ../../../app_service/tasks/marathon/mailserver/worker import communities, chat_item, channels_list, communities, community_list, message_list, channel, message_item @@ -23,6 +24,7 @@ type QtObject: type MessageView* = ref object of QAbstractListModel status: Status + appService: AppService messageList*: OrderedTable[string, ChatMessageList] pinnedMessagesList*: OrderedTable[string, ChatMessageList] channelView*: ChannelView @@ -45,9 +47,10 @@ QtObject: self.channelOpenTime = initTable[string, int64]() self.QAbstractListModel.delete - proc newMessageView*(status: Status, channelView: ChannelView, communitiesView: CommunitiesView): MessageView = + proc newMessageView*(status: Status, appService: AppService, channelView: ChannelView, communitiesView: CommunitiesView): MessageView = new(result, delete) result.status = status + result.appService = appService result.channelView = channelView result.communities = communitiesView result.messageList = initOrderedTable[string, ChatMessageList]() @@ -260,7 +263,7 @@ QtObject: proc loadMoreMessages*(self: MessageView, channelId: string) {.slot.} = self.setLoadingHistoryMessages(channelId, true) - self.status.chat.loadMoreMessagesForChannel(channelId) + self.appService.chatService.loadMoreMessagesForChannel(channelId) proc onMessagesLoaded*(self: MessageView, chatId: string, messages: var seq[Message]) = self.pushMessages(messages) @@ -290,7 +293,7 @@ QtObject: proc fillGaps*(self: MessageView, messageId: string) {.slot.} = self.setLoadingMessages(true) - let mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + let mailserverWorker = self.appService.marathon[MailserverWorker().name] let task = FillGapsTaskArg( `method`: "fillGaps", chatId: self.channelView.activeChannel.id, messageIds: @[messageId]) mailserverWorker.start(task) diff --git a/src/app/chat/views/reactions.nim b/src/app/chat/views/reactions.nim index 25f14f27b9..2987806b53 100644 --- a/src/app/chat/views/reactions.nim +++ b/src/app/chat/views/reactions.nim @@ -1,8 +1,8 @@ import NimQml, tables, json, chronicles -import ../../../status/[status, chat/message, chat/chat, settings] +import ../../../status/[status, chat/chat, settings] import message_list, chat_item import ../../../status/utils as status_utils -import ../../../status/types +import ../../../status/types/[message, setting] logScope: topics = "reactions-view" diff --git a/src/app/chat/views/sticker_list.nim b/src/app/chat/views/sticker_list.nim index 71a8352aff..527d72a769 100644 --- a/src/app/chat/views/sticker_list.nim +++ b/src/app/chat/views/sticker_list.nim @@ -1,6 +1,6 @@ import NimQml, Tables, sequtils import ../../../status/chat/stickers -import ../../../status/types +import ../../../status/types/[sticker] type StickerRoles {.pure.} = enum diff --git a/src/app/chat/views/sticker_pack_list.nim b/src/app/chat/views/sticker_pack_list.nim index eb86d157f2..1e2e1c825a 100644 --- a/src/app/chat/views/sticker_pack_list.nim +++ b/src/app/chat/views/sticker_pack_list.nim @@ -1,6 +1,7 @@ import NimQml, Tables, sequtils, sugar import ../../../status/chat/stickers, ./sticker_list -import ../../../status/types, ../../../status/utils +import ../../../status/utils +import ../../../status/types/[sticker] type StickerPackRoles {.pure.} = enum diff --git a/src/app/chat/views/stickers.nim b/src/app/chat/views/stickers.nim index 4f2c440e17..418e16fdb1 100644 --- a/src/app/chat/views/stickers.nim +++ b/src/app/chat/views/stickers.nim @@ -5,8 +5,12 @@ import # vendor libs chronicles, NimQml import # status-desktop libs - ../../../status/[status, stickers, wallet, types, utils], - sticker_pack_list, sticker_list, chat_item, ../../../status/tasks/[qt, task_runner_impl] + ../../../status/[status, stickers, wallet, utils], + sticker_pack_list, sticker_list, chat_item +import ../../../status/types/[sticker, pending_transaction_type] +import ../../../app_service/[main] +import ../../../app_service/tasks/[qt, threadpool] +import ../../../app_service/tasks/marathon/mailserver/worker logScope: topics = "stickers-view" @@ -48,7 +52,7 @@ proc estimate[T](self: T, slot: string, packId: int, address: string, price: str price: price, uuid: uuid ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) const obtainAvailableStickerPacksTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[ObtainAvailableStickerPacksTaskArg](argEncoded) @@ -64,13 +68,14 @@ proc obtainAvailableStickerPacks[T](self: T, slot: string) = tptr: cast[ByteAddress](obtainAvailableStickerPacksTask), vptr: cast[ByteAddress](self.vptr), slot: slot, - running: cast[ByteAddress](addr self.status.tasks.threadpool.running) + running: cast[ByteAddress](addr self.appService.threadpool.running) ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type StickersView* = ref object of QObject status: Status + appService: AppService activeChannel: ChatItemView stickerPacks*: StickerPackList recentStickers*: StickerList @@ -81,10 +86,11 @@ QtObject: proc delete*(self: StickersView) = self.QObject.delete - proc newStickersView*(status: Status, activeChannel: ChatItemView): StickersView = + proc newStickersView*(status: Status, appService: AppService, activeChannel: ChatItemView): StickersView = new(result, delete) result = StickersView() result.status = status + result.appService = appService result.stickerPacks = newStickerPackList() result.recentStickers = newStickerList() result.activeChannel = activeChannel diff --git a/src/app/chat/views/user_list.nim b/src/app/chat/views/user_list.nim index 46da409eb3..bdbdb7409d 100644 --- a/src/app/chat/views/user_list.nim +++ b/src/app/chat/views/user_list.nim @@ -2,8 +2,9 @@ import NimQml, Tables, json, chronicles, sequtils import ../../../status/status import ../../../status/accounts import ../../../status/chat as status_chat -import ../../../status/chat/[message, chat] +import ../../../status/chat/[chat] import ../../../status/ens +import ../../../status/types/[message] import strutils diff --git a/src/app/login/core.nim b/src/app/login/core.nim index 7f22432af3..2653dceb8c 100644 --- a/src/app/login/core.nim +++ b/src/app/login/core.nim @@ -1,7 +1,7 @@ import NimQml, chronicles, options, std/wrapnils -import ../../status/types as status_types -import ../../status/signals/types import ../../status/status +import ../../status/types/[account, rpc_response] +import ../../app_service/signals/[base] import view import ../../eventemitter diff --git a/src/app/login/view.nim b/src/app/login/view.nim index 956a97e7a5..23ad3ac97e 100644 --- a/src/app/login/view.nim +++ b/src/app/login/view.nim @@ -1,10 +1,9 @@ import NimQml, Tables, json, nimcrypto, strformat, json_serialization, chronicles -import ../../status/signals/types -import ../../status/types as status_types import ../../status/accounts as AccountModel +import ../../status/types/[account, rpc_response] +import ../../app_service/signals/[base] import ../onboarding/views/account_info import ../../status/status -import core type AccountRoles {.pure.} = enum diff --git a/src/app/node/core.nim b/src/app/node/core.nim index 13dd53b179..49b0b99693 100644 --- a/src/app/node/core.nim +++ b/src/app/node/core.nim @@ -1,7 +1,7 @@ import NimQml, chronicles -import ../../status/signals/types import ../../status/[status, node, network] -import ../../status/types as status_types +import ../../app_service/[main] +import ../../app_service/signals/[base, wallet, discovery_summary, stats] import ../../eventemitter import view @@ -10,14 +10,16 @@ logScope: type NodeController* = ref object status*: Status + appService: AppService view*: NodeView variant*: QVariant networkAccessMananger*: QNetworkAccessManager -proc newController*(status: Status, nam: QNetworkAccessManager): NodeController = +proc newController*(status: Status, appService: AppService, nam: QNetworkAccessManager): NodeController = result = NodeController() result.status = status - result.view = newNodeView(status) + result.appService = appService + result.view = newNodeView(status, appService) result.variant = newQVariant(result.view) result.networkAccessMananger = nam diff --git a/src/app/node/view.nim b/src/app/node/view.nim index ba4d65cd43..f78e18eca3 100644 --- a/src/app/node/view.nim +++ b/src/app/node/view.nim @@ -1,7 +1,9 @@ import NimQml, chronicles, strutils, json -import ../../status/[status, node, types, settings, accounts] -import ../../status/signals/types as signal_types -import ../../status/tasks/[qt, task_runner_impl] +import ../../status/[status, node, settings, accounts] +import ../../status/types/[setting] +import ../../app_service/[main] +import ../../app_service/signals/[stats] +import ../../app_service/tasks/[qt, threadpool] logScope: topics = "node-view" @@ -22,12 +24,13 @@ proc bloomFiltersBitsSet[T](self: T, slot: string) = vptr: cast[ByteAddress](self.vptr), slot: slot ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type NodeView* = ref object of QObject status*: Status + appService: AppService callResult: string lastMessage*: string wakuBloomFilterMode*: bool @@ -39,9 +42,10 @@ QtObject: proc setup(self: NodeView) = self.QObject.setup - proc newNodeView*(status: Status): NodeView = + proc newNodeView*(status: Status, appService: AppService): NodeView = new(result) result.status = status + result.appService = appService result.callResult = "Use this tool to call JSONRPC methods" result.lastMessage = "" result.wakuBloomFilterMode = false diff --git a/src/app/onboarding/core.nim b/src/app/onboarding/core.nim index 42c15013ed..30ed52afbe 100644 --- a/src/app/onboarding/core.nim +++ b/src/app/onboarding/core.nim @@ -1,8 +1,8 @@ import NimQml, chronicles, std/wrapnils -import ../../status/types as status_types import ../../status/accounts as AccountModel import ../../status/status -import ../../status/signals/types +import ../../status/types/[account] +import ../../app_service/signals/[base] import ../../eventemitter import view diff --git a/src/app/onboarding/view.nim b/src/app/onboarding/view.nim index 5c204043b3..008dc809e4 100644 --- a/src/app/onboarding/view.nim +++ b/src/app/onboarding/view.nim @@ -1,8 +1,8 @@ import NimQml, Tables, json, nimcrypto, strformat, json_serialization, strutils -import ../../status/types as status_types -import ../../status/signals/types import ../../status/accounts as AccountModel import ../../status/[status, wallet] +import ../../status/types/[account, rpc_response] +import ../../app_service/signals/[base] import views/account_info type diff --git a/src/app/onboarding/views/account_info.nim b/src/app/onboarding/views/account_info.nim index 91d42d07c8..96c1ca7f32 100644 --- a/src/app/onboarding/views/account_info.nim +++ b/src/app/onboarding/views/account_info.nim @@ -1,5 +1,5 @@ import NimQml -import ../../../status/types +import ../../../status/types/[account] import std/wrapnils QtObject: diff --git a/src/app/profile/core.nim b/src/app/profile/core.nim index cd883c59b2..171f97084b 100644 --- a/src/app/profile/core.nim +++ b/src/app/profile/core.nim @@ -1,7 +1,5 @@ import NimQml, json, strutils, sugar, sequtils, tables import json_serialization -import ../../status/signals/types -import ../../status/types as status_types import ../../status/profile/[profile, mailserver] import ../../status/[status, settings] import ../../status/contacts as status_contacts @@ -9,24 +7,29 @@ import ../../status/chat as status_chat import ../../status/devices as status_devices import ../../status/chat/chat import ../../status/wallet +import ../../status/types/[account, transaction, setting] +import ../../app_service/[main] +import ../../app_service/signals/[base, messages] +import ../../app_service/tasks/marathon/mailserver/events import ../../eventemitter import view import views/[ens_manager, devices, network, mailservers, contacts, muted_chats] import ../chat/views/channels_list import chronicles -import ../../status/tasks/marathon/mailserver/events const DEFAULT_NETWORK_NAME* = "mainnet_rpc" type ProfileController* = ref object view*: ProfileView variant*: QVariant - status*: Status + status: Status + appService: AppService -proc newController*(status: Status, changeLanguage: proc(locale: string)): ProfileController = +proc newController*(status: Status, appService: AppService, changeLanguage: proc(locale: string)): ProfileController = result = ProfileController() result.status = status - result.view = newProfileView(status, changeLanguage) + result.appService = appService + result.view = newProfileView(status, appService, changeLanguage) result.variant = newQVariant(result.view) proc delete*(self: ProfileController) = diff --git a/src/app/profile/view.nim b/src/app/profile/view.nim index 2113c4d18c..457ac0a5a3 100644 --- a/src/app/profile/view.nim +++ b/src/app/profile/view.nim @@ -9,9 +9,10 @@ import ../../status/contacts as status_contacts import ../../status/status import ../../status/ens as status_ens import ../../status/chat/chat -import ../../status/types +import ../../status/types/[setting, os_notification] import ../../status/constants as accountConstants -import ../../status/notifications/[os_notifications, os_notification_details] +import ../../status/notifications/[os_notifications] +import ../../app_service/[main] import qrcode/qrcode import ../utils/image_utils @@ -32,6 +33,7 @@ QtObject: fleets*: Fleets network*: NetworkView status*: Status + appService: AppService changeLanguage*: proc(locale: string) ens*: EnsManager @@ -53,23 +55,24 @@ QtObject: if not self.mailservers.isNil: self.mailservers.delete self.QObject.delete - proc newProfileView*(status: Status, changeLanguage: proc(locale: string)): ProfileView = + proc newProfileView*(status: Status, appService: AppService, changeLanguage: proc(locale: string)): ProfileView = new(result, delete) result = ProfileView() result.profile = newProfileInfoView() result.profilePicture = newProfilePictureView(status, result.profile) result.profileSettings = newProfileSettingsView(status, result.profile) result.mutedChats = newMutedChatsView(status) - result.contacts = newContactsView(status) + result.contacts = newContactsView(status, appService) result.devices = newDevicesView(status) result.network = newNetworkView(status) result.mnemonic = newMnemonicView(status) - result.mailservers = newMailserversView(status) + result.mailservers = newMailserversView(status, appService) result.dappList = newDappList(status) - result.ens = newEnsManager(status) + result.ens = newEnsManager(status, appService) result.fleets = newFleets(status) result.changeLanguage = changeLanguage result.status = status + result.appService = appService result.setup proc initialized*(self: ProfileView) {.signal.} @@ -201,5 +204,5 @@ QtObject: notificationType: notificationType.OsNotificationType ) - self.status.osnotifications.showNotification(title, message, details, - useOSNotifications) \ No newline at end of file + self.appService.osNotificationService.showNotification(title, message, + details, useOSNotifications) \ No newline at end of file diff --git a/src/app/profile/views/contacts.nim b/src/app/profile/views/contacts.nim index 7a66b5892a..1e03e28fc6 100644 --- a/src/app/profile/views/contacts.nim +++ b/src/app/profile/views/contacts.nim @@ -5,7 +5,8 @@ import ../../../status/chat/chat import contact_list import ../../../status/profile/profile import ../../../status/ens as status_ens -import ../../../status/tasks/[qt, task_runner_impl] +import ../../../app_service/[main] +import ../../../app_service/tasks/[qt, threadpool] logScope: topics = "contacts-view" @@ -28,11 +29,12 @@ proc lookupContact[T](self: T, slot: string, value: string) = slot: slot, value: value ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type ContactsView* = ref object of QObject status: Status + appService: AppService contactList*: ContactList contactRequests*: ContactList addedContacts*: ContactList @@ -49,9 +51,10 @@ QtObject: self.blockedContacts.delete self.QObject.delete - proc newContactsView*(status: Status): ContactsView = + proc newContactsView*(status: Status, appService: AppService): ContactsView = new(result, delete) result.status = status + result.appService = appService result.contactList = newContactList() result.contactRequests = newContactList() result.addedContacts = newContactList() diff --git a/src/app/profile/views/custom_networks.nim b/src/app/profile/views/custom_networks.nim index 61171bfb0c..0472d17221 100644 --- a/src/app/profile/views/custom_networks.nim +++ b/src/app/profile/views/custom_networks.nim @@ -2,7 +2,7 @@ import NimQml import Tables import json, sequtils import ../../../status/settings -import ../../../status/types +import ../../../status/types/[setting] import ../../../status/status type diff --git a/src/app/profile/views/device_list.nim b/src/app/profile/views/device_list.nim index 892fe4576e..137c16dae0 100644 --- a/src/app/profile/views/device_list.nim +++ b/src/app/profile/views/device_list.nim @@ -1,6 +1,6 @@ import NimQml import Tables -import ../../../status/profile/devices +import ../../../status/types/[installation] type DeviceRoles {.pure.} = enum diff --git a/src/app/profile/views/devices.nim b/src/app/profile/views/devices.nim index e6a30fcc3d..40aea3fdac 100644 --- a/src/app/profile/views/devices.nim +++ b/src/app/profile/views/devices.nim @@ -1,7 +1,7 @@ import NimQml, chronicles import ../../../status/status import ../../../status/devices as status_devices -import ../../../status/profile/devices +import ../../../status/types/[installation] import device_list logScope: diff --git a/src/app/profile/views/ens_manager.nim b/src/app/profile/views/ens_manager.nim index ac5f9ad6da..ee12c21844 100644 --- a/src/app/profile/views/ens_manager.nim +++ b/src/app/profile/views/ens_manager.nim @@ -3,14 +3,15 @@ import Tables import json import sequtils import strutils -from ../../../status/types import Setting, PendingTransactionType, RpcException import ../../../status/ens as status_ens import ../../../status/utils as status_utils import ../../../status/[status, settings, wallet] import ../../../status/wallet +import ../../../status/types/[setting, transaction, rpc_response] +import ../../../app_service/[main] +import ../../../app_service/tasks/[qt, threadpool] import sets import web3/ethtypes -import ../../../status/tasks/[qt, task_runner_impl] import chronicles type EnsRoles {.pure.} = enum @@ -38,7 +39,7 @@ proc validate[T](self: T, slot: string, ens: string, isStatus: bool, usernames: isStatus: isStatus, usernames: usernames ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) const detailsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let @@ -69,13 +70,14 @@ proc details[T](self: T, slot: string, username: string) = slot: slot, username: username ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type EnsManager* = ref object of QAbstractListModel usernames*: seq[string] pendingUsernames*: HashSet[string] status: Status + appService: AppService proc setup(self: EnsManager) = self.QAbstractListModel.setup @@ -83,10 +85,11 @@ QtObject: self.usernames = @[] self.QAbstractListModel.delete - proc newEnsManager*(status: Status): EnsManager = + proc newEnsManager*(status: Status, appService: AppService): EnsManager = new(result, delete) result.usernames = @[] result.status = status + result.appService = appService result.pendingUsernames = initHashSet[string]() result.setup diff --git a/src/app/profile/views/fleets.nim b/src/app/profile/views/fleets.nim index 52cca5c30d..fc827366cb 100644 --- a/src/app/profile/views/fleets.nim +++ b/src/app/profile/views/fleets.nim @@ -1,6 +1,5 @@ import NimQml, json import chronicles, strutils -import ../../../status/types as status_types import ../../../status/[status, settings, accounts] QtObject: diff --git a/src/app/profile/views/mailservers.nim b/src/app/profile/views/mailservers.nim index 57dc818b48..401ddcbc30 100644 --- a/src/app/profile/views/mailservers.nim +++ b/src/app/profile/views/mailservers.nim @@ -2,7 +2,9 @@ import NimQml, chronicles import ../../../status/[status, settings] import ../../../status/profile/mailserver import mailservers_list -import ../../../status/tasks/marathon/mailserver/worker +import ../../../app_service/[main] +import ../../../app_service/tasks/[qt, threadpool] +import ../../../app_service/tasks/marathon/mailserver/worker logScope: topics = "mailservers-view" @@ -10,6 +12,7 @@ logScope: QtObject: type MailserversView* = ref object of QObject status: Status + appService: AppService mailserversList*: MailServersList proc setup(self: MailserversView) = @@ -19,9 +22,10 @@ QtObject: self.mailserversList.delete self.QObject.delete - proc newMailserversView*(status: Status): MailserversView = + proc newMailserversView*(status: Status, appService: AppService): MailserversView = new(result, delete) result.status = status + result.appService = appService result.mailserversList = newMailServersList() result.setup @@ -38,7 +42,7 @@ QtObject: proc getActiveMailserver(self: MailserversView): string {.slot.} = let - mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + mailserverWorker = self.appService.marathon[MailserverWorker().name] task = GetActiveMailserverTaskArg( `method`: "getActiveMailserver", vptr: cast[ByteAddress](self.vptr), @@ -64,7 +68,7 @@ QtObject: self.status.settings.pinMailserver() else: let - mailserverWorker = self.status.tasks.marathon[MailserverWorker().name] + mailserverWorker = self.appService.marathon[MailserverWorker().name] task = GetActiveMailserverTaskArg( `method`: "getActiveMailserver", vptr: cast[ByteAddress](self.vptr), diff --git a/src/app/profile/views/mnemonic.nim b/src/app/profile/views/mnemonic.nim index ed01043b25..1739861269 100644 --- a/src/app/profile/views/mnemonic.nim +++ b/src/app/profile/views/mnemonic.nim @@ -1,6 +1,6 @@ import NimQml, chronicles, strutils import ../../../status/[status, settings] -import ../../../status/types +import ../../../status/types/[setting] import options logScope: diff --git a/src/app/profile/views/profile_info.nim b/src/app/profile/views/profile_info.nim index 66c0624793..05bf2055fb 100644 --- a/src/app/profile/views/profile_info.nim +++ b/src/app/profile/views/profile_info.nim @@ -1,7 +1,6 @@ import NimQml import chronicles import ../../../status/profile/profile -import ../../../status/types import std/wrapnils QtObject: diff --git a/src/app/provider/core.nim b/src/app/provider/core.nim index 7652f999db..98d76e17ef 100644 --- a/src/app/provider/core.nim +++ b/src/app/provider/core.nim @@ -1,5 +1,4 @@ import NimQml, chronicles -import ../../status/signals/types import ../../status/status import view diff --git a/src/app/provider/view.nim b/src/app/provider/view.nim index 1368eb96ed..9ee208b8f9 100644 --- a/src/app/provider/view.nim +++ b/src/app/provider/view.nim @@ -1,6 +1,6 @@ import NimQml import ../../status/[status, ens, chat/stickers, wallet, settings, provider] -import ../../status/types +import ../../status/types/[setting] import json, json_serialization, sets, strutils import chronicles import nbaser diff --git a/src/app/utilsView/core.nim b/src/app/utilsView/core.nim index 130e7b6f3c..114e85c0b4 100644 --- a/src/app/utilsView/core.nim +++ b/src/app/utilsView/core.nim @@ -1,7 +1,6 @@ import NimQml, chronicles -import ../../status/signals/types import ../../status/[status, node, network] -import ../../status/types as status_types +import ../../app_service/[main] import view import ../../eventemitter @@ -10,13 +9,15 @@ logScope: type UtilsController* = ref object status*: Status + appService: AppService view*: UtilsView variant*: QVariant -proc newController*(status: Status): UtilsController = +proc newController*(status: Status, appService: AppService): UtilsController = result = UtilsController() result.status = status - result.view = newUtilsView(status) + result.appService = appService + result.view = newUtilsView(status, appService) result.variant = newQVariant(result.view) proc delete*(self: UtilsController) = diff --git a/src/app/utilsView/view.nim b/src/app/utilsView/view.nim index 24e7cb9053..855ac7fe5e 100644 --- a/src/app/utilsView/view.nim +++ b/src/app/utilsView/view.nim @@ -1,12 +1,13 @@ import NimQml, os, strformat, strutils, parseUtils, chronicles import stint import ../../status/[status, wallet, settings, updates] -import ../../status/tasks/[qt, task_runner_impl] import ../../status/stickers import ../../status/tokens as status_tokens -import ../../status/types import ../../status/utils as status_utils import ../../status/ens as status_ens +import ../../status/types/[network] +import ../../app_service/[main] +import ../../app_service/tasks/[qt, threadpool] import ../utils/image_utils import web3/[ethtypes, conversions] import stew/byteutils @@ -20,6 +21,7 @@ type CheckForNewVersionTaskArg = ref object of QObjectTaskArg QtObject: type UtilsView* = ref object of QObject status*: Status + appService: AppService newVersion*: string proc setup(self: UtilsView) = @@ -33,10 +35,11 @@ QtObject: proc delete*(self: UtilsView) = self.QObject.delete - proc newUtilsView*(status: Status): UtilsView = + proc newUtilsView*(status: Status, appService: AppService): UtilsView = new(result, delete) result = UtilsView() result.status = status + result.appService = appService result.setup proc getOs*(self: UtilsView): string {.slot.} = @@ -159,7 +162,7 @@ QtObject: vptr: cast[ByteAddress](self.vptr), slot: slot ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) proc latestVersionSuccess*(self: UtilsView, latestVersionJSON: string) {.slot.} = let latestVersionObj = parseJSON(latestVersionJSON) diff --git a/src/app/wallet/v1/core.nim b/src/app/wallet/v1/core.nim index 7ca85e6686..d470cf2071 100644 --- a/src/app/wallet/v1/core.nim +++ b/src/app/wallet/v1/core.nim @@ -2,10 +2,13 @@ import NimQml, strformat, strutils, chronicles, sugar, sequtils import view import views/[asset_list, account_list, account_item] -import ../../../status/types as status_types -import ../../../status/signals/types + import ../../../status/[status, wallet, settings] import ../../../status/wallet/account as WalletTypes +import ../../../status/types/[transaction, setting] +import ../../../app_service/[main] +import ../../../app_service/signals/[base] +import ../../../app_service/signals/wallet as wallet_signal import ../../../eventemitter logScope: @@ -13,13 +16,15 @@ logScope: type WalletController* = ref object status: Status + appService: AppService view*: WalletView variant*: QVariant -proc newController*(status: Status): WalletController = +proc newController*(status: Status, appService: AppService): WalletController = result = WalletController() result.status = status - result.view = newWalletView(status) + result.appService = appService + result.view = newWalletView(status, appService) result.variant = newQVariant(result.view) proc delete*(self: WalletController) = diff --git a/src/app/wallet/v1/view.nim b/src/app/wallet/v1/view.nim index 1a9bc11ef6..6b11b18def 100644 --- a/src/app/wallet/v1/view.nim +++ b/src/app/wallet/v1/view.nim @@ -4,11 +4,13 @@ import NimQml, chronicles, stint import ../../../status/[status, wallet], views/[accounts, collectibles, transactions, tokens, gas, ens, dapp_browser, history, balance, utils, asset_list, account_list] +import ../../../app_service/[main] QtObject: type WalletView* = ref object of QAbstractListModel status: Status + appService: AppService accountsView: AccountsView collectiblesView: CollectiblesView transactionsView*: TransactionsView @@ -37,19 +39,20 @@ QtObject: proc setup(self: WalletView) = self.QAbstractListModel.setup - proc newWalletView*(status: Status): WalletView = + proc newWalletView*(status: Status, appService: AppService): WalletView = new(result, delete) result.status = status + result.appService = appService result.accountsView = newAccountsView(status) - result.collectiblesView = newCollectiblesView(status, result.accountsView) - result.transactionsView = newTransactionsView(status, result.accountsView) - result.tokensView = newTokensView(status, result.accountsView) - result.gasView = newGasView(status) - result.ensView = newEnsView(status) + result.collectiblesView = newCollectiblesView(status, appService, result.accountsView) + result.transactionsView = newTransactionsView(status, appService, result.accountsView) + result.tokensView = newTokensView(status, appService, result.accountsView) + result.gasView = newGasView(status, appService) + result.ensView = newEnsView(status, appService) result.dappBrowserView = newDappBrowserView(status, result.accountsView) - result.historyView = newHistoryView(status, result.accountsView, result.transactionsView) - result.balanceView = newBalanceView(status, result.accountsView, result.transactionsView, result.historyView) + result.historyView = newHistoryView(status, appService, result.accountsView, result.transactionsView) + result.balanceView = newBalanceView(status, appService, result.accountsView, result.transactionsView, result.historyView) result.utilsView = newUtilsView() result.isNonArchivalNode = false diff --git a/src/app/wallet/v1/views/accounts.nim b/src/app/wallet/v1/views/accounts.nim index 7b346a96d6..dd0b758a9b 100644 --- a/src/app/wallet/v1/views/accounts.nim +++ b/src/app/wallet/v1/views/accounts.nim @@ -1,9 +1,10 @@ import NimQml, json, sequtils, chronicles, strutils, strformat, json import - ../../../../status/[status, settings, types], - ../../../../status/signals/types as signal_types, - ../../../../status/wallet as status_wallet + ../../../../status/[status, settings], + ../../../../status/wallet as status_wallet, + ../../../../status/types/[rpc_response], + ../../../../app_service/signals/[base] import account_list, account_item diff --git a/src/app/wallet/v1/views/balance.nim b/src/app/wallet/v1/views/balance.nim index c9ab408c3b..ca961d2af1 100644 --- a/src/app/wallet/v1/views/balance.nim +++ b/src/app/wallet/v1/views/balance.nim @@ -3,8 +3,9 @@ import NimQml, json, sequtils, chronicles, strutils, strformat, json import ../../../../status/[status, wallet, tokens], - ../../../../status/tokens as status_tokens, - ../../../../status/tasks/[qt, task_runner_impl] + ../../../../status/tokens as status_tokens +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] import account_item, accounts, transactions, history @@ -34,11 +35,12 @@ proc initBalances[T](self: T, slot: string, address: string, tokenList: seq[stri vptr: cast[ByteAddress](self.vptr), slot: slot, address: address, tokenList: tokenList ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type BalanceView* = ref object of QObject status: Status + appService: AppService totalFiatBalance: string accountsView: AccountsView transactionsView*: TransactionsView @@ -47,9 +49,10 @@ QtObject: proc setup(self: BalanceView) = self.QObject.setup proc delete(self: BalanceView) = self.QObject.delete - proc newBalanceView*(status: Status, accountsView: AccountsView, transactionsView: TransactionsView, historyView: HistoryView): BalanceView = + proc newBalanceView*(status: Status, appService: AppService, accountsView: AccountsView, transactionsView: TransactionsView, historyView: HistoryView): BalanceView = new(result, delete) result.status = status + result.appService = appService result.totalFiatBalance = "" result.accountsView = accountsView result.transactionsView = transactionsView diff --git a/src/app/wallet/v1/views/collectibles.nim b/src/app/wallet/v1/views/collectibles.nim index 89b6e3658c..f71ca494c5 100644 --- a/src/app/wallet/v1/views/collectibles.nim +++ b/src/app/wallet/v1/views/collectibles.nim @@ -2,9 +2,10 @@ import atomics, strformat, strutils, sequtils, json, std/wrapnils, parseUtils, t import NimQml, json, sequtils, chronicles, strutils, strformat, json import - ../../../../status/[status, settings, wallet, tokens, utils, types], - ../../../../status/wallet/collectibles as status_collectibles, - ../../../../status/tasks/[qt, task_runner_impl] + ../../../../status/[status, settings, wallet, tokens, utils], + ../../../../status/wallet/collectibles as status_collectibles +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] import collectibles_list, accounts, account_list, account_item @@ -43,13 +44,14 @@ proc loadCollectibles[T](self: T, slot: string, address: string, collectiblesTyp tptr: cast[ByteAddress](loadCollectiblesTask), vptr: cast[ByteAddress](self.vptr), slot: slot, address: address, collectiblesType: collectiblesType, - running: cast[ByteAddress](addr self.status.tasks.threadpool.running) + running: cast[ByteAddress](addr self.appService.threadpool.running) ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type CollectiblesView* = ref object of QObject status: Status + appService: AppService accountsView*: AccountsView currentCollectiblesLists*: CollectiblesList @@ -58,9 +60,10 @@ QtObject: self.currentCollectiblesLists.delete self.QObject.delete - proc newCollectiblesView*(status: Status, accountsView: AccountsView): CollectiblesView = + proc newCollectiblesView*(status: Status, appService: AppService, accountsView: AccountsView): CollectiblesView = new(result, delete) result.status = status + result.appService = appService result.currentCollectiblesLists = newCollectiblesList() result.accountsView = accountsView # TODO: not ideal but a solution for now result.setup diff --git a/src/app/wallet/v1/views/dapp_browser.nim b/src/app/wallet/v1/views/dapp_browser.nim index 82139fe99f..058c3da67f 100644 --- a/src/app/wallet/v1/views/dapp_browser.nim +++ b/src/app/wallet/v1/views/dapp_browser.nim @@ -1,7 +1,8 @@ import sequtils, json, chronicles, web3/[ethtypes, conversions], stint import NimQml, json, sequtils, chronicles, strutils, json -import ../../../../status/[status, settings, wallet, types] +import ../../../../status/[status, settings, wallet] +import ../../../../status/types/[setting] import account_list, account_item, accounts diff --git a/src/app/wallet/v1/views/ens.nim b/src/app/wallet/v1/views/ens.nim index d8270d6042..b2432c9f0a 100644 --- a/src/app/wallet/v1/views/ens.nim +++ b/src/app/wallet/v1/views/ens.nim @@ -3,8 +3,9 @@ import NimQml, json, sequtils, chronicles, strutils, strformat, json import ../../../../status/[status, settings, wallet, tokens], - ../../../../status/ens as status_ens, - ../../../../status/tasks/[qt, task_runner_impl] + ../../../../status/ens as status_ens +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] import account_list, account_item, transaction_list, accounts, asset_list, token_list @@ -28,18 +29,20 @@ proc resolveEns[T](self: T, slot: string, ens: string, uuid: string) = vptr: cast[ByteAddress](self.vptr), slot: slot, ens: ens, uuid: uuid ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type EnsView* = ref object of QObject status: Status + appService: AppService proc setup(self: EnsView) = self.QObject.setup proc delete(self: EnsView) = self.QObject.delete - proc newEnsView*(status: Status): EnsView = + proc newEnsView*(status: Status, appService: AppService): EnsView = new(result, delete) result.status = status + result.appService = appService result.setup proc resolveENS*(self: EnsView, ens: string, uuid: string) {.slot.} = diff --git a/src/app/wallet/v1/views/gas.nim b/src/app/wallet/v1/views/gas.nim index bf98b430cc..9b77a93023 100644 --- a/src/app/wallet/v1/views/gas.nim +++ b/src/app/wallet/v1/views/gas.nim @@ -2,8 +2,10 @@ import atomics, strformat, strutils, sequtils, json, std/wrapnils, parseUtils, c import NimQml, json, sequtils, chronicles, strutils, strformat, json import - ../../../../status/[status, wallet, utils, types], - ../../../../status/tasks/[qt, task_runner_impl] + ../../../../status/[status, wallet, utils], + ../../../../status/types/[gas_prediction] +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] import account_item @@ -24,7 +26,7 @@ proc getGasPredictions[T](self: T, slot: string) = vptr: cast[ByteAddress](self.vptr), slot: slot ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) logScope: topics = "gas-view" @@ -32,6 +34,7 @@ logScope: QtObject: type GasView* = ref object of QObject status: Status + appService: AppService safeLowGasPrice: string standardGasPrice: string fastGasPrice: string @@ -41,9 +44,10 @@ QtObject: proc setup(self: GasView) = self.QObject.setup proc delete(self: GasView) = self.QObject.delete - proc newGasView*(status: Status): GasView = + proc newGasView*(status: Status, appService: AppService): GasView = new(result, delete) result.status = status + result.appService = appService result.safeLowGasPrice = "0" result.standardGasPrice = "0" result.fastGasPrice = "0" diff --git a/src/app/wallet/v1/views/history.nim b/src/app/wallet/v1/views/history.nim index b0e9c37844..03d788dc38 100644 --- a/src/app/wallet/v1/views/history.nim +++ b/src/app/wallet/v1/views/history.nim @@ -3,10 +3,11 @@ from sugar import `=>`, `->` import NimQml, json, sequtils, chronicles, strutils, json import - ../../../../status/[status, wallet, types, utils], + ../../../../status/[status, wallet, utils], ../../../../status/wallet as status_wallet, - ../../../../status/tasks/[qt, task_runner_impl] - + ../../../../status/types/[transaction] +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] import account_list, account_item, transaction_list, accounts, transactions logScope: @@ -39,11 +40,12 @@ proc loadTransactions*[T](self: T, slot: string, address: string, toBlock: Uint2 limit: limit, loadMore: loadMore ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type HistoryView* = ref object of QObject status: Status + appService: AppService accountsView: AccountsView transactionsView*: TransactionsView fetchingHistoryState: Table[string, bool] @@ -51,9 +53,11 @@ QtObject: proc setup(self: HistoryView) = self.QObject.setup proc delete(self: HistoryView) = self.QObject.delete - proc newHistoryView*(status: Status, accountsView: AccountsView, transactionsView: TransactionsView): HistoryView = + proc newHistoryView*(status: Status, appService: AppService, + accountsView: AccountsView, transactionsView: TransactionsView): HistoryView = new(result, delete) result.status = status + result.appService = appService result.fetchingHistoryState = initTable[string, bool]() result.accountsView = accountsView result.transactionsView = transactionsView diff --git a/src/app/wallet/v1/views/token_list.nim b/src/app/wallet/v1/views/token_list.nim index 6477b52815..1b82c6185f 100644 --- a/src/app/wallet/v1/views/token_list.nim +++ b/src/app/wallet/v1/views/token_list.nim @@ -6,7 +6,10 @@ import # vendor libs import # status-desktop libs ../../../../status/[utils, tokens, settings], - ../../../../status/tasks/[qt, task_runner_impl], ../../../../status/status + ../../../../status/status +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] +import ../../../../app_service/tasks/marathon/mailserver/worker from web3/conversions import `$` type @@ -46,11 +49,12 @@ proc getTokenDetails[T](self: T, slot: string, address: string) = vptr: cast[ByteAddress](self.vptr), slot: slot, address: address) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type TokenList* = ref object of QAbstractListModel - status*: Status + status: Status + appService: AppService tokens*: seq[Erc20Contract] isCustom*: bool @@ -76,10 +80,11 @@ QtObject: self.isCustom = true self.endResetModel() - proc newTokenList*(status: Status): TokenList = + proc newTokenList*(status: Status, appService: AppService): TokenList = new(result, delete) result.tokens = @[] result.status = status + result.appService = appService result.setup proc rowData(self: TokenList, index: int, column: string): string {.slot.} = diff --git a/src/app/wallet/v1/views/tokens.nim b/src/app/wallet/v1/views/tokens.nim index 7f6bfafa73..f435b10fe7 100644 --- a/src/app/wallet/v1/views/tokens.nim +++ b/src/app/wallet/v1/views/tokens.nim @@ -1,8 +1,8 @@ import atomics, strformat, strutils, sequtils, json, std/wrapnils, parseUtils, tables, chronicles, web3/[ethtypes, conversions], stint import NimQml, json, sequtils, chronicles, strutils, strformat, json -import ../../../../status/[status, settings, wallet, tokens, utils, types] - +import ../../../../status/[status, settings, wallet, tokens, utils] +import ../../../../app_service/[main] import account_list, account_item, transaction_list, accounts, asset_list, token_list logScope: @@ -11,6 +11,7 @@ logScope: QtObject: type TokensView* = ref object of QObject status: Status + appService: AppService accountsView: AccountsView currentAssetList*: AssetList defaultTokenList: TokenList @@ -23,13 +24,14 @@ QtObject: self.customTokenList.delete self.QObject.delete - proc newTokensView*(status: Status, accountsView: AccountsView): TokensView = + proc newTokensView*(status: Status, appService: AppService, accountsView: AccountsView): TokensView = new(result, delete) result.status = status + result.appService = appService result.accountsView = accountsView result.currentAssetList = newAssetList() - result.defaultTokenList = newTokenList(status) - result.customTokenList = newTokenList(status) + result.defaultTokenList = newTokenList(status, appService) + result.customTokenList = newTokenList(status, appService) result.setup proc hasAsset*(self: TokensView, symbol: string): bool {.slot.} = diff --git a/src/app/wallet/v1/views/transactions.nim b/src/app/wallet/v1/views/transactions.nim index 1a1a9017b0..6de8a72aea 100644 --- a/src/app/wallet/v1/views/transactions.nim +++ b/src/app/wallet/v1/views/transactions.nim @@ -3,8 +3,11 @@ import NimQml, json, sequtils, chronicles, strutils, strformat, json, stint import ../../../../status/[status, settings, wallet, tokens, utils], - ../../../../status/wallet as status_wallet, - ../../../../status/tasks/[qt, task_runner_impl] + ../../../../status/wallet as status_wallet + +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] +import ../../../../app_service/tasks/marathon/mailserver/worker import account_list, account_item, transaction_list, accounts @@ -46,7 +49,7 @@ proc sendTransaction[T](self: T, slot: string, from_addr: string, to: string, as assetAddress: assetAddress, value: value, gas: gas, gasPrice: gasPrice, password: password, uuid: uuid ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) const watchTransactionTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let @@ -61,11 +64,12 @@ proc watchTransaction[T](self: T, slot: string, transactionHash: string) = vptr: cast[ByteAddress](self.vptr), slot: slot, transactionHash: transactionHash ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type TransactionsView* = ref object of QObject status: Status + appService: AppService accountsView*: AccountsView transactionsView*: TransactionsView currentTransactions*: TransactionList @@ -75,9 +79,10 @@ QtObject: self.currentTransactions.delete self.QObject.delete - proc newTransactionsView*(status: Status, accountsView: AccountsView): TransactionsView = + proc newTransactionsView*(status: Status, appService: AppService, accountsView: AccountsView): TransactionsView = new(result, delete) result.status = status + result.appService = appService result.accountsView = accountsView # TODO: not ideal but a solution for now result.currentTransactions = newTransactionList() result.setup diff --git a/src/app/wallet/v2/core.nim b/src/app/wallet/v2/core.nim index 7abeebf667..358df552ed 100644 --- a/src/app/wallet/v2/core.nim +++ b/src/app/wallet/v2/core.nim @@ -2,10 +2,13 @@ import NimQml, strformat, strutils, chronicles, sugar, sequtils import view import views/[account_list, account_item] -import ../../../status/types as status_types -import ../../../status/signals/types + import ../../../status/[status, wallet2, settings] import ../../../status/wallet2/account as WalletTypes +import ../../../status/types/[transaction, setting] +import ../../../app_service/[main] +import ../../../app_service/signals/[base] +import ../../../app_service/signals/wallet as wallet_signal import ../../../eventemitter logScope: @@ -13,13 +16,15 @@ logScope: type WalletController* = ref object status: Status + appService: AppService view*: WalletView variant*: QVariant -proc newController*(status: Status): WalletController = +proc newController*(status: Status, appService: AppService): WalletController = result = WalletController() result.status = status - result.view = newWalletView(status) + result.appService = appService + result.view = newWalletView(status, appService) result.variant = newQVariant(result.view) proc delete*(self: WalletController) = diff --git a/src/app/wallet/v2/view.nim b/src/app/wallet/v2/view.nim index ad67f061e2..5f3d9505b0 100644 --- a/src/app/wallet/v2/view.nim +++ b/src/app/wallet/v2/view.nim @@ -4,11 +4,13 @@ import NimQml, chronicles, stint import ../../../status/[status, wallet2] import views/[accounts, account_list, collectibles] import views/buy_sell_crypto/[service_controller] +import ../../../app_service/[main] QtObject: type WalletView* = ref object of QAbstractListModel status: Status + appService: AppService accountsView: AccountsView collectiblesView: CollectiblesView cryptoServiceController: CryptoServiceController @@ -22,12 +24,13 @@ QtObject: proc setup(self: WalletView) = self.QAbstractListModel.setup - proc newWalletView*(status: Status): WalletView = + proc newWalletView*(status: Status, appService: AppService): WalletView = new(result, delete) result.status = status + result.appService = appService result.accountsView = newAccountsView(status) - result.collectiblesView = newCollectiblesView(status) - result.cryptoServiceController = newCryptoServiceController(status) + result.collectiblesView = newCollectiblesView(status, appService) + result.cryptoServiceController = newCryptoServiceController(status, appService) result.setup proc getAccounts(self: WalletView): QVariant {.slot.} = diff --git a/src/app/wallet/v2/views/accounts.nim b/src/app/wallet/v2/views/accounts.nim index a0c0e19189..236a3e1f21 100644 --- a/src/app/wallet/v2/views/accounts.nim +++ b/src/app/wallet/v2/views/accounts.nim @@ -1,9 +1,10 @@ import NimQml, json, sequtils, chronicles, strutils, strformat, json import - ../../../../status/[status, settings, types], - ../../../../status/signals/types as signal_types, - ../../../../status/wallet2 as status_wallet + ../../../../status/[status, settings], + ../../../../status/wallet2 as status_wallet, + ../../../../status/types/[rpc_response], + ../../../../app_service/signals/[base] import account_list, account_item diff --git a/src/app/wallet/v2/views/buy_sell_crypto/service_controller.nim b/src/app/wallet/v2/views/buy_sell_crypto/service_controller.nim index 022c0e4eda..5c2acb7203 100644 --- a/src/app/wallet/v2/views/buy_sell_crypto/service_controller.nim +++ b/src/app/wallet/v2/views/buy_sell_crypto/service_controller.nim @@ -2,6 +2,7 @@ import NimQml, json, strutils, chronicles import service_model, service_item +import ../../../../../app_service/[main] import ../../../../../status/[status, wallet2] logScope: @@ -10,6 +11,7 @@ logScope: QtObject: type CryptoServiceController* = ref object of QObject status: Status + appService: AppService cryptoServiceModel: CryptoServiceModel servicesFetched: bool @@ -20,9 +22,11 @@ QtObject: self.cryptoServiceModel.delete self.QObject.delete - proc newCryptoServiceController*(status: Status): CryptoServiceController = + proc newCryptoServiceController*(status: Status, appService: AppService): + CryptoServiceController = new(result, delete) result.status = status + result.appService = appService result.cryptoServiceModel = newCryptoServiceModel() result.servicesFetched = false result.setup @@ -37,7 +41,7 @@ QtObject: proc fetchCryptoServices*(self: CryptoServiceController) {.slot.} = if(not self.servicesFetched): - self.status.wallet2.asyncFetchCryptoServices() + self.appService.walletService.asyncFetchCryptoServices() else: self.fetchCryptoServicesFetched() diff --git a/src/app/wallet/v2/views/collectibles.nim b/src/app/wallet/v2/views/collectibles.nim index ea0594a295..87032fcb8b 100644 --- a/src/app/wallet/v2/views/collectibles.nim +++ b/src/app/wallet/v2/views/collectibles.nim @@ -1,8 +1,8 @@ import NimQml, Tables, json, chronicles -import - ../../../../status/[status, wallet2], - ../../../../status/tasks/[qt, task_runner_impl] +import ../../../../status/[status, wallet2] +import ../../../../app_service/[main] +import ../../../../app_service/tasks/[qt, threadpool] import collection_list, asset_list @@ -24,7 +24,7 @@ proc loadCollections[T](self: T, slot: string, address: string) = vptr: cast[ByteAddress](self.vptr), slot: slot, address: address, ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) type LoadAssetsTaskArg = ref object of QObjectTaskArg @@ -46,11 +46,12 @@ proc loadAssets[T](self: T, slot: string, address: string, collectionSlug: strin vptr: cast[ByteAddress](self.vptr), slot: slot, address: address, collectionSlug: collectionSlug, limit: 200 ) - self.status.tasks.threadpool.start(arg) + self.appService.threadpool.start(arg) QtObject: type CollectiblesView* = ref object of QObject status: Status + appService: AppService collections: CollectionList isLoading: bool assets: Table[string, AssetList] @@ -63,9 +64,10 @@ QtObject: list.delete self.QObject.delete - proc newCollectiblesView*(status: Status): CollectiblesView = + proc newCollectiblesView*(status: Status, appService: AppService): CollectiblesView = new(result, delete) result.status = status + result.appService = appService result.collections = newCollectionList() result.assets = initTable[string, AssetList]() result.isLoading = false diff --git a/src/status/chat/async_tasks.nim b/src/app_service/async_service/chat/async_tasks.nim similarity index 98% rename from src/status/chat/async_tasks.nim rename to src/app_service/async_service/chat/async_tasks.nim index 5f99531a77..6ee64a5760 100644 --- a/src/status/chat/async_tasks.nim +++ b/src/app_service/async_service/chat/async_tasks.nim @@ -1,4 +1,4 @@ -include ../utils/json_utils +include ../../../status/utils/json_utils type AsyncSearchMessagesTaskArg = ref object of QObjectTaskArg diff --git a/src/app_service/async_service/chat/service.nim b/src/app_service/async_service/chat/service.nim new file mode 100644 index 0000000000..a6d3de85de --- /dev/null +++ b/src/app_service/async_service/chat/service.nim @@ -0,0 +1,111 @@ +import NimQml +import json, chronicles + +import ../../tasks/[qt, threadpool] +import ../../../status/status +import ../../../status/libstatus/chat as status_chat + +include ../../../status/chat/utils +include async_tasks + +logScope: + topics = "chat-async-service" + +QtObject: + type ChatService* = ref object of QObject + status: Status + threadpool: ThreadPool + + proc setup(self: ChatService) = + self.QObject.setup + + proc delete*(self: ChatService) = + self.QObject.delete + + proc newChatService*(status: Status, threadpool: ThreadPool): ChatService = + new(result, delete) + result.status = status + result.threadpool = threadpool + result.setup() + + proc onAsyncMarkMessagesRead(self: ChatService, response: string) {.slot.} = + self.status.chat.onAsyncMarkMessagesRead(response) + + proc asyncMarkAllChannelMessagesRead*(self: ChatService, chatId: string) = + let arg = AsyncMarkAllReadTaskArg( + tptr: cast[ByteAddress](asyncMarkAllReadTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAsyncMarkMessagesRead", + chatId: chatId, + ) + self.threadpool.start(arg) + + proc onAsyncSearchMessages*(self: ChatService, response: string) {.slot.} = + self.status.chat.onAsyncSearchMessages(response) + + proc asyncSearchMessages*(self: ChatService, chatId: string, searchTerm: string, + caseSensitive: bool) = + ## Asynchronous search for messages which contain the searchTerm and belong + ## to the chat with chatId. + + if (chatId.len == 0): + info "empty channel id set for fetching more messages" + return + + if (searchTerm.len == 0): + return + + let arg = AsyncSearchMessagesInChatTaskArg( + tptr: cast[ByteAddress](asyncSearchMessagesInChatTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAsyncSearchMessages", + chatId: chatId, + searchTerm: searchTerm, + caseSensitive: caseSensitive + ) + self.threadpool.start(arg) + + proc asyncSearchMessages*(self: ChatService, communityIds: seq[string], + chatIds: seq[string], searchTerm: string, caseSensitive: bool) = + ## Asynchronous search for messages which contain the searchTerm and belong + ## to either any chat/channel from chatIds array or any channel of community + ## from communityIds array. + + if (communityIds.len == 0 and chatIds.len == 0): + info "either community ids or chat ids or both must be set" + return + + if (searchTerm.len == 0): + return + + let arg = AsyncSearchMessagesInChatsAndCommunitiesTaskArg( + tptr: cast[ByteAddress](asyncSearchMessagesInChatsAndCommunitiesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAsyncSearchMessages", + communityIds: communityIds, + chatIds: chatIds, + searchTerm: searchTerm, + caseSensitive: caseSensitive + ) + self.threadpool.start(arg) + + proc onLoadMoreMessagesForChannel*(self: ChatService, response: string) {.slot.} = + self.status.chat.onLoadMoreMessagesForChannel(response) + + proc loadMoreMessagesForChannel*(self: ChatService, channelId: string) = + if (channelId.len == 0): + info "empty channel id set for fetching more messages" + return + + let arg = AsyncFetchChatMessagesTaskArg( + tptr: cast[ByteAddress](asyncFetchChatMessagesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onLoadMoreMessagesForChannel", + chatId: channelId, + chatCursor: self.status.chat.getCurrentMessageCursor(channelId), + emojiCursor: self.status.chat.getCurrentEmojiCursor(channelId), + pinnedMsgCursor: self.status.chat.getCurrentPinnedMessageCursor(channelId), + limit: 20 + ) + + self.threadpool.start(arg) \ No newline at end of file diff --git a/src/status/wallet2/async_tasks.nim b/src/app_service/async_service/wallet/async_tasks.nim similarity index 88% rename from src/status/wallet2/async_tasks.nim rename to src/app_service/async_service/wallet/async_tasks.nim index 9c172a673b..843c2d8f8d 100644 --- a/src/status/wallet2/async_tasks.nim +++ b/src/app_service/async_service/wallet/async_tasks.nim @@ -1,4 +1,4 @@ -include ../utils/json_utils +include ../../../status/utils/json_utils ################################################# # Async request for the list of services to buy/sell crypto diff --git a/src/app_service/async_service/wallet/service.nim b/src/app_service/async_service/wallet/service.nim new file mode 100644 index 0000000000..4613601f84 --- /dev/null +++ b/src/app_service/async_service/wallet/service.nim @@ -0,0 +1,42 @@ +import NimQml +import json, chronicles + +import ../../tasks/[qt, threadpool] +import ../../../status/[status, wallet2] +import ../../../status/libstatus/wallet as status_wallet + +include async_tasks + +logScope: + topics = "wallet-async-service" + +QtObject: + type WalletService* = ref object of QObject + status: Status + threadpool: ThreadPool + + proc setup(self: WalletService) = + self.QObject.setup + + proc delete*(self: WalletService) = + self.QObject.delete + + proc newWalletService*(status: Status, threadpool: ThreadPool): WalletService = + new(result, delete) + result.status = status + result.threadpool = threadpool + result.setup() + + proc onAsyncFetchCryptoServices*(self: WalletService, response: string) {.slot.} = + self.status.wallet2.onAsyncFetchCryptoServices(response) + + proc asyncFetchCryptoServices*(self: WalletService) = + ## Asynchronous request for the list of services to buy/sell crypto. + let arg = QObjectTaskArg( + tptr: cast[ByteAddress](asyncGetCryptoServicesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAsyncFetchCryptoServices" + ) + self.threadpool.start(arg) + + \ No newline at end of file diff --git a/src/app_service/main.nim b/src/app_service/main.nim new file mode 100644 index 0000000000..1821cd9792 --- /dev/null +++ b/src/app_service/main.nim @@ -0,0 +1,49 @@ +import chronicles, task_runner +import ../status/status +import + ./tasks/marathon, + ./tasks/marathon/worker, + ./tasks/threadpool, + ./signals/signal_controller + +import service/os_notification/service as os_notification_service +import async_service/chat/service as chat_async_service +import async_service/wallet/service as wallet_async_service + +export marathon, task_runner, signal_controller +export os_notification_service +export chat_async_service, wallet_async_service + +logScope: + topics = "app-services" + +type AppService* = ref object + # foundation + threadpool*: ThreadPool + marathon*: Marathon + signalController*: SignalsController + # services + osNotificationService*: OsNotificationService + # async services + chatService*: ChatService + walletService*: WalletService + +proc newAppService*(status: Status, worker: MarathonWorker): AppService = + result = AppService() + result.threadpool = newThreadPool() + result.marathon = newMarathon(worker) + result.signalController = newSignalsController(status) + result.osNotificationService = newOsNotificationService(status) + result.chatService = newChatService(status, result.threadpool) + result.walletService = newWalletService(status, result.threadpool) + +proc delete*(self: AppService) = + self.threadpool.teardown() + self.marathon.teardown() + self.signalController.delete() + self.osNotificationService.delete() + self.chatService.delete() + self.walletService.delete() + +proc onLoggedIn*(self: AppService) = + self.marathon.onLoggedIn() diff --git a/src/app_service/service/os_notification/service.nim b/src/app_service/service/os_notification/service.nim new file mode 100644 index 0000000000..75c9100c2e --- /dev/null +++ b/src/app_service/service/os_notification/service.nim @@ -0,0 +1,48 @@ +import NimQml, json, chronicles + +import ../../../status/[status] +import ../../../status/notifications/[os_notifications] +import ../../../status/types/[os_notification] + +logScope: + topics = "os-notification-service" + +QtObject: + type OsNotificationService* = ref object of QObject + status: Status + notification: StatusOSNotificationObject + + proc setup(self: OsNotificationService, status: Status) = + self.QObject.setup + self.status = status + self.notification = newStatusOSNotificationObject() + signalConnect(self.notification, "notificationClicked(QString)", self, + "onNotificationClicked(QString)", 2) + + proc delete*(self: OsNotificationService) = + self.notification.delete + self.QObject.delete + + proc newOsNotificationService*(status: Status): OsNotificationService = + new(result, delete) + result.setup(status) + + proc showNotification*(self: OsNotificationService, title: string, + message: string, details: OsNotificationDetails, useOSNotifications: bool) = + ## This method will add new notification to the Notification center. Param + ## "details" is used to uniquely define a notification bubble. + + # Whether we need to use OS notifications or not should be checked only here, + # but because we don't have settings class on the nim side yet, we're using + # useOSNotifications param sent from the qml side. Once we are able to check + # that here, we will remove useOSNotifications param from this method. + if(not useOSNotifications): + return + + let identifier = $(details.toJsonNode()) + self.notification.showNotification(title, message, identifier) + + proc onNotificationClicked*(self: OsNotificationService, identifier: string) {.slot.} = + ## This slot is called once user clicks a notificaiton bubble, "identifier" + ## contains data which uniquely define that notification. + self.status.osnotifications.onNotificationClicked(identifier) \ No newline at end of file diff --git a/src/app_service/signals/base.nim b/src/app_service/signals/base.nim new file mode 100644 index 0000000000..f2a7ec7851 --- /dev/null +++ b/src/app_service/signals/base.nim @@ -0,0 +1,15 @@ +import json_serialization +import signal_type + +import ../../eventemitter + +export signal_type + +type Signal* = ref object of Args + signalType* {.serializedFieldName("type").}: SignalType + +type StatusGoError* = object + error*: string + +type NodeSignal* = ref object of Signal + event*: StatusGoError \ No newline at end of file diff --git a/src/app_service/signals/community.nim b/src/app_service/signals/community.nim new file mode 100644 index 0000000000..cfb318a84c --- /dev/null +++ b/src/app_service/signals/community.nim @@ -0,0 +1,13 @@ +import json + +import base + +import ../../status/types/community + +type CommunitySignal* = ref object of Signal + community*: Community + +proc fromEvent*(event: JsonNode): Signal = + var signal: CommunitySignal = CommunitySignal() + signal.community = event["event"].toCommunity() + result = signal \ No newline at end of file diff --git a/src/status/signals/discovery.nim b/src/app_service/signals/discovery_summary.nim similarity index 71% rename from src/status/signals/discovery.nim rename to src/app_service/signals/discovery_summary.nim index bd54603cda..e52187f838 100644 --- a/src/status/signals/discovery.nim +++ b/src/app_service/signals/discovery_summary.nim @@ -1,10 +1,13 @@ import json -import types + +import base + +type DiscoverySummarySignal* = ref object of Signal + enodes*: seq[string] proc fromEvent*(jsonSignal: JsonNode): Signal = var signal:DiscoverySummarySignal = DiscoverySummarySignal() if jsonSignal["event"].kind != JNull: for discoveryItem in jsonSignal["event"]: signal.enodes.add(discoveryItem["enode"].getStr) - result = signal - \ No newline at end of file + result = signal \ No newline at end of file diff --git a/src/status/signals/envelopes.nim b/src/app_service/signals/envelope.nim similarity index 79% rename from src/status/signals/envelopes.nim rename to src/app_service/signals/envelope.nim index 2f268a5faa..0dbfa901f7 100644 --- a/src/status/signals/envelopes.nim +++ b/src/app_service/signals/envelope.nim @@ -1,5 +1,9 @@ import json -import types + +import base + +type EnvelopeSentSignal* = ref object of Signal + messageIds*: seq[string] proc fromEvent*(jsonSignal: JsonNode): Signal = var signal:EnvelopeSentSignal = EnvelopeSentSignal() diff --git a/src/status/signals/expired.nim b/src/app_service/signals/expired.nim similarity index 79% rename from src/status/signals/expired.nim rename to src/app_service/signals/expired.nim index 7438445fe6..e30a5a4f41 100644 --- a/src/status/signals/expired.nim +++ b/src/app_service/signals/expired.nim @@ -1,5 +1,9 @@ import json -import types + +import base + +type EnvelopeExpiredSignal* = ref object of Signal + messageIds*: seq[string] proc fromEvent*(jsonSignal: JsonNode): Signal = var signal:EnvelopeExpiredSignal = EnvelopeExpiredSignal() diff --git a/src/status/signals/mailserver.nim b/src/app_service/signals/mailserver.nim similarity index 73% rename from src/status/signals/mailserver.nim rename to src/app_service/signals/mailserver.nim index 2b6b357945..e6546fbc2f 100644 --- a/src/status/signals/mailserver.nim +++ b/src/app_service/signals/mailserver.nim @@ -1,5 +1,16 @@ import json -import types + +import base + +type MailserverRequestCompletedSignal* = ref object of Signal + requestID*: string + lastEnvelopeHash*: string + cursor*: string + errorMessage*: string + error*: bool + +type MailserverRequestExpiredSignal* = ref object of Signal + # TODO proc fromCompletedEvent*(jsonSignal: JsonNode): Signal = var signal:MailserverRequestCompletedSignal = MailserverRequestCompletedSignal() diff --git a/src/app_service/signals/messages.nim b/src/app_service/signals/messages.nim new file mode 100644 index 0000000000..f19c3038d7 --- /dev/null +++ b/src/app_service/signals/messages.nim @@ -0,0 +1,95 @@ +import json, chronicles + +import base + +import ../../status/types/[message, chat, community, profile, installation, + activity_center_notification, status_update, removed_message] + +type MessageSignal* = ref object of Signal + messages*: seq[Message] + pinnedMessages*: seq[Message] + chats*: seq[Chat] + contacts*: seq[Profile] + installations*: seq[Installation] + emojiReactions*: seq[Reaction] + communities*: seq[Community] + membershipRequests*: seq[CommunityMembershipRequest] + activityCenterNotification*: seq[ActivityCenterNotification] + statusUpdates*: seq[StatusUpdate] + deletedMessages*: seq[RemovedMessage] + +proc fromEvent*(event: JsonNode): Signal = + var signal:MessageSignal = MessageSignal() + signal.messages = @[] + signal.contacts = @[] + + if event["event"]{"contacts"} != nil: + for jsonContact in event["event"]["contacts"]: + signal.contacts.add(jsonContact.toProfileModel()) + + var chatsWithMentions: seq[string] = @[] + + if event["event"]{"messages"} != nil: + for jsonMsg in event["event"]["messages"]: + var message = jsonMsg.toMessage() + if message.hasMention: + chatsWithMentions.add(message.chatId) + signal.messages.add(message) + + if event["event"]{"chats"} != nil: + for jsonChat in event["event"]["chats"]: + var chat = jsonChat.toChat + if chatsWithMentions.contains(chat.id): + chat.mentionsCount.inc + signal.chats.add(chat) + + if event["event"]{"statusUpdates"} != nil: + for jsonStatusUpdate in event["event"]["statusUpdates"]: + var statusUpdate = jsonStatusUpdate.toStatusUpdate + signal.statusUpdates.add(statusUpdate) + + if event["event"]{"installations"} != nil: + for jsonInstallation in event["event"]["installations"]: + signal.installations.add(jsonInstallation.toInstallation) + + if event["event"]{"emojiReactions"} != nil: + for jsonReaction in event["event"]["emojiReactions"]: + signal.emojiReactions.add(jsonReaction.toReaction) + + if event["event"]{"communities"} != nil: + for jsonCommunity in event["event"]["communities"]: + signal.communities.add(jsonCommunity.toCommunity) + + if event["event"]{"requestsToJoinCommunity"} != nil: + for jsonCommunity in event["event"]["requestsToJoinCommunity"]: + signal.membershipRequests.add(jsonCommunity.toCommunityMembershipRequest) + + if event["event"]{"removedMessages"} != nil: + for jsonRemovedMessage in event["event"]["removedMessages"]: + signal.deletedMessages.add(jsonRemovedMessage.toRemovedMessage) + + if event["event"]{"activityCenterNotifications"} != nil: + for jsonNotification in event["event"]["activityCenterNotifications"]: + signal.activityCenterNotification.add(jsonNotification.toActivityCenterNotification()) + + if event["event"]{"pinMessages"} != nil: + for jsonPinnedMessage in event["event"]["pinMessages"]: + var contentType: ContentType + try: + contentType = ContentType(jsonPinnedMessage{"contentType"}.getInt) + except: + warn "Unknown content type received", type = jsonPinnedMessage{"contentType"}.getInt + contentType = ContentType.Message + signal.pinnedMessages.add(Message( + id: jsonPinnedMessage{"message_id"}.getStr, + chatId: jsonPinnedMessage{"chat_id"}.getStr, + localChatId: jsonPinnedMessage{"localChatId"}.getStr, + pinnedBy: jsonPinnedMessage{"from"}.getStr, + identicon: jsonPinnedMessage{"identicon"}.getStr, + alias: jsonPinnedMessage{"alias"}.getStr, + clock: jsonPinnedMessage{"clock"}.getInt, + isPinned: jsonPinnedMessage{"pinned"}.getBool, + contentType: contentType + )) + + result = signal \ No newline at end of file diff --git a/src/status/signals/core.nim b/src/app_service/signals/signal_controller.nim similarity index 81% rename from src/status/signals/core.nim rename to src/app_service/signals/signal_controller.nim index 54ee3440b9..18417f339f 100644 --- a/src/status/signals/core.nim +++ b/src/app_service/signals/signal_controller.nim @@ -1,7 +1,7 @@ -import NimQml, tables, json, chronicles, strutils, json_serialization -import ../types as status_types -import types, messages, discovery, whisperFilter, envelopes, expired, wallet, mailserver, communities, stats -import ../status +import NimQml, json, chronicles, strutils, json_serialization +import base, signal_type, messages, discovery_summary, whisper_filter, envelope, expired, + wallet, mailserver, community, stats +import ../../status/status import ../../eventemitter logScope: @@ -12,7 +12,7 @@ QtObject: variant*: QVariant status*: Status - proc newController*(status: Status): SignalsController = + proc newSignalsController*(status: Status): SignalsController = new(result) result.status = status result.setup() @@ -48,15 +48,15 @@ QtObject: var signal: Signal = case signalType: of SignalType.Message: messages.fromEvent(jsonSignal) - of SignalType.EnvelopeSent: envelopes.fromEvent(jsonSignal) + of SignalType.EnvelopeSent: envelope.fromEvent(jsonSignal) of SignalType.EnvelopeExpired: expired.fromEvent(jsonSignal) of SignalType.WhisperFilterAdded: whisperFilter.fromEvent(jsonSignal) of SignalType.Wallet: wallet.fromEvent(jsonSignal) of SignalType.NodeLogin: Json.decode($jsonSignal, NodeSignal) - of SignalType.DiscoverySummary: discovery.fromEvent(jsonSignal) + of SignalType.DiscoverySummary: discovery_summary.fromEvent(jsonSignal) of SignalType.MailserverRequestCompleted: mailserver.fromCompletedEvent(jsonSignal) of SignalType.MailserverRequestExpired: mailserver.fromExpiredEvent(jsonSignal) - of SignalType.CommunityFound: communities.fromEvent(jsonSignal) + of SignalType.CommunityFound: community.fromEvent(jsonSignal) of SignalType.Stats: stats.fromEvent(jsonSignal) else: Signal() diff --git a/src/app_service/signals/signal_type.nim b/src/app_service/signals/signal_type.nim new file mode 100644 index 0000000000..0a48eac022 --- /dev/null +++ b/src/app_service/signals/signal_type.nim @@ -0,0 +1,26 @@ +{.used.} + +type SignalType* {.pure.} = enum + Message = "messages.new" + Wallet = "wallet" + NodeReady = "node.ready" + NodeCrashed = "node.crashed" + NodeStarted = "node.started" + NodeStopped = "node.stopped" + NodeLogin = "node.login" + EnvelopeSent = "envelope.sent" + EnvelopeExpired = "envelope.expired" + MailserverRequestCompleted = "mailserver.request.completed" + MailserverRequestExpired = "mailserver.request.expired" + DiscoveryStarted = "discovery.started" + DiscoveryStopped = "discovery.stopped" + DiscoverySummary = "discovery.summary" + SubscriptionsData = "subscriptions.data" + SubscriptionsError = "subscriptions.error" + WhisperFilterAdded = "whisper.filter.added" + CommunityFound = "community.found" + Stats = "stats" + Unknown + +proc event*(self:SignalType):string = + result = "signal:" & $self \ No newline at end of file diff --git a/src/status/signals/stats.nim b/src/app_service/signals/stats.nim similarity index 67% rename from src/status/signals/stats.nim rename to src/app_service/signals/stats.nim index 9522651947..5b88edf8d2 100644 --- a/src/status/signals/stats.nim +++ b/src/app_service/signals/stats.nim @@ -1,5 +1,13 @@ import json -import types + +import base + +type Stats* = object + uploadRate*: uint64 + downloadRate*: uint64 + +type StatsSignal* = ref object of Signal + stats*: Stats proc toStats(jsonMsg: JsonNode): Stats = result = Stats( diff --git a/src/status/signals/wallet.nim b/src/app_service/signals/wallet.nim similarity index 75% rename from src/status/signals/wallet.nim rename to src/app_service/signals/wallet.nim index 34020aaff6..8d52bb1d98 100644 --- a/src/status/signals/wallet.nim +++ b/src/app_service/signals/wallet.nim @@ -1,5 +1,14 @@ import json -import types + +import base + +type WalletSignal* = ref object of Signal + content*: string + eventType*: string + blockNumber*: int + accounts*: seq[string] + # newTransactions*: ??? + erc20*: bool proc fromEvent*(jsonSignal: JsonNode): Signal = var signal:WalletSignal = WalletSignal() diff --git a/src/status/signals/whisperFilter.nim b/src/app_service/signals/whisper_filter.nim similarity index 69% rename from src/status/signals/whisperFilter.nim rename to src/app_service/signals/whisper_filter.nim index daafede54b..f8f1336165 100644 --- a/src/status/signals/whisperFilter.nim +++ b/src/app_service/signals/whisper_filter.nim @@ -1,5 +1,17 @@ import json -import types + +import base + +type Filter* = object + chatId*: string + symKeyId*: string + listen*: bool + filterId*: string + identity*: string + topic*: string + +type WhisperFilterSignal* = ref object of Signal + filters*: seq[Filter] proc toFilter(jsonMsg: JsonNode): Filter = result = Filter( diff --git a/src/status/tasks/common.nim b/src/app_service/tasks/common.nim similarity index 100% rename from src/status/tasks/common.nim rename to src/app_service/tasks/common.nim diff --git a/src/status/tasks/marathon.nim b/src/app_service/tasks/marathon.nim similarity index 84% rename from src/status/tasks/marathon.nim rename to src/app_service/tasks/marathon.nim index 565cc42119..c5c191b432 100644 --- a/src/status/tasks/marathon.nim +++ b/src/app_service/tasks/marathon.nim @@ -18,22 +18,21 @@ type proc start*[T: MarathonTaskArg](self: MarathonWorker, arg: T) = self.chanSendToWorker.sendSync(arg.encode.safe) -proc newMarathon*(): Marathon = +proc init(self: Marathon) = + for worker in self.workers.values: + worker.init() + +proc newMarathon*(worker: MarathonWorker): Marathon = new(result) result.workers = initTable[string, MarathonWorker]() - -proc registerWorker*(self: Marathon, worker: MarathonWorker) = - self.workers[worker.name] = worker # overwrite if exists + result.workers[worker.name] = worker + result.init() proc `[]`*(self: Marathon, name: string): MarathonWorker = if not self.workers.contains(name): raise newException(ValueError, &"""Worker '{name}' is not registered. Use 'registerWorker("{name}", {name}Worker)' to register the worker first.""") self.workers[name] -proc init*(self: Marathon) = - for worker in self.workers.values: - worker.init() - proc teardown*(self: Marathon) = for worker in self.workers.values: worker.teardown() diff --git a/src/status/tasks/marathon/common.nim b/src/app_service/tasks/marathon/common.nim similarity index 100% rename from src/status/tasks/marathon/common.nim rename to src/app_service/tasks/marathon/common.nim diff --git a/src/status/tasks/marathon/mailserver/controller.nim b/src/app_service/tasks/marathon/mailserver/controller.nim similarity index 91% rename from src/status/tasks/marathon/mailserver/controller.nim rename to src/app_service/tasks/marathon/mailserver/controller.nim index db4d71c494..5b7de1e29e 100644 --- a/src/status/tasks/marathon/mailserver/controller.nim +++ b/src/app_service/tasks/marathon/mailserver/controller.nim @@ -5,7 +5,7 @@ import # vendor libs chronicles, NimQml, json_serialization import # status-desktop libs - ../../../status, ../../common as task_runner_common, ./events + ../../../../status/status, ../../common as task_runner_common, ./events logScope: topics = "mailserver controller" diff --git a/src/status/tasks/marathon/mailserver/events.nim b/src/app_service/tasks/marathon/mailserver/events.nim similarity index 100% rename from src/status/tasks/marathon/mailserver/events.nim rename to src/app_service/tasks/marathon/mailserver/events.nim diff --git a/src/status/tasks/marathon/mailserver/model.nim b/src/app_service/tasks/marathon/mailserver/model.nim similarity index 97% rename from src/status/tasks/marathon/mailserver/model.nim rename to src/app_service/tasks/marathon/mailserver/model.nim index 58ff945951..859dc8af04 100644 --- a/src/status/tasks/marathon/mailserver/model.nim +++ b/src/app_service/tasks/marathon/mailserver/model.nim @@ -4,11 +4,11 @@ import from times import cpuTime import - ../../../libstatus/settings as status_settings, - ../../../libstatus/chat as status_chat, - ../../../libstatus/mailservers as status_mailservers, - ../../../libstatus/core as status_core, - ../../../types, ../../../fleet, + ../../../../status/libstatus/settings as status_settings, + ../../../../status/libstatus/chat as status_chat, + ../../../../status/libstatus/mailservers as status_mailservers, + ../../../../status/libstatus/core as status_core, + ../../../../status/fleet, ./events as mailserver_events logScope: diff --git a/src/status/tasks/marathon/mailserver/worker.nim b/src/app_service/tasks/marathon/mailserver/worker.nim similarity index 97% rename from src/status/tasks/marathon/mailserver/worker.nim rename to src/app_service/tasks/marathon/mailserver/worker.nim index 9896f869ce..4747172e35 100644 --- a/src/status/tasks/marathon/mailserver/worker.nim +++ b/src/app_service/tasks/marathon/mailserver/worker.nim @@ -7,7 +7,7 @@ import # vendor libs import # status-desktop libs ../worker, ./model, ../../qt, ../../common as task_runner_common, ../common as methuselash_common, - ../../../libstatus/mailservers # TODO: needed for MailserverTopic type, remove? + ../../../../status/libstatus/mailservers # TODO: needed for MailserverTopic type, remove? export chronos, task_runner_common, json_serialization diff --git a/src/status/tasks/marathon/worker.nim b/src/app_service/tasks/marathon/worker.nim similarity index 100% rename from src/status/tasks/marathon/worker.nim rename to src/app_service/tasks/marathon/worker.nim diff --git a/src/status/tasks/qt.nim b/src/app_service/tasks/qt.nim similarity index 100% rename from src/status/tasks/qt.nim rename to src/app_service/tasks/qt.nim diff --git a/src/status/tasks/threadpool.nim b/src/app_service/tasks/threadpool.nim similarity index 99% rename from src/status/tasks/threadpool.nim rename to src/app_service/tasks/threadpool.nim index fcb614cbf2..6493815555 100644 --- a/src/status/tasks/threadpool.nim +++ b/src/app_service/tasks/threadpool.nim @@ -37,15 +37,7 @@ proc poolThread(arg: PoolThreadArg) {.thread.} const MaxThreadPoolSize = 16 -proc newThreadPool*(size: int = MaxThreadPoolSize): ThreadPool = - new(result) - result.chanRecvFromPool = newAsyncChannel[ThreadSafeString](-1) - result.chanSendToPool = newAsyncChannel[ThreadSafeString](-1) - result.thread = Thread[PoolThreadArg]() - result.size = size - result.running.store(false) - -proc init*(self: ThreadPool) = +proc init(self: ThreadPool) = self.chanRecvFromPool.open() self.chanSendToPool.open() let arg = PoolThreadArg( @@ -57,6 +49,15 @@ proc init*(self: ThreadPool) = # block until we receive "ready" discard $(self.chanRecvFromPool.recvSync()) +proc newThreadPool*(size: int = MaxThreadPoolSize): ThreadPool = + new(result) + result.chanRecvFromPool = newAsyncChannel[ThreadSafeString](-1) + result.chanSendToPool = newAsyncChannel[ThreadSafeString](-1) + result.thread = Thread[PoolThreadArg]() + result.size = size + result.running.store(false) + result.init() + proc teardown*(self: ThreadPool) = self.running.store(false) self.chanSendToPool.sendSync("shutdown".safe) diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index 8a8798d41f..dfdbda8f76 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -10,14 +10,14 @@ import app/profile/core as profile import app/onboarding/core as onboarding import app/login/core as login import app/provider/core as provider -import status/signals/core as signals -import status/types +import status/types/[account] import status/constants import status_go import status/status as statuslib -import ./eventemitter -import ./status/tasks/marathon/mailserver/controller as mailserver_controller -import ./status/tasks/marathon/mailserver/worker as mailserver_worker +import eventemitter +import app_service/tasks/marathon/mailserver/controller as mailserver_controller +import app_service/tasks/marathon/mailserver/worker as mailserver_worker +import app_service/main var signalsQObjPointer: pointer @@ -45,8 +45,9 @@ proc mainProc() = mailserverController = mailserver_controller.newController(status) mailserverWorker = mailserver_worker.newMailserverWorker(cast[ByteAddress](mailserverController.vptr)) - # TODO: create and register an ipcWorker - status.tasks.marathon.registerWorker(mailserverWorker) + let appService = newAppService(status, mailserverWorker) + defer: appService.delete() + status.initNode() let uiScaleFilePath = joinPath(DATADIR, "ui-scale") @@ -119,32 +120,30 @@ proc mainProc() = netAccMgr.clearConnectionCache() netAccMgr.setNetworkAccessible(NetworkAccessibility.Accessible) - let signalController = signals.newController(status) - defer: - signalsQObjPointer = nil - signalController.delete() # We need this global variable in order to be able to access the application # from the non-closure callback passed to `libstatus.setSignalEventCallback` - signalsQObjPointer = cast[pointer](signalController.vptr) + signalsQObjPointer = cast[pointer](appService.signalController.vptr) + defer: + signalsQObjPointer = nil - var wallet = wallet.newController(status) + var wallet = wallet.newController(status, appService) defer: wallet.delete() engine.setRootContextProperty("walletModel", wallet.variant) - var wallet2 = walletV2.newController(status) + var wallet2 = walletV2.newController(status, appService) defer: wallet2.delete() engine.setRootContextProperty("walletV2Model", wallet2.variant) - var chat = chat.newController(status) + var chat = chat.newController(status, appService) defer: chat.delete() engine.setRootContextProperty("chatsModel", chat.variant) - var node = node.newController(status, netAccMgr) + var node = node.newController(status, appService, netAccMgr) defer: node.delete() engine.setRootContextProperty("nodeModel", node.variant) - var utilsController = utilsView.newController(status) + var utilsController = utilsView.newController(status, appService) defer: utilsController.delete() engine.setRootContextProperty("utilsModel", utilsController.variant) @@ -159,7 +158,7 @@ proc mainProc() = let shouldRetranslate = not defined(linux) engine.setTranslationPackage(joinPath(i18nPath, fmt"qml_{locale}.qm"), shouldRetranslate) - var profile = profile.newController(status, changeLanguage) + var profile = profile.newController(status, appService, changeLanguage) defer: profile.delete() engine.setRootContextProperty("profileModel", profile.variant) @@ -175,7 +174,7 @@ proc mainProc() = status.events.once("login") do(a: Args): var args = AccountArgs(a) - status.tasks.marathon.onLoggedIn() + appService.onLoggedIn() # Reset login and onboarding to remove any mnemonic that would have been saved in the accounts list login.reset() @@ -202,7 +201,6 @@ proc mainProc() = # this should be the last defer in the scope defer: info "Status app is shutting down..." - status.tasks.teardown() engine.setRootContextProperty("loginModel", login.variant) engine.setRootContextProperty("onboardingModel", onboarding.variant) @@ -237,7 +235,7 @@ proc mainProc() = # 2. Re-init controllers that don't require a running node initControllers() - engine.setRootContextProperty("signals", signalController.variant) + engine.setRootContextProperty("signals", appService.signalController.variant) engine.setRootContextProperty("mailserver", mailserverController.variant) var prValue = newQVariant(if defined(production): true else: false) diff --git a/src/status/accounts.nim b/src/status/accounts.nim index 84a6215efc..f74ca8078a 100644 --- a/src/status/accounts.nim +++ b/src/status/accounts.nim @@ -1,7 +1,7 @@ import options, chronicles, json, json_serialization, sequtils, sugar import libstatus/accounts as status_accounts import libstatus/settings as status_settings -import types +import ./types/[account, fleet, sticker, setting] import utils import ../eventemitter diff --git a/src/status/browser.nim b/src/status/browser.nim index ece17c9d5f..626bef20e9 100644 --- a/src/status/browser.nim +++ b/src/status/browser.nim @@ -1,10 +1,7 @@ -import json - import libstatus/browser as status_browser import ../eventemitter -#TODO: temporary? -import types as LibStatusTypes +import ./types/[bookmark] type BrowserModel* = ref object diff --git a/src/status/chat.nim b/src/status/chat.nim index 8cc8a1302c..0f1e0551a6 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -1,21 +1,18 @@ -import NimQml import json, strutils, sequtils, tables, chronicles, times, sugar import libstatus/chat as status_chat import libstatus/chatCommands as status_chat_commands -import types +import types/[message, status_update, activity_center_notification, + sticker, removed_message] import utils as status_utils import stickers import ../eventemitter import profile/profile import contacts -import chat/[chat, message] -import tasks/[qt, task_runner_impl] -import signals/messages +import chat/[chat, utils] import ens, accounts -include chat/utils -include chat/async_tasks +include utils/json_utils logScope: topics = "chat-model" @@ -72,774 +69,695 @@ type channelId*: string notificationTypes*: seq[ActivityCenterNotificationType] -QtObject: - type ChatModel* = ref object of QObject - publicKey*: string - events*: EventEmitter - tasks*: TaskRunner - communitiesToFetch*: seq[string] - mailserverReady*: bool - contacts*: Table[string, Profile] - channels*: Table[string, Chat] - msgCursor: Table[string, string] - pinnedMsgCursor: Table[string, string] - activityCenterCursor*: string - emojiCursor: Table[string, string] - lastMessageTimestamps*: Table[string, int64] - - proc setup(self: ChatModel) = - self.QObject.setup +type ChatModel* = ref object + publicKey*: string + events*: EventEmitter + communitiesToFetch*: seq[string] + mailserverReady*: bool + contacts*: Table[string, Profile] + channels*: Table[string, Chat] + msgCursor: Table[string, string] + pinnedMsgCursor: Table[string, string] + activityCenterCursor*: string + emojiCursor: Table[string, string] + lastMessageTimestamps*: Table[string, int64] - proc delete*(self: ChatModel) = - self.QObject.delete +proc newChatModel*(events: EventEmitter): ChatModel = + result = ChatModel() + result.events = events + result.mailserverReady = false + result.communitiesToFetch = @[] + result.contacts = initTable[string, Profile]() + result.channels = initTable[string, Chat]() + result.msgCursor = initTable[string, string]() + result.pinnedMsgCursor = initTable[string, string]() + result.emojiCursor = initTable[string, string]() + result.lastMessageTimestamps = initTable[string, int64]() - proc newChatModel*(events: EventEmitter, tasks: TaskRunner): ChatModel = - new(result, delete) - result.events = events - result.tasks = tasks - result.mailserverReady = false - result.communitiesToFetch = @[] - result.contacts = initTable[string, Profile]() - result.channels = initTable[string, Chat]() - result.msgCursor = initTable[string, string]() - result.pinnedMsgCursor = initTable[string, string]() - result.emojiCursor = initTable[string, string]() - result.lastMessageTimestamps = initTable[string, int64]() - - result.setup() +proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community], communityMembershipRequests: seq[CommunityMembershipRequest], pinnedMessages: seq[Message], activityCenterNotifications: seq[ActivityCenterNotification], statusUpdates: seq[StatusUpdate], deletedMessages: seq[RemovedMessage]) = + for chat in chats: + self.channels[chat.id] = chat - proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community], communityMembershipRequests: seq[CommunityMembershipRequest], pinnedMessages: seq[Message], activityCenterNotifications: seq[ActivityCenterNotification], statusUpdates: seq[StatusUpdate], deletedMessages: seq[RemovedMessage]) = - for chat in chats: - self.channels[chat.id] = chat - - for message in messages: - let chatId = message.chatId - let ts = times.convert(Milliseconds, Seconds, message.whisperTimestamp.parseInt()) - if not self.lastMessageTimestamps.hasKey(chatId): - self.lastMessageTimestamps[chatId] = ts - else: - if self.lastMessageTimestamps[chatId] > ts: - self.lastMessageTimestamps[chatId] = ts - - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages,chats: chats, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests, pinnedMessages: pinnedMessages, activityCenterNotifications: activityCenterNotifications, statusUpdates: statusUpdates, deletedMessages: deletedMessages)) - - proc parseChatResponse(self: ChatModel, response: string): (seq[Chat], seq[Message]) = - var parsedResponse = parseJson(response) - var chats: seq[Chat] = @[] - var messages: seq[Message] = @[] - if parsedResponse{"result"}{"messages"} != nil: - for jsonMsg in parsedResponse["result"]["messages"]: - messages.add(jsonMsg.toMessage()) - if parsedResponse{"result"}{"chats"} != nil: - for jsonChat in parsedResponse["result"]["chats"]: - let chat = jsonChat.toChat - self.channels[chat.id] = chat - chats.add(chat) - result = (chats, messages) - - proc emitUpdate(self: ChatModel, response: string) = - var (chats, messages) = self.parseChatResponse(response) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) - - proc removeFiltersByChatId(self: ChatModel, chatId: string, filters: JsonNode) - - proc removeChatFilters(self: ChatModel, chatId: string) = - # TODO: this code should be handled by status-go / stimbus instead of the client - # Clients should not have to care about filters. For more info about filters: - # https://github.com/status-im/specs/blob/master/docs/stable/3-whisper-usage.md#keys-management - let filters = parseJson(status_chat.loadFilters(@[]))["result"] - - case self.channels[chatId].chatType - of ChatType.Public: - for filter in filters: - if filter["chatId"].getStr == chatId: - status_chat.removeFilters(chatId, filter["filterId"].getStr) - of ChatType.OneToOne, ChatType.Profile: - # Check if user does not belong to any active chat group - var inGroup = false - for channel in self.channels.values: - if channel.isActive and channel.id != chatId and channel.chatType == ChatType.PrivateGroupChat: - inGroup = true - break - if not inGroup: self.removeFiltersByChatId(chatId, filters) - of ChatType.PrivateGroupChat: - for member in self.channels[chatId].members: - # Check that any of the members are not in other active group chats, or that you don’t have a one-to-one open. - var hasConversation = false - for channel in self.channels.values: - if (channel.isActive and channel.chatType == ChatType.OneToOne and channel.id == member.id) or - (channel.isActive and channel.id != chatId and channel.chatType == ChatType.PrivateGroupChat and channel.isMember(member.id)): - hasConversation = true - break - if not hasConversation: self.removeFiltersByChatId(member.id, filters) + for message in messages: + let chatId = message.chatId + let ts = times.convert(Milliseconds, Seconds, message.whisperTimestamp.parseInt()) + if not self.lastMessageTimestamps.hasKey(chatId): + self.lastMessageTimestamps[chatId] = ts else: - error "Unknown chat type removed", chatId + if self.lastMessageTimestamps[chatId] > ts: + self.lastMessageTimestamps[chatId] = ts + + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages,chats: chats, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests, pinnedMessages: pinnedMessages, activityCenterNotifications: activityCenterNotifications, statusUpdates: statusUpdates, deletedMessages: deletedMessages)) - proc removeFiltersByChatId(self: ChatModel, chatId: string, filters: JsonNode) = - var partitionedTopic = "" +proc parseChatResponse(self: ChatModel, response: string): (seq[Chat], seq[Message]) = + var parsedResponse = parseJson(response) + var chats: seq[Chat] = @[] + var messages: seq[Message] = @[] + if parsedResponse{"result"}{"messages"} != nil: + for jsonMsg in parsedResponse["result"]["messages"]: + messages.add(jsonMsg.toMessage()) + if parsedResponse{"result"}{"chats"} != nil: + for jsonChat in parsedResponse["result"]["chats"]: + let chat = jsonChat.toChat + self.channels[chat.id] = chat + chats.add(chat) + result = (chats, messages) + +proc emitUpdate(self: ChatModel, response: string) = + var (chats, messages) = self.parseChatResponse(response) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + +proc removeFiltersByChatId(self: ChatModel, chatId: string, filters: JsonNode) + +proc removeChatFilters(self: ChatModel, chatId: string) = + # TODO: this code should be handled by status-go / stimbus instead of the client + # Clients should not have to care about filters. For more info about filters: + # https://github.com/status-im/specs/blob/master/docs/stable/3-whisper-usage.md#keys-management + let filters = parseJson(status_chat.loadFilters(@[]))["result"] + + case self.channels[chatId].chatType + of ChatType.Public: for filter in filters: - # Contact code filter should be removed - if filter["identity"].getStr == chatId and filter["chatId"].getStr.endsWith("-contact-code"): + if filter["chatId"].getStr == chatId: + status_chat.removeFilters(chatId, filter["filterId"].getStr) + of ChatType.OneToOne, ChatType.Profile: + # Check if user does not belong to any active chat group + var inGroup = false + for channel in self.channels.values: + if channel.isActive and channel.id != chatId and channel.chatType == ChatType.PrivateGroupChat: + inGroup = true + break + if not inGroup: self.removeFiltersByChatId(chatId, filters) + of ChatType.PrivateGroupChat: + for member in self.channels[chatId].members: + # Check that any of the members are not in other active group chats, or that you don’t have a one-to-one open. + var hasConversation = false + for channel in self.channels.values: + if (channel.isActive and channel.chatType == ChatType.OneToOne and channel.id == member.id) or + (channel.isActive and channel.id != chatId and channel.chatType == ChatType.PrivateGroupChat and channel.isMember(member.id)): + hasConversation = true + break + if not hasConversation: self.removeFiltersByChatId(member.id, filters) + else: + error "Unknown chat type removed", chatId + +proc removeFiltersByChatId(self: ChatModel, chatId: string, filters: JsonNode) = + var partitionedTopic = "" + for filter in filters: + # Contact code filter should be removed + if filter["identity"].getStr == chatId and filter["chatId"].getStr.endsWith("-contact-code"): + status_chat.removeFilters(chatId, filter["filterId"].getStr) + + # Remove partitioned topic if no other user in an active group chat or one-to-one is from the + # same partitioned topic + if filter["identity"].getStr == chatId and filter["chatId"].getStr.startsWith("contact-discovery-"): + partitionedTopic = filter["topic"].getStr + var samePartitionedTopic = false + for f in filters.filterIt(it["topic"].getStr == partitionedTopic and it["filterId"].getStr != filter["filterId"].getStr): + let fIdentity = f["identity"].getStr; + if self.channels.hasKey(fIdentity) and self.channels[fIdentity].isActive: + samePartitionedTopic = true + break + if not samePartitionedTopic: status_chat.removeFilters(chatId, filter["filterId"].getStr) - # Remove partitioned topic if no other user in an active group chat or one-to-one is from the - # same partitioned topic - if filter["identity"].getStr == chatId and filter["chatId"].getStr.startsWith("contact-discovery-"): - partitionedTopic = filter["topic"].getStr - var samePartitionedTopic = false - for f in filters.filterIt(it["topic"].getStr == partitionedTopic and it["filterId"].getStr != filter["filterId"].getStr): - let fIdentity = f["identity"].getStr; - if self.channels.hasKey(fIdentity) and self.channels[fIdentity].isActive: - samePartitionedTopic = true - break - if not samePartitionedTopic: - status_chat.removeFilters(chatId, filter["filterId"].getStr) - - proc hasChannel*(self: ChatModel, chatId: string): bool = - self.channels.hasKey(chatId) - - proc getActiveChannel*(self: ChatModel): string = - if (self.channels.len == 0): "" else: toSeq(self.channels.values)[self.channels.len - 1].id - - proc emitTopicAndJoin(self: ChatModel, chat: Chat) = - let filterResult = status_chat.loadFilters(@[status_chat.buildFilter(chat)]) - self.events.emit("channelJoined", ChannelArgs(chat: chat)) - - proc join*(self: ChatModel, chatId: string, chatType: ChatType, ensName: string = "", pubKey: string = "") = - if self.hasChannel(chatId): return - var chat = newChat(chatId, ChatType(chatType)) - self.channels[chat.id] = chat - status_chat.saveChat(chatId, chatType, color=chat.color, ensName=ensName, profile=pubKey) - self.emitTopicAndJoin(chat) - - proc createOneToOneChat*(self: ChatModel, publicKey: string, ensName: string = "") = - if self.hasChannel(publicKey): - self.emitTopicAndJoin(self.channels[publicKey]) - return - - var chat = newChat(publicKey, ChatType.OneToOne) - if ensName != "": - chat.name = ensName - chat.ensName = ensName - self.channels[chat.id] = chat - discard status_chat.createOneToOneChat(publicKey) - self.emitTopicAndJoin(chat) - - proc createPublicChat*(self: ChatModel, chatId: string) = - if self.hasChannel(chatId): return - var chat = newChat(chatId, ChatType.Public) - self.channels[chat.id] = chat - discard status_chat.createPublicChat(chatId) - self.emitTopicAndJoin(chat) - - - proc updateContacts*(self: ChatModel, contacts: seq[Profile]) = - for c in contacts: - self.contacts[c.id] = c - self.events.emit("chatUpdate", ChatUpdateArgs(contacts: contacts)) - - proc requestMissingCommunityInfos*(self: ChatModel) = - if (self.communitiesToFetch.len == 0): - return - for communityId in self.communitiesToFetch: - status_chat.requestCommunityInfo(communityId) - - proc init*(self: ChatModel, pubKey: string) = - self.publicKey = pubKey - - var contacts = getAddedContacts() - var chatList = status_chat.loadChats() - - let profileUpdatesChatIds = chatList.filter(c => c.chatType == ChatType.Profile).map(c => c.id) - - if chatList.filter(c => c.chatType == ChatType.Timeline).len == 0: - var timelineChannel = newChat(status_utils.getTimelineChatId(), ChatType.Timeline) - self.join(timelineChannel.id, timelineChannel.chatType) - chatList.add(timelineChannel) - - let timelineChatId = status_utils.getTimelineChatId(pubKey) - - if not profileUpdatesChatIds.contains(timelineChatId): - var profileUpdateChannel = newChat(timelineChatId, ChatType.Profile) - status_chat.saveChat(profileUpdateChannel.id, profileUpdateChannel.chatType, profile=pubKey) - chatList.add(profileUpdateChannel) - - # For profile updates and timeline, we have to make sure that for - # each added contact, a chat has been saved for the currently logged-in - # user. Users that will use a version of Status with timeline support for the - # first time, won't have any of those otherwise. - if profileUpdatesChatIds.filter(id => id != timelineChatId).len != contacts.len: - for contact in contacts: - if not profileUpdatesChatIds.contains(status_utils.getTimelineChatId(contact.address)): - let profileUpdatesChannel = newChat(status_utils.getTimelineChatId(contact.address), ChatType.Profile) - status_chat.saveChat(profileUpdatesChannel.id, profileUpdatesChannel.chatType, ensName=contact.ensName, profile=contact.address) - chatList.add(profileUpdatesChannel) - - var filters:seq[JsonNode] = @[] - for chat in chatList: - if self.hasChannel(chat.id): - continue - filters.add status_chat.buildFilter(chat) - self.channels[chat.id] = chat - self.events.emit("channelLoaded", ChannelArgs(chat: chat)) - - if filters.len == 0: return - - let filterResult = status_chat.loadFilters(filters) - - self.events.emit("chatsLoaded", ChatArgs(chats: chatList)) - - - self.events.once("mailserverAvailable") do(a: Args): - self.mailserverReady = true - self.requestMissingCommunityInfos() - - self.events.on("contactUpdate") do(a: Args): - var evArgs = ContactUpdateArgs(a) - self.updateContacts(evArgs.contacts) - - proc statusUpdates*(self: ChatModel) = - let statusUpdates = status_chat.statusUpdates() - self.events.emit("messagesLoaded", MsgsLoadedArgs(statusUpdates: statusUpdates)) - - proc leave*(self: ChatModel, chatId: string) = - self.removeChatFilters(chatId) - - if self.channels[chatId].chatType == ChatType.PrivateGroupChat: - let leaveGroupResponse = status_chat.leaveGroupChat(chatId) - self.emitUpdate(leaveGroupResponse) - - discard status_chat.deactivateChat(self.channels[chatId]) - - self.channels.del(chatId) - discard status_chat.clearChatHistory(chatId) - self.events.emit("channelLeft", ChatIdArg(chatId: chatId)) - - proc clearHistory*(self: ChatModel, chatId: string) = - discard status_chat.clearChatHistory(chatId) - let chat = self.channels[chatId] - self.events.emit("chatHistoryCleared", ChannelArgs(chat: chat)) - - proc setActiveChannel*(self: ChatModel, chatId: string) = - self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId)) - - proc processMessageUpdateAfterSend(self: ChatModel, response: string): (seq[Chat], seq[Message]) = - result = self.parseChatResponse(response) - var (chats, messages) = result - if chats.len == 0 and messages.len == 0: - self.events.emit("messageSendingFailed", Args()) - return - - self.events.emit("messageSendingSuccess", MessageSendingSuccess(message: messages[0], chat: chats[0])) - - proc sendMessage*(self: ChatModel, chatId: string, msg: string, replyTo: string = "", contentType: int = ContentType.Message.int, communityId: string = "") = - var response = status_chat.sendChatMessage(chatId, msg, replyTo, contentType, communityId) - discard self.processMessageUpdateAfterSend(response) - - proc editMessage*(self: ChatModel, messageId: string, msg: string) = - var response = status_chat.editMessage(messageId, msg) - discard self.processMessageUpdateAfterSend(response) - - proc sendImage*(self: ChatModel, chatId: string, image: string) = - var response = status_chat.sendImageMessage(chatId, image) - discard self.processMessageUpdateAfterSend(response) - - proc sendImages*(self: ChatModel, chatId: string, images: var seq[string]) = - var response = status_chat.sendImageMessages(chatId, images) - discard self.processMessageUpdateAfterSend(response) - - proc deleteMessageAndSend*(self: ChatModel, messageId: string) = - discard status_chat.deleteMessageAndSend(messageId) - - proc sendSticker*(self: ChatModel, chatId: string, replyTo: string, sticker: Sticker) = - var response = status_chat.sendStickerMessage(chatId, replyTo, sticker) - self.events.emit("stickerSent", StickerArgs(sticker: sticker, save: true)) - var (chats, messages) = self.parseChatResponse(response) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) - self.events.emit("sendingMessage", MessageArgs(id: messages[0].id, channel: messages[0].chatId)) - - proc addEmojiReaction*(self: ChatModel, chatId: string, messageId: string, emojiId: int) = - let reactions = status_chat.addEmojiReaction(chatId, messageId, emojiId) - self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactions)) - - proc removeEmojiReaction*(self: ChatModel, emojiReactionId: string) = - let reactions = status_chat.removeEmojiReaction(emojiReactionId) - self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactions)) - - proc onMarkMessagesRead(self: ChatModel, response: string, chatId: string): JsonNode = - result = parseJson(response) - if self.channels.hasKey(chatId): - self.channels[chatId].unviewedMessagesCount = 0 - self.channels[chatId].mentionsCount = 0 - self.events.emit("channelUpdate", ChatUpdateArgs(messages: @[], chats: @[self.channels[chatId]], contacts: @[])) - - proc onAsyncMarkMessagesRead(self: ChatModel, response: string) {.slot.} = - let parsedResponse = parseJson(response) - discard self.onMarkMessagesRead(parsedResponse{"response"}.getStr, parsedResponse{"chatId"}.getStr) - - proc markAllChannelMessagesRead*(self: ChatModel, chatId: string): JsonNode = - var response = status_chat.markAllRead(chatId) - return self.onMarkMessagesRead(response, chatId) - - proc asyncMarkAllChannelMessagesRead*(self: ChatModel, chatId: string) = - let arg = AsyncMarkAllReadTaskArg( - tptr: cast[ByteAddress](asyncMarkAllReadTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onAsyncMarkMessagesRead", - chatId: chatId, - ) - self.tasks.threadpool.start(arg) - - proc markMessagesSeen*(self: ChatModel, chatId: string, messageIds: seq[string]): JsonNode = - var response = status_chat.markMessagesSeen(chatId, messageIds) - return self.onMarkMessagesRead(response, chatId) - - proc confirmJoiningGroup*(self: ChatModel, chatId: string) = - var response = status_chat.confirmJoiningGroup(chatId) - self.emitUpdate(response) - - proc renameGroup*(self: ChatModel, chatId: string, newName: string) = - var response = status_chat.renameGroup(chatId, newName) - self.emitUpdate(response) - - proc getUserName*(self: ChatModel, id: string, defaultUserName: string):string = - if(self.contacts.hasKey(id)): - return userNameOrAlias(self.contacts[id]) - else: - return defaultUserName - - proc processGroupChatCreation*(self: ChatModel, result: string) = - var response = parseJson(result) - var (chats, messages) = formatChatUpdate(response) - let chat = chats[0] - self.channels[chat.id] = chat - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) - self.events.emit("activeChannelChanged", ChatIdArg(chatId: chat.id)) - - proc createGroup*(self: ChatModel, groupName: string, pubKeys: seq[string]) = - var result = status_chat.createGroup(groupName, pubKeys) - self.processGroupChatCreation(result) - - proc createGroupChatFromInvitation*(self: ChatModel, groupName: string, chatID: string, adminPK: string) = - var result = status_chat.createGroupChatFromInvitation(groupName, chatID, adminPK) - self.processGroupChatCreation(result) - - proc addGroupMembers*(self: ChatModel, chatId: string, pubKeys: seq[string]) = - var response = status_chat.addGroupMembers(chatId, pubKeys) - self.emitUpdate(response) - - proc kickGroupMember*(self: ChatModel, chatId: string, pubKey: string) = - var response = status_chat.kickGroupMember(chatId, pubKey) - self.emitUpdate(response) - - proc makeAdmin*(self: ChatModel, chatId: string, pubKey: string) = - var response = status_chat.makeAdmin(chatId, pubKey) - self.emitUpdate(response) - - proc resendMessage*(self: ChatModel, messageId: string) = - discard status_chat.reSendChatMessage(messageId) - - proc muteChat*(self: ChatModel, chat: Chat) = - discard status_chat.muteChat(chat.id) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat], contacts: @[])) - - proc unmuteChat*(self: ChatModel, chat: Chat) = - discard status_chat.unmuteChat(chat.id) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat], contacts: @[])) - - proc processUpdateForTransaction*(self: ChatModel, messageId: string, response: string) = - var (chats, messages) = self.processMessageUpdateAfterSend(response) - self.events.emit("messageDeleted", MessageArgs(id: messageId, channel: chats[0].id)) - - proc acceptRequestAddressForTransaction*(self: ChatModel, messageId: string, address: string) = - let response = status_chat_commands.acceptRequestAddressForTransaction(messageId, address) - self.processUpdateForTransaction(messageId, response) - - proc declineRequestAddressForTransaction*(self: ChatModel, messageId: string) = - let response = status_chat_commands.declineRequestAddressForTransaction(messageId) - self.processUpdateForTransaction(messageId, response) - - proc declineRequestTransaction*(self: ChatModel, messageId: string) = - let response = status_chat_commands.declineRequestTransaction(messageId) - self.processUpdateForTransaction(messageId, response) - - proc requestAddressForTransaction*(self: ChatModel, chatId: string, fromAddress: string, amount: string, tokenAddress: string) = - let address = if (tokenAddress == ZERO_ADDRESS): "" else: tokenAddress - let response = status_chat_commands.requestAddressForTransaction(chatId, fromAddress, amount, address) - discard self.processMessageUpdateAfterSend(response) - - proc acceptRequestTransaction*(self: ChatModel, transactionHash: string, messageId: string, signature: string) = - let response = status_chat_commands.acceptRequestTransaction(transactionHash, messageId, signature) - discard self.processMessageUpdateAfterSend(response) - - proc requestTransaction*(self: ChatModel, chatId: string, fromAddress: string, amount: string, tokenAddress: string) = - let address = if (tokenAddress == ZERO_ADDRESS): "" else: tokenAddress - let response = status_chat_commands.requestTransaction(chatId, fromAddress, amount, address) - discard self.processMessageUpdateAfterSend(response) - - proc getAllComunities*(self: ChatModel): seq[Community] = - result = status_chat.getAllComunities() - - proc getJoinedComunities*(self: ChatModel): seq[Community] = - result = status_chat.getJoinedComunities() - - proc createCommunity*(self: ChatModel, name: string, description: string, access: int, ensOnly: bool, color: string, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community = - result = status_chat.createCommunity(name, description, access, ensOnly, color, imageUrl, aX, aY, bX, bY) - - proc editCommunity*(self: ChatModel, id: string, name: string, description: string, access: int, ensOnly: bool, color: string, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community = - result = status_chat.editCommunity(id, name, description, access, ensOnly, color, imageUrl, aX, aY, bX, bY) - - proc createCommunityChannel*(self: ChatModel, communityId: string, name: string, description: string): Chat = - result = status_chat.createCommunityChannel(communityId, name, description) - - proc editCommunityChannel*(self: ChatModel, communityId: string, channelId: string, name: string, description: string, categoryId: string): Chat = - result = status_chat.editCommunityChannel(communityId, channelId, name, description, categoryId) - - proc deleteCommunityChat*(self: ChatModel, communityId: string, channelId: string) = - status_chat.deleteCommunityChat(communityId, channelId) - - proc reorderCommunityCategories*(self: ChatModel, communityId: string, categoryId: string, position: int) = - status_chat.reorderCommunityCategories(communityId, categoryId, position) - - proc createCommunityCategory*(self: ChatModel, communityId: string, name: string, channels: seq[string]): CommunityCategory = - result = status_chat.createCommunityCategory(communityId, name, channels) - - proc editCommunityCategory*(self: ChatModel, communityId: string, categoryId: string, name: string, channels: seq[string]) = - status_chat.editCommunityCategory(communityId, categoryId, name, channels) - - proc deleteCommunityCategory*(self: ChatModel, communityId: string, categoryId: string) = - status_chat.deleteCommunityCategory(communityId, categoryId) - - proc reorderCommunityChannel*(self: ChatModel, communityId: string, categoryId: string, chatId: string, position: int) = - status_chat.reorderCommunityChat(communityId, categoryId, chatId, position) - - proc joinCommunity*(self: ChatModel, communityId: string) = - status_chat.joinCommunity(communityId) - - proc requestCommunityInfo*(self: ChatModel, communityId: string) = - if (not self.mailserverReady): - self.communitiesToFetch.add(communityId) - self.communitiesToFetch = self.communitiesToFetch.deduplicate() - return +proc hasChannel*(self: ChatModel, chatId: string): bool = + self.channels.hasKey(chatId) + +proc getActiveChannel*(self: ChatModel): string = + if (self.channels.len == 0): "" else: toSeq(self.channels.values)[self.channels.len - 1].id + +proc emitTopicAndJoin(self: ChatModel, chat: Chat) = + let filterResult = status_chat.loadFilters(@[status_chat.buildFilter(chat)]) + self.events.emit("channelJoined", ChannelArgs(chat: chat)) + +proc join*(self: ChatModel, chatId: string, chatType: ChatType, ensName: string = "", pubKey: string = "") = + if self.hasChannel(chatId): return + var chat = newChat(chatId, ChatType(chatType)) + self.channels[chat.id] = chat + status_chat.saveChat(chatId, chatType, color=chat.color, ensName=ensName, profile=pubKey) + self.emitTopicAndJoin(chat) + +proc createOneToOneChat*(self: ChatModel, publicKey: string, ensName: string = "") = + if self.hasChannel(publicKey): + self.emitTopicAndJoin(self.channels[publicKey]) + return + + var chat = newChat(publicKey, ChatType.OneToOne) + if ensName != "": + chat.name = ensName + chat.ensName = ensName + self.channels[chat.id] = chat + discard status_chat.createOneToOneChat(publicKey) + self.emitTopicAndJoin(chat) + +proc createPublicChat*(self: ChatModel, chatId: string) = + if self.hasChannel(chatId): return + var chat = newChat(chatId, ChatType.Public) + self.channels[chat.id] = chat + discard status_chat.createPublicChat(chatId) + self.emitTopicAndJoin(chat) + + +proc updateContacts*(self: ChatModel, contacts: seq[Profile]) = + for c in contacts: + self.contacts[c.id] = c + self.events.emit("chatUpdate", ChatUpdateArgs(contacts: contacts)) + +proc requestMissingCommunityInfos*(self: ChatModel) = + if (self.communitiesToFetch.len == 0): + return + for communityId in self.communitiesToFetch: status_chat.requestCommunityInfo(communityId) - proc leaveCommunity*(self: ChatModel, communityId: string) = - status_chat.leaveCommunity(communityId) +proc init*(self: ChatModel, pubKey: string) = + self.publicKey = pubKey - proc inviteUserToCommunity*(self: ChatModel, communityId: string, pubKey: string) = - status_chat.inviteUsersToCommunity(communityId, @[pubKey]) + var contacts = getAddedContacts() + var chatList = status_chat.loadChats() - proc inviteUsersToCommunity*(self: ChatModel, communityId: string, pubKeys: seq[string]) = - status_chat.inviteUsersToCommunity(communityId, pubKeys) + let profileUpdatesChatIds = chatList.filter(c => c.chatType == ChatType.Profile).map(c => c.id) - proc removeUserFromCommunity*(self: ChatModel, communityId: string, pubKey: string) = - status_chat.removeUserFromCommunity(communityId, pubKey) + if chatList.filter(c => c.chatType == ChatType.Timeline).len == 0: + var timelineChannel = newChat(status_utils.getTimelineChatId(), ChatType.Timeline) + self.join(timelineChannel.id, timelineChannel.chatType) + chatList.add(timelineChannel) - proc banUserFromCommunity*(self: ChatModel, pubKey: string, communityId: string): string = - return status_chat.banUserFromCommunity(pubKey, communityId) + let timelineChatId = status_utils.getTimelineChatId(pubKey) - proc exportCommunity*(self: ChatModel, communityId: string): string = - result = status_chat.exportCommunity(communityId) + if not profileUpdatesChatIds.contains(timelineChatId): + var profileUpdateChannel = newChat(timelineChatId, ChatType.Profile) + status_chat.saveChat(profileUpdateChannel.id, profileUpdateChannel.chatType, profile=pubKey) + chatList.add(profileUpdateChannel) - proc importCommunity*(self: ChatModel, communityKey: string): string = - result = status_chat.importCommunity(communityKey) + # For profile updates and timeline, we have to make sure that for + # each added contact, a chat has been saved for the currently logged-in + # user. Users that will use a version of Status with timeline support for the + # first time, won't have any of those otherwise. + if profileUpdatesChatIds.filter(id => id != timelineChatId).len != contacts.len: + for contact in contacts: + if not profileUpdatesChatIds.contains(status_utils.getTimelineChatId(contact.address)): + let profileUpdatesChannel = newChat(status_utils.getTimelineChatId(contact.address), ChatType.Profile) + status_chat.saveChat(profileUpdatesChannel.id, profileUpdatesChannel.chatType, ensName=contact.ensName, profile=contact.address) + chatList.add(profileUpdatesChannel) - proc requestToJoinCommunity*(self: ChatModel, communityKey: string, ensName: string): seq[CommunityMembershipRequest] = - status_chat.requestToJoinCommunity(communityKey, ensName) + var filters:seq[JsonNode] = @[] + for chat in chatList: + if self.hasChannel(chat.id): + continue + filters.add status_chat.buildFilter(chat) + self.channels[chat.id] = chat + self.events.emit("channelLoaded", ChannelArgs(chat: chat)) - proc acceptRequestToJoinCommunity*(self: ChatModel, requestId: string) = - status_chat.acceptRequestToJoinCommunity(requestId) + if filters.len == 0: return - proc declineRequestToJoinCommunity*(self: ChatModel, requestId: string) = - status_chat.declineRequestToJoinCommunity(requestId) + let filterResult = status_chat.loadFilters(filters) - proc pendingRequestsToJoinForCommunity*(self: ChatModel, communityKey: string): seq[CommunityMembershipRequest] = - result = status_chat.pendingRequestsToJoinForCommunity(communityKey) + self.events.emit("chatsLoaded", ChatArgs(chats: chatList)) - proc setCommunityMuted*(self: ChatModel, communityId: string, muted: bool) = - status_chat.setCommunityMuted(communityId, muted) - proc myPendingRequestsToJoin*(self: ChatModel): seq[CommunityMembershipRequest] = - result = status_chat.myPendingRequestsToJoin() + self.events.once("mailserverAvailable") do(a: Args): + self.mailserverReady = true + self.requestMissingCommunityInfos() - proc setPinMessage*(self: ChatModel, messageId: string, chatId: string, pinned: bool) = - status_chat.setPinMessage(messageId, chatId, pinned) + self.events.on("contactUpdate") do(a: Args): + var evArgs = ContactUpdateArgs(a) + self.updateContacts(evArgs.contacts) - proc activityCenterNotifications*(self: ChatModel, initialLoad: bool = true) = - # Notifications were already loaded, since cursor will - # be nil/empty if there are no more notifs - if(not initialLoad and self.activityCenterCursor == ""): return +proc statusUpdates*(self: ChatModel) = + let statusUpdates = status_chat.statusUpdates() + self.events.emit("messagesLoaded", MsgsLoadedArgs(statusUpdates: statusUpdates)) - let activityCenterNotificationsTuple = status_chat.activityCenterNotification(self.activityCenterCursor) - self.activityCenterCursor = activityCenterNotificationsTuple[0]; +proc leave*(self: ChatModel, chatId: string) = + self.removeChatFilters(chatId) - self.events.emit("activityCenterNotificationsLoaded", ActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotificationsTuple[1])) + if self.channels[chatId].chatType == ChatType.PrivateGroupChat: + let leaveGroupResponse = status_chat.leaveGroupChat(chatId) + self.emitUpdate(leaveGroupResponse) - proc activityCenterNotifications*(self: ChatModel, cursor: string = "", activityCenterNotifications: seq[ActivityCenterNotification]) = - self.activityCenterCursor = cursor + discard status_chat.deactivateChat(self.channels[chatId]) - self.events.emit("activityCenterNotificationsLoaded", ActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotifications)) + self.channels.del(chatId) + discard status_chat.clearChatHistory(chatId) + self.events.emit("channelLeft", ChatIdArg(chatId: chatId)) - proc markAllActivityCenterNotificationsRead*(self: ChatModel): string = - try: - status_chat.markAllActivityCenterNotificationsRead() - except Exception as e: - error "Error marking all as read", msg = e.msg - result = e.msg - - # This proc should accept ActivityCenterNotificationType in order to clear all notifications - # per type, that's why we have this part here. If we add all types to notificationsType that - # means that we need to clear all notifications for all types. - var types : seq[ActivityCenterNotificationType] - for t in ActivityCenterNotificationType: - types.add(t) +proc clearHistory*(self: ChatModel, chatId: string) = + discard status_chat.clearChatHistory(chatId) + let chat = self.channels[chatId] + self.events.emit("chatHistoryCleared", ChannelArgs(chat: chat)) - self.events.emit("markNotificationsAsRead", MarkAsReadNotificationProperties(notificationTypes: types)) +proc setActiveChannel*(self: ChatModel, chatId: string) = + self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId)) - proc markActivityCenterNotificationRead*(self: ChatModel, notificationId: string, - markAsReadProps: MarkAsReadNotificationProperties): string = - try: - status_chat.markActivityCenterNotificationsRead(@[notificationId]) - except Exception as e: - error "Error marking as read", msg = e.msg - result = e.msg - - self.events.emit("markNotificationsAsRead", markAsReadProps) - - proc acceptActivityCenterNotifications*(self: ChatModel, ids: seq[string]): string = - try: - let response = status_chat.acceptActivityCenterNotifications(ids) - - let (chats, messages) = self.parseChatResponse(response) - self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats)) - - except Exception as e: - error "Error marking as accepted", msg = e.msg - result = e.msg - - proc dismissActivityCenterNotifications*(self: ChatModel, ids: seq[string]): string = - try: - discard status_chat.dismissActivityCenterNotifications(ids) - except Exception as e: - error "Error marking as dismissed", msg = e.msg - result = e.msg - - proc unreadActivityCenterNotificationsCount*(self: ChatModel): int = - status_chat.unreadActivityCenterNotificationsCount() - - proc getLinkPreviewData*(link: string, success: var bool): JsonNode = - result = status_chat.getLinkPreviewData(link, success) - - proc getCommunityIdForChat*(self: ChatModel, chatId: string): string = - if (not self.hasChannel(chatId)): - return "" - return self.channels[chatId].communityId - - proc asyncSearchMessages*(self: ChatModel, chatId: string, searchTerm: string, caseSensitive: bool) = - ## Asynchronous search for messages which contain the searchTerm and belong - ## to the chat with chatId. - - if (chatId.len == 0): - info "empty channel id set for fetching more messages" - return - - if (searchTerm.len == 0): - return - - let arg = AsyncSearchMessagesInChatTaskArg( - tptr: cast[ByteAddress](asyncSearchMessagesInChatTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onAsyncSearchMessages", - chatId: chatId, - searchTerm: searchTerm, - caseSensitive: caseSensitive - ) - self.tasks.threadpool.start(arg) - - proc asyncSearchMessages*(self: ChatModel, communityIds: seq[string], chatIds: seq[string], searchTerm: string, caseSensitive: bool) = - ## Asynchronous search for messages which contain the searchTerm and belong - ## to either any chat/channel from chatIds array or any channel of community - ## from communityIds array. - - if (communityIds.len == 0 and chatIds.len == 0): - info "either community ids or chat ids or both must be set" - return - - if (searchTerm.len == 0): - return - - let arg = AsyncSearchMessagesInChatsAndCommunitiesTaskArg( - tptr: cast[ByteAddress](asyncSearchMessagesInChatsAndCommunitiesTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onAsyncSearchMessages", - communityIds: communityIds, - chatIds: chatIds, - searchTerm: searchTerm, - caseSensitive: caseSensitive - ) - self.tasks.threadpool.start(arg) - - proc onAsyncSearchMessages*(self: ChatModel, response: string) {.slot.} = - let responseObj = response.parseJson - if (responseObj.kind != JObject): - info "search messages response is not an json object" - return - - var chatId: string - discard responseObj.getProp("chatId", chatId) - - var messagesObj: JsonNode - if (not responseObj.getProp("messages", messagesObj)): - info "search messages response doesn't contain messages property" - return - - var messagesArray: JsonNode - if (not messagesObj.getProp("messages", messagesArray)): - info "search messages response doesn't contain messages array" - return - - var messages: seq[Message] = @[] - if (messagesArray.kind == JArray): - for jsonMsg in messagesArray: - messages.add(jsonMsg.toMessage()) - - self.events.emit("searchMessagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: messages)) +proc processMessageUpdateAfterSend(self: ChatModel, response: string): (seq[Chat], seq[Message]) = + result = self.parseChatResponse(response) + var (chats, messages) = result + if chats.len == 0 and messages.len == 0: + self.events.emit("messageSendingFailed", Args()) + return - proc loadMoreMessagesForChannel*(self: ChatModel, channelId: string) = - if (channelId.len == 0): - info "empty channel id set for fetching more messages" - return + self.events.emit("messageSendingSuccess", MessageSendingSuccess(message: messages[0], chat: chats[0])) - if(not self.msgCursor.hasKey(channelId)): - self.msgCursor[channelId] = "" +proc sendMessage*(self: ChatModel, chatId: string, msg: string, replyTo: string = "", contentType: int = ContentType.Message.int, communityId: string = "") = + var response = status_chat.sendChatMessage(chatId, msg, replyTo, contentType, communityId) + discard self.processMessageUpdateAfterSend(response) - if(not self.emojiCursor.hasKey(channelId)): - self.emojiCursor[channelId] = "" +proc editMessage*(self: ChatModel, messageId: string, msg: string) = + var response = status_chat.editMessage(messageId, msg) + discard self.processMessageUpdateAfterSend(response) - if(not self.pinnedMsgCursor.hasKey(channelId)): - self.pinnedMsgCursor[channelId] = "" +proc sendImage*(self: ChatModel, chatId: string, image: string) = + var response = status_chat.sendImageMessage(chatId, image) + discard self.processMessageUpdateAfterSend(response) - let arg = AsyncFetchChatMessagesTaskArg( - tptr: cast[ByteAddress](asyncFetchChatMessagesTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onLoadMoreMessagesForChannel", - chatId: channelId, - chatCursor: self.msgCursor[channelId], - emojiCursor: self.emojiCursor[channelId], - pinnedMsgCursor: self.pinnedMsgCursor[channelId], - limit: 20 - ) +proc sendImages*(self: ChatModel, chatId: string, images: var seq[string]) = + var response = status_chat.sendImageMessages(chatId, images) + discard self.processMessageUpdateAfterSend(response) - self.tasks.threadpool.start(arg) +proc deleteMessageAndSend*(self: ChatModel, messageId: string) = + var response = status_chat.deleteMessageAndSend(messageId) + discard self.processMessageUpdateAfterSend(response) - proc loadInitialMessagesForChannel*(self: ChatModel, channelId: string) = - if (channelId.len == 0): - info "empty channel id set for loading initial messages" - return +proc sendSticker*(self: ChatModel, chatId: string, replyTo: string, sticker: Sticker) = + var response = status_chat.sendStickerMessage(chatId, replyTo, sticker) + self.events.emit("stickerSent", StickerArgs(sticker: sticker, save: true)) + var (chats, messages) = self.parseChatResponse(response) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + self.events.emit("sendingMessage", MessageArgs(id: messages[0].id, channel: messages[0].chatId)) - if(self.msgCursor.hasKey(channelId)): - return +proc addEmojiReaction*(self: ChatModel, chatId: string, messageId: string, emojiId: int) = + let reactions = status_chat.addEmojiReaction(chatId, messageId, emojiId) + self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactions)) - if(self.emojiCursor.hasKey(channelId)): - return +proc removeEmojiReaction*(self: ChatModel, emojiReactionId: string) = + let reactions = status_chat.removeEmojiReaction(emojiReactionId) + self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactions)) - if(self.pinnedMsgCursor.hasKey(channelId)): - return +proc onMarkMessagesRead(self: ChatModel, response: string, chatId: string): JsonNode = + result = parseJson(response) + if self.channels.hasKey(chatId): + self.channels[chatId].unviewedMessagesCount = 0 + self.channels[chatId].mentionsCount = 0 + self.events.emit("channelUpdate", ChatUpdateArgs(messages: @[], chats: @[self.channels[chatId]], contacts: @[])) - self.loadMoreMessagesForChannel(channelId) +proc onAsyncMarkMessagesRead*(self: ChatModel, response: string) = + let parsedResponse = parseJson(response) + discard self.onMarkMessagesRead(parsedResponse{"response"}.getStr, parsedResponse{"chatId"}.getStr) - proc onLoadMoreMessagesForChannel*(self: ChatModel, response: string) {.slot.} = - let responseObj = response.parseJson - if (responseObj.kind != JObject): - info "load more messages response is not an json object" - - # notify view - self.events.emit("messagesLoaded", MsgsLoadedArgs()) - self.events.emit("reactionsLoaded", ReactionsLoadedArgs()) - self.events.emit("pinnedMessagesLoaded", MsgsLoadedArgs()) - return +proc markAllChannelMessagesRead*(self: ChatModel, chatId: string): JsonNode = + var response = status_chat.markAllRead(chatId) + return self.onMarkMessagesRead(response, chatId) + +proc markMessagesSeen*(self: ChatModel, chatId: string, messageIds: seq[string]): JsonNode = + var response = status_chat.markMessagesSeen(chatId, messageIds) + return self.onMarkMessagesRead(response, chatId) + +proc confirmJoiningGroup*(self: ChatModel, chatId: string) = + var response = status_chat.confirmJoiningGroup(chatId) + self.emitUpdate(response) + +proc renameGroup*(self: ChatModel, chatId: string, newName: string) = + var response = status_chat.renameGroup(chatId, newName) + self.emitUpdate(response) + +proc getUserName*(self: ChatModel, id: string, defaultUserName: string):string = + if(self.contacts.hasKey(id)): + return userNameOrAlias(self.contacts[id]) + else: + return defaultUserName + +proc processGroupChatCreation*(self: ChatModel, result: string) = + var response = parseJson(result) + var (chats, messages) = formatChatUpdate(response) + let chat = chats[0] + self.channels[chat.id] = chat + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[])) + self.events.emit("activeChannelChanged", ChatIdArg(chatId: chat.id)) + +proc createGroup*(self: ChatModel, groupName: string, pubKeys: seq[string]) = + var result = status_chat.createGroup(groupName, pubKeys) + self.processGroupChatCreation(result) + +proc createGroupChatFromInvitation*(self: ChatModel, groupName: string, chatID: string, adminPK: string) = + var result = status_chat.createGroupChatFromInvitation(groupName, chatID, adminPK) + self.processGroupChatCreation(result) + +proc addGroupMembers*(self: ChatModel, chatId: string, pubKeys: seq[string]) = + var response = status_chat.addGroupMembers(chatId, pubKeys) + self.emitUpdate(response) + +proc kickGroupMember*(self: ChatModel, chatId: string, pubKey: string) = + var response = status_chat.kickGroupMember(chatId, pubKey) + self.emitUpdate(response) + +proc makeAdmin*(self: ChatModel, chatId: string, pubKey: string) = + var response = status_chat.makeAdmin(chatId, pubKey) + self.emitUpdate(response) + +proc resendMessage*(self: ChatModel, messageId: string) = + discard status_chat.reSendChatMessage(messageId) + +proc muteChat*(self: ChatModel, chat: Chat) = + discard status_chat.muteChat(chat.id) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat], contacts: @[])) + +proc unmuteChat*(self: ChatModel, chat: Chat) = + discard status_chat.unmuteChat(chat.id) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: @[], chats: @[chat], contacts: @[])) + +proc processUpdateForTransaction*(self: ChatModel, messageId: string, response: string) = + var (chats, messages) = self.processMessageUpdateAfterSend(response) + self.events.emit("messageDeleted", MessageArgs(id: messageId, channel: chats[0].id)) + +proc acceptRequestAddressForTransaction*(self: ChatModel, messageId: string, address: string) = + let response = status_chat_commands.acceptRequestAddressForTransaction(messageId, address) + self.processUpdateForTransaction(messageId, response) + +proc declineRequestAddressForTransaction*(self: ChatModel, messageId: string) = + let response = status_chat_commands.declineRequestAddressForTransaction(messageId) + self.processUpdateForTransaction(messageId, response) + +proc declineRequestTransaction*(self: ChatModel, messageId: string) = + let response = status_chat_commands.declineRequestTransaction(messageId) + self.processUpdateForTransaction(messageId, response) + +proc requestAddressForTransaction*(self: ChatModel, chatId: string, fromAddress: string, amount: string, tokenAddress: string) = + let address = if (tokenAddress == ZERO_ADDRESS): "" else: tokenAddress + let response = status_chat_commands.requestAddressForTransaction(chatId, fromAddress, amount, address) + discard self.processMessageUpdateAfterSend(response) + +proc acceptRequestTransaction*(self: ChatModel, transactionHash: string, messageId: string, signature: string) = + let response = status_chat_commands.acceptRequestTransaction(transactionHash, messageId, signature) + discard self.processMessageUpdateAfterSend(response) + +proc requestTransaction*(self: ChatModel, chatId: string, fromAddress: string, amount: string, tokenAddress: string) = + let address = if (tokenAddress == ZERO_ADDRESS): "" else: tokenAddress + let response = status_chat_commands.requestTransaction(chatId, fromAddress, amount, address) + discard self.processMessageUpdateAfterSend(response) + +proc getAllComunities*(self: ChatModel): seq[Community] = + result = status_chat.getAllComunities() + +proc getJoinedComunities*(self: ChatModel): seq[Community] = + result = status_chat.getJoinedComunities() + +proc createCommunity*(self: ChatModel, name: string, description: string, access: int, ensOnly: bool, color: string, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community = + result = status_chat.createCommunity(name, description, access, ensOnly, color, imageUrl, aX, aY, bX, bY) + +proc editCommunity*(self: ChatModel, id: string, name: string, description: string, access: int, ensOnly: bool, color: string, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community = + result = status_chat.editCommunity(id, name, description, access, ensOnly, color, imageUrl, aX, aY, bX, bY) + +proc createCommunityChannel*(self: ChatModel, communityId: string, name: string, description: string): Chat = + result = status_chat.createCommunityChannel(communityId, name, description) + +proc editCommunityChannel*(self: ChatModel, communityId: string, channelId: string, name: string, description: string, categoryId: string): Chat = + result = status_chat.editCommunityChannel(communityId, channelId, name, description, categoryId) + +proc deleteCommunityChat*(self: ChatModel, communityId: string, channelId: string) = + status_chat.deleteCommunityChat(communityId, channelId) + +proc reorderCommunityCategories*(self: ChatModel, communityId: string, categoryId: string, position: int) = + status_chat.reorderCommunityCategories(communityId, categoryId, position) + +proc createCommunityCategory*(self: ChatModel, communityId: string, name: string, channels: seq[string]): CommunityCategory = + result = status_chat.createCommunityCategory(communityId, name, channels) + +proc editCommunityCategory*(self: ChatModel, communityId: string, categoryId: string, name: string, channels: seq[string]) = + status_chat.editCommunityCategory(communityId, categoryId, name, channels) + +proc deleteCommunityCategory*(self: ChatModel, communityId: string, categoryId: string) = + status_chat.deleteCommunityCategory(communityId, categoryId) + +proc reorderCommunityChannel*(self: ChatModel, communityId: string, categoryId: string, chatId: string, position: int) = + status_chat.reorderCommunityChat(communityId, categoryId, chatId, position) + +proc joinCommunity*(self: ChatModel, communityId: string) = + status_chat.joinCommunity(communityId) + +proc requestCommunityInfo*(self: ChatModel, communityId: string) = + if (not self.mailserverReady): + self.communitiesToFetch.add(communityId) + self.communitiesToFetch = self.communitiesToFetch.deduplicate() + return + status_chat.requestCommunityInfo(communityId) + +proc leaveCommunity*(self: ChatModel, communityId: string) = + status_chat.leaveCommunity(communityId) + +proc inviteUserToCommunity*(self: ChatModel, communityId: string, pubKey: string) = + status_chat.inviteUsersToCommunity(communityId, @[pubKey]) + +proc inviteUsersToCommunity*(self: ChatModel, communityId: string, pubKeys: seq[string]) = + status_chat.inviteUsersToCommunity(communityId, pubKeys) + +proc removeUserFromCommunity*(self: ChatModel, communityId: string, pubKey: string) = + status_chat.removeUserFromCommunity(communityId, pubKey) + +proc banUserFromCommunity*(self: ChatModel, pubKey: string, communityId: string): string = + return status_chat.banUserFromCommunity(pubKey, communityId) + +proc exportCommunity*(self: ChatModel, communityId: string): string = + result = status_chat.exportCommunity(communityId) + +proc importCommunity*(self: ChatModel, communityKey: string): string = + result = status_chat.importCommunity(communityKey) + +proc requestToJoinCommunity*(self: ChatModel, communityKey: string, ensName: string): seq[CommunityMembershipRequest] = + status_chat.requestToJoinCommunity(communityKey, ensName) + +proc acceptRequestToJoinCommunity*(self: ChatModel, requestId: string) = + status_chat.acceptRequestToJoinCommunity(requestId) + +proc declineRequestToJoinCommunity*(self: ChatModel, requestId: string) = + status_chat.declineRequestToJoinCommunity(requestId) + +proc pendingRequestsToJoinForCommunity*(self: ChatModel, communityKey: string): seq[CommunityMembershipRequest] = + result = status_chat.pendingRequestsToJoinForCommunity(communityKey) + +proc setCommunityMuted*(self: ChatModel, communityId: string, muted: bool) = + status_chat.setCommunityMuted(communityId, muted) + +proc myPendingRequestsToJoin*(self: ChatModel): seq[CommunityMembershipRequest] = + result = status_chat.myPendingRequestsToJoin() + +proc setPinMessage*(self: ChatModel, messageId: string, chatId: string, pinned: bool) = + status_chat.setPinMessage(messageId, chatId, pinned) + +proc activityCenterNotifications*(self: ChatModel, initialLoad: bool = true) = + # Notifications were already loaded, since cursor will + # be nil/empty if there are no more notifs + if(not initialLoad and self.activityCenterCursor == ""): return + + let activityCenterNotificationsTuple = status_chat.activityCenterNotification(self.activityCenterCursor) + self.activityCenterCursor = activityCenterNotificationsTuple[0]; + + self.events.emit("activityCenterNotificationsLoaded", ActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotificationsTuple[1])) + +proc activityCenterNotifications*(self: ChatModel, cursor: string = "", activityCenterNotifications: seq[ActivityCenterNotification]) = + self.activityCenterCursor = cursor + + self.events.emit("activityCenterNotificationsLoaded", ActivityCenterNotificationsArgs(activityCenterNotifications: activityCenterNotifications)) + +proc markAllActivityCenterNotificationsRead*(self: ChatModel): string = + try: + status_chat.markAllActivityCenterNotificationsRead() + except Exception as e: + error "Error marking all as read", msg = e.msg + result = e.msg + + # This proc should accept ActivityCenterNotificationType in order to clear all notifications + # per type, that's why we have this part here. If we add all types to notificationsType that + # means that we need to clear all notifications for all types. + var types : seq[ActivityCenterNotificationType] + for t in ActivityCenterNotificationType: + types.add(t) + + self.events.emit("markNotificationsAsRead", MarkAsReadNotificationProperties(notificationTypes: types)) + +proc markActivityCenterNotificationRead*(self: ChatModel, notificationId: string, +markAsReadProps: MarkAsReadNotificationProperties): string = + try: + status_chat.markActivityCenterNotificationsRead(@[notificationId]) + except Exception as e: + error "Error marking as read", msg = e.msg + result = e.msg + + self.events.emit("markNotificationsAsRead", markAsReadProps) + +proc acceptActivityCenterNotifications*(self: ChatModel, ids: seq[string]): string = + try: + let response = status_chat.acceptActivityCenterNotifications(ids) + + let (chats, messages) = self.parseChatResponse(response) + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats)) + + except Exception as e: + error "Error marking as accepted", msg = e.msg + result = e.msg + +proc dismissActivityCenterNotifications*(self: ChatModel, ids: seq[string]): string = + try: + discard status_chat.dismissActivityCenterNotifications(ids) + except Exception as e: + error "Error marking as dismissed", msg = e.msg + result = e.msg + +proc unreadActivityCenterNotificationsCount*(self: ChatModel): int = + status_chat.unreadActivityCenterNotificationsCount() + +proc getLinkPreviewData*(link: string, success: var bool): JsonNode = + result = status_chat.getLinkPreviewData(link, success) + +proc getCommunityIdForChat*(self: ChatModel, chatId: string): string = + if (not self.hasChannel(chatId)): + return "" + return self.channels[chatId].communityId + +proc onAsyncSearchMessages*(self: ChatModel, response: string) = + let responseObj = response.parseJson + if (responseObj.kind != JObject): + info "search messages response is not an json object" + return + + var chatId: string + discard responseObj.getProp("chatId", chatId) + + var messagesObj: JsonNode + if (not responseObj.getProp("messages", messagesObj)): + info "search messages response doesn't contain messages property" + return + + var messagesArray: JsonNode + if (not messagesObj.getProp("messages", messagesArray)): + info "search messages response doesn't contain messages array" + return + + var messages: seq[Message] = @[] + if (messagesArray.kind == JArray): + for jsonMsg in messagesArray: + messages.add(jsonMsg.toMessage()) + + self.events.emit("searchMessagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: messages)) + +proc onLoadMoreMessagesForChannel*(self: ChatModel, response: string) = + let responseObj = response.parseJson + if (responseObj.kind != JObject): + info "load more messages response is not an json object" - var chatId: string - discard responseObj.getProp("chatId", chatId) - - # handling chat messages - var chatMessagesObj: JsonNode - var chatCursor: string - discard responseObj.getProp("messages", chatMessagesObj) - discard responseObj.getProp("messagesCursor", chatCursor) - - self.msgCursor[chatId] = chatCursor - - var messages: seq[Message] = @[] - if (chatMessagesObj.kind == JArray): - for jsonMsg in chatMessagesObj: - messages.add(jsonMsg.toMessage()) - - if messages.len > 0: - let lastMsgIndex = messages.len - 1 - let ts = times.convert(Milliseconds, Seconds, messages[lastMsgIndex].whisperTimestamp.parseInt()) - self.lastMessageTimestamps[chatId] = ts - - # handling reactions - var reactionsObj: JsonNode - var reactionsCursor: string - discard responseObj.getProp("reactions", reactionsObj) - discard responseObj.getProp("reactionsCursor", reactionsCursor) - - self.emojiCursor[chatId] = reactionsCursor; - - var reactions: seq[Reaction] = @[] - if (reactionsObj.kind == JArray): - for jsonMsg in reactionsObj: - reactions.add(jsonMsg.toReaction) - - # handling pinned messages - var pinnedMsgObj: JsonNode - var pinnedMsgCursor: string - discard responseObj.getProp("pinnedMessages", pinnedMsgObj) - discard responseObj.getProp("pinnedMessagesCursor", pinnedMsgCursor) - - self.pinnedMsgCursor[chatId] = pinnedMsgCursor - - var pinnedMessages: seq[Message] = @[] - if (pinnedMsgObj.kind == JArray): - for jsonMsg in pinnedMsgObj: - var msgObj: JsonNode - if(jsonMsg.getProp("message", msgObj)): - var msg: Message - msg = msgObj.toMessage() - discard jsonMsg.getProp("pinnedBy", msg.pinnedBy) - pinnedMessages.add(msg) - # notify view - self.events.emit("messagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: messages)) - self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactions)) - self.events.emit("pinnedMessagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: pinnedMessages)) + self.events.emit("messagesLoaded", MsgsLoadedArgs()) + self.events.emit("reactionsLoaded", ReactionsLoadedArgs()) + self.events.emit("pinnedMessagesLoaded", MsgsLoadedArgs()) + return + + var chatId: string + discard responseObj.getProp("chatId", chatId) + + # handling chat messages + var chatMessagesObj: JsonNode + var chatCursor: string + discard responseObj.getProp("messages", chatMessagesObj) + discard responseObj.getProp("messagesCursor", chatCursor) - proc userNameOrAlias*(self: ChatModel, pubKey: string, - prettyForm: bool = false): string = - ## Returns ens name or alias, in case if prettyForm is true and ens name - ## ends with ".stateofus.eth" that part will be removed. - var alias: string - if self.contacts.hasKey(pubKey): - alias = ens.userNameOrAlias(self.contacts[pubKey]) - else: - alias = generateAlias(pubKey) + self.msgCursor[chatId] = chatCursor - if (prettyForm and alias.endsWith(".stateofus.eth")): - alias = alias[0 .. ^15] + var messages: seq[Message] = @[] + if (chatMessagesObj.kind == JArray): + for jsonMsg in chatMessagesObj: + messages.add(jsonMsg.toMessage()) - return alias + if messages.len > 0: + let lastMsgIndex = messages.len - 1 + let ts = times.convert(Milliseconds, Seconds, messages[lastMsgIndex].whisperTimestamp.parseInt()) + self.lastMessageTimestamps[chatId] = ts - proc chatName*(self: ChatModel, chatItem: Chat): string = - if (not chatItem.chatType.isOneToOne): - return chatItem.name + # handling reactions + var reactionsObj: JsonNode + var reactionsCursor: string + discard responseObj.getProp("reactions", reactionsObj) + discard responseObj.getProp("reactionsCursor", reactionsCursor) - if (self.contacts.hasKey(chatItem.id) and - self.contacts[chatItem.id].hasNickname()): - return self.contacts[chatItem.id].localNickname + self.emojiCursor[chatId] = reactionsCursor; - if chatItem.ensName != "": - return "@" & userName(chatItem.ensName).userName(true) + var reactions: seq[Reaction] = @[] + if (reactionsObj.kind == JArray): + for jsonMsg in reactionsObj: + reactions.add(jsonMsg.toReaction) - return self.userNameOrAlias(chatItem.id) + # handling pinned messages + var pinnedMsgObj: JsonNode + var pinnedMsgCursor: string + discard responseObj.getProp("pinnedMessages", pinnedMsgObj) + discard responseObj.getProp("pinnedMessagesCursor", pinnedMsgCursor) + + self.pinnedMsgCursor[chatId] = pinnedMsgCursor + + var pinnedMessages: seq[Message] = @[] + if (pinnedMsgObj.kind == JArray): + for jsonMsg in pinnedMsgObj: + var msgObj: JsonNode + if(jsonMsg.getProp("message", msgObj)): + var msg: Message + msg = msgObj.toMessage() + discard jsonMsg.getProp("pinnedBy", msg.pinnedBy) + pinnedMessages.add(msg) + + # notify view + self.events.emit("messagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: messages)) + self.events.emit("reactionsLoaded", ReactionsLoadedArgs(reactions: reactions)) + self.events.emit("pinnedMessagesLoaded", MsgsLoadedArgs(chatId: chatId, messages: pinnedMessages)) + +proc userNameOrAlias*(self: ChatModel, pubKey: string, + prettyForm: bool = false): string = + ## Returns ens name or alias, in case if prettyForm is true and ens name + ## ends with ".stateofus.eth" that part will be removed. + var alias: string + if self.contacts.hasKey(pubKey): + alias = ens.userNameOrAlias(self.contacts[pubKey]) + else: + alias = generateAlias(pubKey) + + if (prettyForm and alias.endsWith(".stateofus.eth")): + alias = alias[0 .. ^15] + + return alias + +proc chatName*(self: ChatModel, chatItem: Chat): string = + if (not chatItem.chatType.isOneToOne): + return chatItem.name + + if (self.contacts.hasKey(chatItem.id) and + self.contacts[chatItem.id].hasNickname()): + return self.contacts[chatItem.id].localNickname + + if chatItem.ensName != "": + return "@" & userName(chatItem.ensName).userName(true) + + return self.userNameOrAlias(chatItem.id) + +proc isMessageCursorSet*(self: ChatModel, channelId: string): bool = + self.msgCursor.hasKey(channelId) + +proc getCurrentMessageCursor*(self: ChatModel, channelId: string): string = + if(not self.msgCursor.hasKey(channelId)): + self.msgCursor[channelId] = "" + + return self.msgCursor[channelId] + +proc isEmojiCursorSet*(self: ChatModel, channelId: string): bool = + self.emojiCursor.hasKey(channelId) + +proc getCurrentEmojiCursor*(self: ChatModel, channelId: string): string = + if(not self.emojiCursor.hasKey(channelId)): + self.emojiCursor[channelId] = "" + + return self.emojiCursor[channelId] + +proc isPinnedMessageCursorSet*(self: ChatModel, channelId: string): bool = + self.pinnedMsgCursor.hasKey(channelId) + +proc getCurrentPinnedMessageCursor*(self: ChatModel, channelId: string): string = + if(not self.pinnedMsgCursor.hasKey(channelId)): + self.pinnedMsgCursor[channelId] = "" + + return self.pinnedMsgCursor[channelId] \ No newline at end of file diff --git a/src/status/chat/chat.nim b/src/status/chat/chat.nim index bd18930e18..693447d99a 100644 --- a/src/status/chat/chat.nim +++ b/src/status/chat/chat.nim @@ -1,189 +1,6 @@ -import strformat, json, sequtils, tables -from message import Message -import ../types +import ../types/[chat, community] -type ChatType* {.pure.}= enum - Unknown = 0, - OneToOne = 1, - Public = 2, - PrivateGroupChat = 3, - Profile = 4, - Timeline = 5 - CommunityChat = 6 - -type ActivityCenterNotificationType* {.pure.}= enum - Unknown = 0, - NewOneToOne = 1, - NewPrivateGroupChat = 2, - Mention = 3 - Reply = 4 - -proc isOneToOne*(self: ChatType): bool = self == ChatType.OneToOne -proc isTimeline*(self: ChatType): bool = self == ChatType.Timeline - -type ChatMember* = object - admin*: bool - id*: string - joined*: bool - identicon*: string - userName*: string - localNickname*: string - -proc toJsonNode*(self: ChatMember): JsonNode = - result = %* { - "id": self.id, - "admin": self.admin, - "joined": self.joined - } - -proc toJsonNode*(self: seq[ChatMember]): seq[JsonNode] = - result = map(self, proc(x: ChatMember): JsonNode = x.toJsonNode) - -type ChatMembershipEvent* = object - chatId*: string - clockValue*: int64 - fromKey*: string - name*: string - members*: seq[string] - rawPayload*: string - signature*: string - eventType*: int - -proc toJsonNode*(self: ChatMembershipEvent): JsonNode = - result = %* { - "chatId": self.chatId, - "name": self.name, - "clockValue": self.clockValue, - "from": self.fromKey, - "members": self.members, - "rawPayload": self.rawPayload, - "signature": self.signature, - "type": self.eventType - } - -proc toJsonNode*(self: seq[ChatMembershipEvent]): seq[JsonNode] = - result = map(self, proc(x: ChatMembershipEvent): JsonNode = x.toJsonNode) - -type Chat* = ref object - id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat - communityId*: string - private*: bool - categoryId*: string - name*: string - description*: string - color*: string - identicon*: string - isActive*: bool # indicates whether the chat has been soft deleted - chatType*: ChatType - timestamp*: int64 # indicates the last time this chat has received/sent a message - joined*: int64 # indicates when the user joined the chat last time - lastClockValue*: int64 # indicates the last clock value to be used when sending messages - deletedAtClockValue*: int64 # indicates the clock value at time of deletion, messages with lower clock value of this should be discarded - unviewedMessagesCount*: int - unviewedMentionsCount*: int - lastMessage*: Message - members*: seq[ChatMember] - membershipUpdateEvents*: seq[ChatMembershipEvent] - mentionsCount*: int # Using this is not a good approach, we should instead use unviewedMentionsCount and refer to it always. - muted*: bool - canPost*: bool - ensName*: string - position*: int - -type RemovedMessage* = object - chatId*: string - messageId*: string - -type CommunityAccessLevel* = enum - unknown = 0 - public = 1 - invitationOnly = 2 - onRequest = 3 - -type CommunityMembershipRequest* = object - id*: string - publicKey*: string - chatId*: string - communityId*: string - state*: int - our*: string - -type CommunityCategory* = object - id*: string - name*: string - position*: int - -type StatusUpdateType* {.pure.}= enum - Unknown = 0, - Online = 1, - DoNotDisturb = 2 - -type StatusUpdate* = object - publicKey*: string - statusType*: StatusUpdateType - clock*: uint64 - text*: string - -type Community* = object - id*: string - name*: string - lastChannelSeen*: string - description*: string - chats*: seq[Chat] - categories*: seq[CommunityCategory] - members*: seq[string] - access*: int - unviewedMessagesCount*: int - unviewedMentionsCount*: int - admin*: bool - joined*: bool - verified*: bool - ensOnly*: bool - canRequestAccess*: bool - canManageUsers*: bool - canJoin*: bool - isMember*: bool - muted*: bool - communityImage*: IdentityImage - membershipRequests*: seq[CommunityMembershipRequest] - communityColor*: string - memberStatus*: OrderedTable[string, StatusUpdate] - -type ActivityCenterNotification* = ref object of RootObj - id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat - chatId*: string - name*: string - author*: string - notificationType*: ActivityCenterNotificationType - message*: Message - timestamp*: int64 - read*: bool - dismissed*: bool - accepted*: bool - -proc `$`*(self: Chat): string = - result = fmt"Chat(id:{self.id}, name:{self.name}, active:{self.isActive}, type:{self.chatType})" - -proc `$`*(self: Community): string = - result = fmt"Community(id:{self.id}, name:{self.name}, description:{self.description}" - -proc toJsonNode*(self: Chat): JsonNode = - result = %* { - "active": self.isActive, - "chatType": self.chatType.int, - "color": self.color, - "deletedAtClockValue": self.deletedAtClockValue, - "id": self.id, - "lastClockValue": self.lastClockValue, - "lastMessage": nil, - "members": self.members.toJsonNode, - "membershipUpdateEvents": self.membershipUpdateEvents.toJsonNode, - "name": (if self.ensName != "": self.ensName else: self.name), - "timestamp": self.timestamp, - "unviewedMessagesCount": self.unviewedMessagesCount, - "joined": self.joined, - "position": self.position - } +export chat, community proc findIndexById*(self: seq[Chat], id: string): int = result = -1 diff --git a/src/status/chat/message.nim b/src/status/chat/message.nim deleted file mode 100644 index 2bf13eb406..0000000000 --- a/src/status/chat/message.nim +++ /dev/null @@ -1,90 +0,0 @@ -import strformat - -type ContentType* {.pure.} = enum - FetchMoreMessagesButton = -2 - ChatIdentifier = -1, - Unknown = 0, - Message = 1, - Sticker = 2, - Status = 3, - Emoji = 4, - Transaction = 5, - Group = 6, - Image = 7, - Audio = 8 - Community = 9 - Gap = 10 - Edit = 11 - -type TextItem* = object - textType*: string - children*: seq[TextItem] - literal*: string - destination*: string - -type CommandParameters* = object - id*: string - fromAddress*: string - address*: string - contract*: string - value*: string - transactionHash*: string - commandState*: int - signature*: string - -proc `$`*(self: CommandParameters): string = - result = fmt"CommandParameters(id:{self.id}, fromAddr:{self.fromAddress}, addr:{self.address}, contract:{self.contract}, value:{self.value}, transactionHash:{self.transactionHash}, commandState:{self.commandState}, signature:{self.signature})" - -type Message* = object - alias*: string - userName*: string - localName*: string - chatId*: string - clock*: int - gapFrom*: int - gapTo*: int - commandParameters*: CommandParameters - contentType*: ContentType - ensName*: string - fromAuthor*: string - id*: string - identicon*: string - lineCount*: int - localChatId*: string - messageType*: string # ??? - parsedText*: seq[TextItem] - # quotedMessage: # ??? - replace*: string - responseTo*: string - rtl*: bool # ??? - seen*: bool # ??? - sticker*: string - stickerPackId*: int - text*: string - timestamp*: string - editedAt*: string - whisperTimestamp*: string - isCurrentUser*: bool - stickerHash*: string - outgoingStatus*: string - linkUrls*: string - image*: string - audio*: string - communityId*: string - audioDurationMs*: int - hasMention*: bool - isPinned*: bool - pinnedBy*: string - deleted*: bool - -type Reaction* = object - id*: string - chatId*: string - fromAccount*: string - messageId*: string - emojiId*: int - retracted*: bool - - -proc `$`*(self: Message): string = - result = fmt"Message(id:{self.id}, chatId:{self.chatId}, clock:{self.clock}, from:{self.fromAuthor}, contentType:{self.contentType})" diff --git a/src/status/chat/utils.nim b/src/status/chat/utils.nim index c96bdd5f38..e7d941f864 100644 --- a/src/status/chat/utils.nim +++ b/src/status/chat/utils.nim @@ -1,4 +1,7 @@ -proc formatChatUpdate(response: JsonNode): (seq[Chat], seq[Message]) = +import json +import ../types/[message, chat] + +proc formatChatUpdate*(response: JsonNode): (seq[Chat], seq[Message]) = var chats: seq[Chat] = @[] var messages: seq[Message] = @[] if response["result"]{"messages"} != nil: diff --git a/src/status/devices.nim b/src/status/devices.nim index a0291ff142..329c611a37 100644 --- a/src/status/devices.nim +++ b/src/status/devices.nim @@ -1,8 +1,7 @@ import system import libstatus/settings -import types +import types/[setting, installation] import libstatus/installations -import profile/devices import json proc setDeviceName*(name: string) = diff --git a/src/status/ens.nim b/src/status/ens.nim index 0923f1975a..b3c1c43b1c 100644 --- a/src/status/ens.nim +++ b/src/status/ens.nim @@ -7,7 +7,7 @@ import json_serialization import tables import strformat import libstatus/core -import types +import ./types/[transaction, setting, rpc_response] import utils import libstatus/wallet import stew/byteutils diff --git a/src/status/fleet.nim b/src/status/fleet.nim index 29bc1ad47c..3b19ac9b95 100644 --- a/src/status/fleet.nim +++ b/src/status/fleet.nim @@ -1,5 +1,6 @@ -import json -import types +import ./types/[fleet] + +export fleet type FleetModel* = ref object diff --git a/src/status/libstatus/accounts.nim b/src/status/libstatus/accounts.nim index f8b869da06..175da681e4 100644 --- a/src/status/libstatus/accounts.nim +++ b/src/status/libstatus/accounts.nim @@ -3,9 +3,9 @@ import json, os, nimcrypto, uuids, json_serialization, chronicles, strutils from status_go import multiAccountGenerateAndDeriveAddresses, generateAlias, identicon, saveAccountAndLogin, login, openAccounts, getNodeConfig import core import ../utils as utils -import ../types as types +import ../types/[account, fleet, rpc_response] +import ../../app_service/signals/[base] import accounts/constants -import ../signals/types as signal_types proc getNetworkConfig(currentNetwork: string): JsonNode = result = constants.DEFAULT_NETWORKS.first("id", currentNetwork) @@ -98,7 +98,7 @@ proc saveAccountAndLogin*( accountData: string, password: string, configJSON: string, - settingsJSON: string): types.Account = + settingsJSON: string): Account = let hashedPassword = hashPassword(password) let subaccountData = %* [ { @@ -180,7 +180,7 @@ proc getAccountSettings*(account: GeneratedAccount, defaultNetworks: JsonNode, i "installation-id": installationId } -proc setupAccount*(fleetConfig: FleetConfig, account: GeneratedAccount, password: string): types.Account = +proc setupAccount*(fleetConfig: FleetConfig, account: GeneratedAccount, password: string): Account = try: let storeDerivedResult = storeDerivedAccounts(account, password) let accountData = getAccountData(account) diff --git a/src/status/libstatus/browser.nim b/src/status/libstatus/browser.nim index 4b3c185cfc..7255a229d6 100644 --- a/src/status/libstatus/browser.nim +++ b/src/status/libstatus/browser.nim @@ -1,4 +1,4 @@ -import core, ../types, json, chronicles +import core, ../types/[bookmark], json, chronicles proc storeBookmark*(url: string, name: string): Bookmark = let payload = %* [{"url": url, "name": name}] diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index 6316667e39..94739b019c 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -1,8 +1,7 @@ import json, times, strutils, sequtils, chronicles, json_serialization, algorithm, strformat, sugar import core, ../utils -import ../chat/[chat, message] -import ../signals/messages -import ../types +import ../types/[chat, message, community, activity_center_notification, + status_update, rpc_response, setting, sticker] import ./settings as status_settings proc buildFilter*(chat: Chat):JsonNode = diff --git a/src/status/libstatus/contacts.nim b/src/status/libstatus/contacts.nim index e55cb50944..4e54ab8d5e 100644 --- a/src/status/libstatus/contacts.nim +++ b/src/status/libstatus/contacts.nim @@ -1,6 +1,5 @@ import json, strmisc, atomics -import core, ../utils, ../types -from ../profile/profile import Profile +import core, ../utils var contacts {.threadvar.}: JsonNode diff --git a/src/status/libstatus/edn_helpers.nim b/src/status/libstatus/edn_helpers.nim index b1e0e4cba6..f2873feeb2 100644 --- a/src/status/libstatus/edn_helpers.nim +++ b/src/status/libstatus/edn_helpers.nim @@ -1,6 +1,6 @@ import typetraits import edn, chronicles -import ../types # FIXME: there should be no type deps +import ../types/[sticker] # FIXME: there should be no type deps # forward declaration: proc parseNode[T](node: EdnNode, searchName: string): T diff --git a/src/status/libstatus/eth/contracts.nim b/src/status/libstatus/eth/contracts.nim index ef1245719c..f8eabfb649 100644 --- a/src/status/libstatus/eth/contracts.nim +++ b/src/status/libstatus/eth/contracts.nim @@ -5,7 +5,7 @@ import web3/ethtypes, stew/byteutils, nimcrypto, json_serialization, chronicles import - ../../types, ../settings, ../coder, transactions, methods, ../../utils + ../../types/[network], ../settings, ../coder, transactions, methods, ../../utils export GetPackData, PackData, BuyToken, ApproveAndCall, Transfer, BalanceOf, Register, SetPubkey, diff --git a/src/status/libstatus/eth/eth.nim b/src/status/libstatus/eth/eth.nim index a90bfd1e20..23e442ce9e 100644 --- a/src/status/libstatus/eth/eth.nim +++ b/src/status/libstatus/eth/eth.nim @@ -2,7 +2,7 @@ import web3/ethtypes import - transactions, ../../types + transactions, ../../types/[rpc_response] proc sendTransaction*(tx: var EthSend, password: string, success: var bool): string = success = true diff --git a/src/status/libstatus/eth/methods.nim b/src/status/libstatus/eth/methods.nim index 58c2b18ae7..85653f46a9 100644 --- a/src/status/libstatus/eth/methods.nim +++ b/src/status/libstatus/eth/methods.nim @@ -5,7 +5,7 @@ import nimcrypto, web3/[encoding, ethtypes] import - ../../types, ../coder, eth, transactions + ../../types/[rpc_response], ../coder, eth, transactions export sendTransaction diff --git a/src/status/libstatus/eth/transactions.nim b/src/status/libstatus/eth/transactions.nim index ff61c513eb..ee647ff66a 100644 --- a/src/status/libstatus/eth/transactions.nim +++ b/src/status/libstatus/eth/transactions.nim @@ -5,7 +5,7 @@ import json_serialization, chronicles, web3/ethtypes import - ../core, ../../types, ../conversions + ../core, ../../types/[rpc_response], ../conversions proc estimateGas*(tx: EthSend): RpcResponse = let response = core.callPrivateRPC("eth_estimateGas", %*[%tx]) diff --git a/src/status/libstatus/gif.nim b/src/status/libstatus/gif.nim index d75753ca52..a00afc68ad 100644 --- a/src/status/libstatus/gif.nim +++ b/src/status/libstatus/gif.nim @@ -1,6 +1,7 @@ import json -import ./settings, ../types +import ./settings +import ../types/[setting] proc getRecentGifs*(): JsonNode = return settings.getSetting[JsonNode](Setting.Gifs_Recent, %*{}) diff --git a/src/status/libstatus/settings.nim b/src/status/libstatus/settings.nim index c0463480eb..274e4828a9 100644 --- a/src/status/libstatus/settings.nim +++ b/src/status/libstatus/settings.nim @@ -5,8 +5,10 @@ import json_serialization, chronicles, uuids import - ./core, ../types, ../signals/types as statusgo_types, ./accounts/constants, - ../utils + ./core, ./accounts/constants, ../utils + +import ../types/[setting, network, fleet] +import ../../app_service/signals/[base] from status_go import nil diff --git a/src/status/libstatus/stickers.nim b/src/status/libstatus/stickers.nim index 9b3e38a07c..71d22b1bd5 100644 --- a/src/status/libstatus/stickers.nim +++ b/src/status/libstatus/stickers.nim @@ -8,7 +8,8 @@ import # vendor libs from nimcrypto import fromHex import # status-desktop libs - ./core as status, ../types, ./eth/contracts, ./settings, ./edn_helpers + ./core as status, ../types/[sticker, setting, rpc_response], + ./eth/contracts, ./settings, ./edn_helpers proc decodeContentHash*(value: string): string = if value == "": diff --git a/src/status/libstatus/tokens.nim b/src/status/libstatus/tokens.nim index 278a6f81ef..34e74e6ecd 100644 --- a/src/status/libstatus/tokens.nim +++ b/src/status/libstatus/tokens.nim @@ -5,8 +5,8 @@ import web3/[ethtypes, conversions], json_serialization import - ./settings, ./core, ./wallet, ./eth/contracts -from ../types import Setting, Network, RpcResponse, RpcException + ./settings, ./core, ./wallet, ./eth/contracts, + ../types/[setting, network, rpc_response] from ../utils import parseAddress logScope: diff --git a/src/status/libstatus/wallet.nim b/src/status/libstatus/wallet.nim index 3cff3d9889..de5b37f79c 100644 --- a/src/status/libstatus/wallet.nim +++ b/src/status/libstatus/wallet.nim @@ -1,5 +1,5 @@ import json, json, options, json_serialization, stint, chronicles -import core, conversions, ../types, ../utils, strutils, strformat +import core, conversions, ../types/[transaction, rpc_response], ../utils, strutils, strformat from status_go import validateMnemonic#, startWallet import ../wallet/account import web3/ethtypes @@ -31,17 +31,17 @@ proc getWalletAccounts*(): seq[WalletAccount] = proc getTransactionReceipt*(transactionHash: string): string = result = callPrivateRPC("eth_getTransactionReceipt", %* [transactionHash]) -proc getTransfersByAddress*(address: string, toBlock: Uint256, limit: int, loadMore: bool = false): seq[types.Transaction] = +proc getTransfersByAddress*(address: string, toBlock: Uint256, limit: int, loadMore: bool = false): seq[Transaction] = try: let toBlockParsed = "0x" & stint.toHex(toBlock) limitParsed = "0x" & limit.toHex.stripLeadingZeros transactionsResponse = getTransfersByAddress(address, toBlockParsed, limitParsed, loadMore) transactions = parseJson(transactionsResponse)["result"] - var accountTransactions: seq[types.Transaction] = @[] + var accountTransactions: seq[Transaction] = @[] for transaction in transactions: - accountTransactions.add(types.Transaction( + accountTransactions.add(Transaction( id: transaction["id"].getStr, typeValue: transaction["type"].getStr, address: transaction["address"].getStr, diff --git a/src/status/network.nim b/src/status/network.nim index 90166235a5..95376a52b6 100644 --- a/src/status/network.nim +++ b/src/status/network.nim @@ -4,7 +4,7 @@ import libstatus/settings import json import uuids import json_serialization -import types +import ./types/[setting] logScope: topics = "network-model" diff --git a/src/status/notifications/os_notifications.nim b/src/status/notifications/os_notifications.nim index e2fee2d760..a379134dc3 100644 --- a/src/status/notifications/os_notifications.nim +++ b/src/status/notifications/os_notifications.nim @@ -1,49 +1,20 @@ -import NimQml, json +import json + +import ../types/[os_notification] import ../../eventemitter -import os_notification_details -type - OsNotificationsArgs* = ref object of Args - details*: OsNotificationDetails - -QtObject: - type OsNotifications* = ref object of QObject - events: EventEmitter - notification: StatusOSNotificationObject - - proc setup(self: OsNotifications, events: EventEmitter) = - self.QObject.setup - self.events = events - self.notification = newStatusOSNotificationObject() - signalConnect(self.notification, "notificationClicked(QString)", self, - "onNotificationClicked(QString)", 2) +type OsNotifications* = ref object + events: EventEmitter - proc delete*(self: OsNotifications) = - self.notification.delete - self.QObject.delete +proc delete*(self: OsNotifications) = + discard - proc newOsNotifications*(events: EventEmitter): OsNotifications = - new(result, delete) - result.setup(events) +proc newOsNotifications*(events: EventEmitter): OsNotifications = + result = OsNotifications() + result.events = events - proc showNotification*(self: OsNotifications, title: string, - message: string, details: OsNotificationDetails, useOSNotifications: bool) = - ## This method will add new notification to the Notification center. Param - ## "details" is used to uniquely define a notification bubble. - - # Whether we need to use OS notifications or not should be checked only here, - # but because we don't have settings class on the nim side yet, we're using - # useOSNotifications param sent from the qml side. Once we are able to check - # that here, we will remove useOSNotifications param from this method. - if(not useOSNotifications): - return - - let identifier = $(details.toJsonNode()) - self.notification.showNotification(title, message, identifier) - - proc onNotificationClicked*(self: OsNotifications, identifier: string) {.slot.} = - ## This slot is called once user clicks a notificaiton bubble, "identifier" - ## contains data which uniquely define that notification. - let details = toOsNotificationDetails(parseJson(identifier)) - self.events.emit("osNotificationClicked", - OsNotificationsArgs(details: details)) \ No newline at end of file +proc onNotificationClicked*(self: OsNotifications, identifier: string) = + ## This slot is called once user clicks a notificaiton bubble, "identifier" + ## contains data which uniquely define that notification. + let details = toOsNotificationDetails(parseJson(identifier)) + self.events.emit("osNotificationClicked", OsNotificationsArgs(details: details)) \ No newline at end of file diff --git a/src/status/profile.nim b/src/status/profile.nim index 79b7b20532..5cb83b927d 100644 --- a/src/status/profile.nim +++ b/src/status/profile.nim @@ -1,5 +1,5 @@ import json -import types +import ./types/[identity_image] import profile/profile import libstatus/core as libstatus_core import libstatus/accounts as status_accounts diff --git a/src/status/profile/profile.nim b/src/status/profile/profile.nim index 69341db835..213b467d89 100644 --- a/src/status/profile/profile.nim +++ b/src/status/profile/profile.nim @@ -1,18 +1,7 @@ -import json, strformat -import ../types +import json +import ../types/[profile, account] -type Profile* = ref object - id*, alias*, username*, identicon*, address*, ensName*, localNickname*: string - ensVerified*: bool - messagesFromContactsOnly*: bool - sendUserStatus*: bool - currentUserStatus*: int - identityImage*: IdentityImage - appearance*: int - systemTags*: seq[string] - -proc `$`*(self: Profile): string = - return fmt"Profile(id:{self.id}, username:{self.username})" +export profile const contactAdded* = ":contact/added" const contactBlocked* = ":contact/blocked" @@ -38,33 +27,3 @@ proc toProfileModel*(account: Account): Profile = appearance: 0, systemTags: @[] ) - -proc toProfileModel*(profile: JsonNode): Profile = - var systemTags: seq[string] = @[] - if profile["systemTags"].kind != JNull: - systemTags = profile["systemTags"].to(seq[string]) - - result = Profile( - id: profile["id"].str, - username: profile["alias"].str, - identicon: profile["identicon"].str, - identityImage: IdentityImage(), - address: profile["id"].str, - alias: profile["alias"].str, - ensName: "", - ensVerified: profile["ensVerified"].getBool, - appearance: 0, - systemTags: systemTags - ) - - if profile.hasKey("name"): - result.ensName = profile["name"].str - - if profile.hasKey("localNickname"): - result.localNickname = profile["localNickname"].str - - if profile.hasKey("images") and profile["images"].kind != JNull: - if profile["images"].hasKey("thumbnail"): - result.identityImage.thumbnail = profile["images"]["thumbnail"]["uri"].str - if profile["images"].hasKey("large"): - result.identityImage.large = profile["images"]["large"]["uri"].str diff --git a/src/status/provider.nim b/src/status/provider.nim index 60e80f6666..ae22604360 100644 --- a/src/status/provider.nim +++ b/src/status/provider.nim @@ -1,6 +1,6 @@ import ens, wallet, permissions, utils import ../eventemitter -import types +import ./types/[setting] import utils import libstatus/accounts import libstatus/core diff --git a/src/status/settings.nim b/src/status/settings.nim index 641f6e1946..8f30ac26f5 100644 --- a/src/status/settings.nim +++ b/src/status/settings.nim @@ -5,10 +5,8 @@ import import libstatus/settings as libstatus_settings import ../eventemitter -import signals/types - -#TODO: temporary? -import types as LibStatusTypes +import ./types/[fleet, network, setting] +import ../app_service/signals/[base] type SettingsModel* = ref object @@ -34,7 +32,7 @@ proc getSetting2*[T](name: Setting, defaultValue: T, useCached: bool = true): T proc getSetting2*[T](name: Setting, useCached: bool = true): T = result = libstatus_settings.getSetting[T](name, useCached) -proc getCurrentNetworkDetails*(self: SettingsModel): LibStatusTypes.NetworkDetails = +proc getCurrentNetworkDetails*(self: SettingsModel): NetworkDetails = result = libstatus_settings.getCurrentNetworkDetails() proc getMailservers*(self: SettingsModel):JsonNode = diff --git a/src/status/signals/communities.nim b/src/status/signals/communities.nim deleted file mode 100644 index 83aa0f3421..0000000000 --- a/src/status/signals/communities.nim +++ /dev/null @@ -1,10 +0,0 @@ -import json, strutils, sequtils, sugar, chronicles -import json_serialization -import ../types as status_types -import ../chat/[chat] -import types, messages - -proc fromEvent*(event: JsonNode): Signal = - var signal: CommunitySignal = CommunitySignal() - signal.community = event["event"].toCommunity() - result = signal diff --git a/src/status/signals/messages.nim b/src/status/signals/messages.nim deleted file mode 100644 index 7564a188cf..0000000000 --- a/src/status/signals/messages.nim +++ /dev/null @@ -1,456 +0,0 @@ -import json, random, strutils, sequtils, sugar, chronicles, tables -import json_serialization -import ../utils -import ../wallet/account -import ../libstatus/wallet as status_wallet -import ../libstatus/accounts as status_accounts -import ../libstatus/accounts/constants as constants -import ../libstatus/settings as status_settings -import ../libstatus/tokens as status_tokens -import ../types as status_types -import ../libstatus/eth/contracts as status_contracts -import ../chat/[chat, message] -import ../profile/[profile, devices] -import types -import web3/conversions -from ../utils import parseAddress, wei2Eth - -proc toMessage*(jsonMsg: JsonNode): Message - -proc toChat*(jsonChat: JsonNode): Chat - -proc toStatusUpdate*(jsonStatusUpdate: JsonNode): StatusUpdate - -proc toReaction*(jsonReaction: JsonNode): Reaction - -proc toCommunity*(jsonCommunity: JsonNode): Community - -proc toCommunityMembershipRequest*(jsonCommunityMembershipRequest: JsonNode): CommunityMembershipRequest - -proc toActivityCenterNotification*(jsonNotification: JsonNode): ActivityCenterNotification - -proc toRemovedMessage*(jsonRemovedMessage: JsonNode): RemovedMessage - -proc fromEvent*(event: JsonNode): Signal = - var signal:MessageSignal = MessageSignal() - signal.messages = @[] - signal.contacts = @[] - - if event["event"]{"contacts"} != nil: - for jsonContact in event["event"]["contacts"]: - signal.contacts.add(jsonContact.toProfileModel()) - - var chatsWithMentions: seq[string] = @[] - - if event["event"]{"messages"} != nil: - for jsonMsg in event["event"]["messages"]: - var message = jsonMsg.toMessage() - if message.hasMention: - chatsWithMentions.add(message.chatId) - signal.messages.add(message) - - if event["event"]{"chats"} != nil: - for jsonChat in event["event"]["chats"]: - var chat = jsonChat.toChat - if chatsWithMentions.contains(chat.id): - chat.mentionsCount.inc - signal.chats.add(chat) - - if event["event"]{"statusUpdates"} != nil: - for jsonStatusUpdate in event["event"]["statusUpdates"]: - var statusUpdate = jsonStatusUpdate.toStatusUpdate - signal.statusUpdates.add(statusUpdate) - - if event["event"]{"installations"} != nil: - for jsonInstallation in event["event"]["installations"]: - signal.installations.add(jsonInstallation.toInstallation) - - if event["event"]{"emojiReactions"} != nil: - for jsonReaction in event["event"]["emojiReactions"]: - signal.emojiReactions.add(jsonReaction.toReaction) - - if event["event"]{"communities"} != nil: - for jsonCommunity in event["event"]["communities"]: - signal.communities.add(jsonCommunity.toCommunity) - - if event["event"]{"requestsToJoinCommunity"} != nil: - for jsonCommunity in event["event"]["requestsToJoinCommunity"]: - signal.membershipRequests.add(jsonCommunity.toCommunityMembershipRequest) - - if event["event"]{"removedMessages"} != nil: - for jsonRemovedMessage in event["event"]["removedMessages"]: - signal.deletedMessages.add(jsonRemovedMessage.toRemovedMessage) - - if event["event"]{"activityCenterNotifications"} != nil: - for jsonNotification in event["event"]["activityCenterNotifications"]: - signal.activityCenterNotification.add(jsonNotification.toActivityCenterNotification()) - - if event["event"]{"pinMessages"} != nil: - for jsonPinnedMessage in event["event"]["pinMessages"]: - var contentType: ContentType - try: - contentType = ContentType(jsonPinnedMessage{"contentType"}.getInt) - except: - warn "Unknown content type received", type = jsonPinnedMessage{"contentType"}.getInt - contentType = ContentType.Message - signal.pinnedMessages.add(Message( - id: jsonPinnedMessage{"message_id"}.getStr, - chatId: jsonPinnedMessage{"chat_id"}.getStr, - localChatId: jsonPinnedMessage{"localChatId"}.getStr, - pinnedBy: jsonPinnedMessage{"from"}.getStr, - identicon: jsonPinnedMessage{"identicon"}.getStr, - alias: jsonPinnedMessage{"alias"}.getStr, - clock: jsonPinnedMessage{"clock"}.getInt, - isPinned: jsonPinnedMessage{"pinned"}.getBool, - contentType: contentType - )) - - result = signal - -proc toChatMember*(jsonMember: JsonNode): ChatMember = - let pubkey = jsonMember["id"].getStr - - result = ChatMember( - admin: jsonMember["admin"].getBool, - id: pubkey, - joined: jsonMember["joined"].getBool, - identicon: generateIdenticon(pubkey), - userName: generateAlias(pubkey) - ) - -proc toChatMembershipEvent*(jsonMembership: JsonNode): ChatMembershipEvent = - result = ChatMembershipEvent( - chatId: jsonMembership["chatId"].getStr, - clockValue: jsonMembership["clockValue"].getBiggestInt, - fromKey: jsonMembership["from"].getStr, - rawPayload: jsonMembership["rawPayload"].getStr, - signature: jsonMembership["signature"].getStr, - eventType: jsonMembership["type"].getInt, - name: jsonMembership{"name"}.getStr, - members: @[] - ) - if jsonMembership{"members"} != nil: - for member in jsonMembership["members"]: - result.members.add(member.getStr) - - -const channelColors* = ["#fa6565", "#7cda00", "#887af9", "#51d0f0", "#FE8F59", "#d37ef4"] - -proc newChat*(id: string, chatType: ChatType): Chat = - randomize() - - result = Chat( - id: id, - color: channelColors[rand(channelColors.len - 1)], - isActive: true, - chatType: chatType, - timestamp: 0, - lastClockValue: 0, - deletedAtClockValue: 0, - unviewedMessagesCount: 0, - mentionsCount: 0, - members: @[] - ) - - if chatType == ChatType.OneToOne: - result.identicon = generateIdenticon(id) - result.name = generateAlias(id) - else: - result.name = id - -proc toChat*(jsonChat: JsonNode): Chat = - - let chatTypeInt = jsonChat{"chatType"}.getInt - let chatType: ChatType = if chatTypeInt >= ord(low(ChatType)) or chatTypeInt <= ord(high(ChatType)): ChatType(chatTypeInt) else: ChatType.Unknown - - result = Chat( - id: jsonChat{"id"}.getStr, - communityId: jsonChat{"communityId"}.getStr, - name: jsonChat{"name"}.getStr, - description: jsonChat{"description"}.getStr, - identicon: "", - color: jsonChat{"color"}.getStr, - isActive: jsonChat{"active"}.getBool, - chatType: chatType, - timestamp: jsonChat{"timestamp"}.getBiggestInt, - lastClockValue: jsonChat{"lastClockValue"}.getBiggestInt, - deletedAtClockValue: jsonChat{"deletedAtClockValue"}.getBiggestInt, - unviewedMessagesCount: jsonChat{"unviewedMessagesCount"}.getInt, - unviewedMentionsCount: jsonChat{"unviewedMentionsCount"}.getInt, - mentionsCount: 0, - muted: false, - ensName: "", - joined: 0, - private: jsonChat{"private"}.getBool - ) - - if jsonChat.hasKey("muted") and jsonChat["muted"].kind != JNull: - result.muted = jsonChat["muted"].getBool - - if jsonChat["lastMessage"].kind != JNull: - result.lastMessage = jsonChat{"lastMessage"}.toMessage() - - if jsonChat.hasKey("joined") and jsonChat["joined"].kind != JNull: - result.joined = jsonChat{"joined"}.getInt - - if result.chatType == ChatType.OneToOne: - result.identicon = generateIdenticon(result.id) - if result.name.endsWith(".eth"): - result.ensName = result.name - if result.name == "": - result.name = generateAlias(result.id) - - if jsonChat["members"].kind != JNull: - result.members = @[] - for jsonMember in jsonChat["members"]: - result.members.add(jsonMember.toChatMember) - - if jsonChat["membershipUpdateEvents"].kind != JNull: - result.membershipUpdateEvents = @[] - for jsonMember in jsonChat["membershipUpdateEvents"]: - result.membershipUpdateEvents.add(jsonMember.toChatMembershipEvent) - -proc toStatusUpdate*(jsonStatusUpdate: JsonNode): StatusUpdate = - let statusTypeInt = jsonStatusUpdate{"statusType"}.getInt - let statusType: StatusUpdateType = if statusTypeInt >= ord(low(StatusUpdateType)) or statusTypeInt <= ord(high(StatusUpdateType)): StatusUpdateType(statusTypeInt) else: StatusUpdateType.Unknown - result = StatusUpdate( - publicKey: jsonStatusUpdate{"publicKey"}.getStr, - statusType: statusType, - clock: uint64(jsonStatusUpdate{"clock"}.getBiggestInt), - text: jsonStatusUpdate{"text"}.getStr - ) - -proc toCommunity*(jsonCommunity: JsonNode): Community = - result = Community( - id: jsonCommunity{"id"}.getStr, - name: jsonCommunity{"name"}.getStr, - description: jsonCommunity{"description"}.getStr, - access: jsonCommunity{"permissions"}{"access"}.getInt, - admin: jsonCommunity{"admin"}.getBool, - joined: jsonCommunity{"joined"}.getBool, - verified: jsonCommunity{"verified"}.getBool, - ensOnly: jsonCommunity{"permissions"}{"ens_only"}.getBool, - canRequestAccess: jsonCommunity{"canRequestAccess"}.getBool, - canManageUsers: jsonCommunity{"canManageUsers"}.getBool, - canJoin: jsonCommunity{"canJoin"}.getBool, - isMember: jsonCommunity{"isMember"}.getBool, - muted: jsonCommunity{"muted"}.getBool, - chats: newSeq[Chat](), - members: newSeq[string](), - communityColor: jsonCommunity{"color"}.getStr, - communityImage: IdentityImage() - ) - - result.memberStatus = initOrderedTable[string, StatusUpdate]() - - if jsonCommunity.hasKey("images") and jsonCommunity["images"].kind != JNull: - if jsonCommunity["images"].hasKey("thumbnail"): - result.communityImage.thumbnail = jsonCommunity["images"]["thumbnail"]["uri"].str - if jsonCommunity["images"].hasKey("large"): - result.communityImage.large = jsonCommunity["images"]["large"]["uri"].str - - if jsonCommunity.hasKey("chats") and jsonCommunity["chats"].kind != JNull: - for chatId, chat in jsonCommunity{"chats"}: - result.chats.add(Chat( - id: result.id & chatId, - categoryId: chat{"categoryID"}.getStr(), - communityId: result.id, - name: chat{"name"}.getStr, - description: chat{"description"}.getStr, - canPost: chat{"canPost"}.getBool, - chatType: ChatType.CommunityChat, - private: chat{"permissions"}{"private"}.getBool, - position: chat{"position"}.getInt - )) - - if jsonCommunity.hasKey("categories") and jsonCommunity["categories"].kind != JNull: - for catId, cat in jsonCommunity{"categories"}: - result.categories.add(CommunityCategory( - id: catId, - name: cat{"name"}.getStr, - position: cat{"position"}.getInt - )) - - if jsonCommunity.hasKey("members") and jsonCommunity["members"].kind != JNull: - # memberInfo is empty for now - for memberPubKey, memeberInfo in jsonCommunity{"members"}: - result.members.add(memberPubKey) - -proc toCommunityMembershipRequest*(jsonCommunityMembershipRequest: JsonNode): CommunityMembershipRequest = - result = CommunityMembershipRequest( - id: jsonCommunityMembershipRequest{"id"}.getStr, - publicKey: jsonCommunityMembershipRequest{"publicKey"}.getStr, - chatId: jsonCommunityMembershipRequest{"chatId"}.getStr, - state: jsonCommunityMembershipRequest{"state"}.getInt, - communityId: jsonCommunityMembershipRequest{"communityId"}.getStr, - our: jsonCommunityMembershipRequest{"our"}.getStr, - ) - -proc toRemovedMessage*(jsonRemovedMessage: JsonNode): RemovedMessage = - result = RemovedMessage( - chatId: jsonRemovedMessage{"chatId"}.getStr, - messageId: jsonRemovedMessage{"messageId"}.getStr, - ) - -proc toTextItem*(jsonText: JsonNode): TextItem = - result = TextItem( - literal: jsonText{"literal"}.getStr, - textType: jsonText{"type"}.getStr, - destination: jsonText{"destination"}.getStr, - children: @[] - ) - if (result.literal.startsWith("statusim://")): - result.textType = "link" - # TODO isolate the link only - result.destination = result.literal - - if jsonText.hasKey("children") and jsonText["children"].kind != JNull: - for child in jsonText["children"]: - result.children.add(child.toTextItem) - -proc currentUserWalletContainsAddress(address: string): bool = - if (address.len == 0): - return false - - let accounts = status_wallet.getWalletAccounts() - for acc in accounts: - if (acc.address == address): - return true - - return false - -proc toMessage*(jsonMsg: JsonNode): Message = - let publicChatKey = status_settings.getSetting[string](Setting.PublicKey, "0x0") - - var contentType: ContentType - try: - contentType = ContentType(jsonMsg{"contentType"}.getInt) - except: - warn "Unknown content type received", type = jsonMsg{"contentType"}.getInt - contentType = ContentType.Message - - var message = Message( - alias: jsonMsg{"alias"}.getStr, - userName: "", - localName: "", - chatId: jsonMsg{"localChatId"}.getStr, - clock: jsonMsg{"clock"}.getInt, - contentType: contentType, - ensName: jsonMsg{"ensName"}.getStr, - fromAuthor: jsonMsg{"from"}.getStr, - id: jsonMsg{"id"}.getStr, - identicon: jsonMsg{"identicon"}.getStr, - lineCount: jsonMsg{"lineCount"}.getInt, - localChatId: jsonMsg{"localChatId"}.getStr, - messageType: jsonMsg{"messageType"}.getStr, - replace: jsonMsg{"replace"}.getStr, - editedAt: $jsonMsg{"editedAt"}.getInt, - responseTo: jsonMsg{"responseTo"}.getStr, - rtl: jsonMsg{"rtl"}.getBool, - seen: jsonMsg{"seen"}.getBool, - text: jsonMsg{"text"}.getStr, - timestamp: $jsonMsg{"timestamp"}.getInt, - whisperTimestamp: $jsonMsg{"whisperTimestamp"}.getInt, - outgoingStatus: $jsonMsg{"outgoingStatus"}.getStr, - isCurrentUser: publicChatKey == jsonMsg{"from"}.getStr, - stickerHash: "", - stickerPackId: -1, - parsedText: @[], - linkUrls: "", - image: $jsonMsg{"image"}.getStr, - audio: $jsonMsg{"audio"}.getStr, - communityId: $jsonMsg{"communityId"}.getStr, - audioDurationMs: jsonMsg{"audioDurationMs"}.getInt, - deleted: jsonMsg{"deleted"}.getBool, - hasMention: false - ) - - if contentType == ContentType.Gap: - message.gapFrom = jsonMsg["gapParameters"]["from"].getInt - message.gapTo = jsonMsg["gapParameters"]["to"].getInt - - if jsonMsg.contains("parsedText") and jsonMsg{"parsedText"}.kind != JNull: - for text in jsonMsg{"parsedText"}: - message.parsedText.add(text.toTextItem) - - message.linkUrls = concat(message.parsedText.map(t => t.children.filter(c => c.textType == "link"))) - .filter(t => t.destination.startsWith("http") or t.destination.startsWith("statusim://")) - .map(t => t.destination) - .join(" ") - - if message.contentType == ContentType.Sticker: - message.stickerHash = jsonMsg["sticker"]["hash"].getStr - message.stickerPackId = jsonMsg["sticker"]["pack"].getInt - - if message.contentType == ContentType.Transaction: - let - allContracts = getErc20Contracts().concat(getCustomTokens()) - ethereum = newErc20Contract("Ethereum", Network.Mainnet, parseAddress(constants.ZERO_ADDRESS), "ETH", 18, true) - tokenAddress = jsonMsg["commandParameters"]["contract"].getStr - tokenContract = if tokenAddress == "": ethereum else: allContracts.getErc20ContractByAddress(parseAddress(tokenAddress)) - tokenContractStr = if tokenContract == nil: "{}" else: $(Json.encode(tokenContract)) - var weiStr = if tokenContract == nil: "0" else: wei2Eth(jsonMsg["commandParameters"]["value"].getStr, tokenContract.decimals) - weiStr.trimZeros() - - # TODO find a way to use json_seralization for this. When I try, I get an error - message.commandParameters = CommandParameters( - id: jsonMsg["commandParameters"]["id"].getStr, - fromAddress: jsonMsg["commandParameters"]["from"].getStr, - address: jsonMsg["commandParameters"]["address"].getStr, - contract: tokenContractStr, - value: weiStr, - transactionHash: jsonMsg["commandParameters"]["transactionHash"].getStr, - commandState: jsonMsg["commandParameters"]["commandState"].getInt, - signature: jsonMsg["commandParameters"]["signature"].getStr - ) - - # This is kind of a workaround in case we're processing a transaction message. The reason for - # that is a message where a recipient accepted to share his address with sender. In that message - # a recipient's public key is set as a "from" property of a "Message" object and we cannot - # determine which of two users has initiated transaction actually. - # - # To overcome this we're checking if the "from" address from the "commandParameters" object of - # the "Message" is contained as an address in the wallet of logged in user. If yes, means that - # currently logged in user has initiated a transaction (he is a sender), otherwise currently - # logged in user is a recipient. - message.isCurrentUser = currentUserWalletContainsAddress(message.commandParameters.fromAddress) - - message.hasMention = concat(message.parsedText.map( - t => t.children.filter( - c => c.textType == "mention" and c.literal == publicChatKey))).len > 0 - - result = message - -proc toReaction*(jsonReaction: JsonNode): Reaction = - result = Reaction( - id: jsonReaction{"id"}.getStr, - chatId: jsonReaction{"chatId"}.getStr, - fromAccount: jsonReaction{"from"}.getStr, - messageId: jsonReaction{"messageId"}.getStr, - emojiId: jsonReaction{"emojiId"}.getInt, - retracted: jsonReaction{"retracted"}.getBool - ) - -proc toActivityCenterNotification*(jsonNotification: JsonNode): ActivityCenterNotification = - var activityCenterNotificationType: ActivityCenterNotificationType - try: - activityCenterNotificationType = ActivityCenterNotificationType(jsonNotification{"type"}.getInt) - except: - warn "Unknown notification type received", type = jsonNotification{"type"}.getInt - activityCenterNotificationType = ActivityCenterNotificationType.Unknown - result = ActivityCenterNotification( - id: jsonNotification{"id"}.getStr, - chatId: jsonNotification{"chatId"}.getStr, - name: jsonNotification{"name"}.getStr, - author: jsonNotification{"author"}.getStr, - notificationType: activityCenterNotificationType, - timestamp: jsonNotification{"timestamp"}.getInt, - read: jsonNotification{"read"}.getBool, - dismissed: jsonNotification{"dismissed"}.getBool, - accepted: jsonNotification{"accepted"}.getBool - ) - - if jsonNotification.contains("message") and jsonNotification{"message"}.kind != JNull: - result.message = jsonNotification{"message"}.toMessage() - elif activityCenterNotificationType == ActivityCenterNotificationType.NewOneToOne and jsonNotification.contains("lastMessage") and jsonNotification{"lastMessage"}.kind != JNull: - result.message = jsonNotification{"lastMessage"}.toMessage() diff --git a/src/status/signals/types.nim b/src/status/signals/types.nim deleted file mode 100644 index 8527868698..0000000000 --- a/src/status/signals/types.nim +++ /dev/null @@ -1,75 +0,0 @@ -import json, chronicles, json_serialization, tables -import ../types -import ../chat/[chat, message] -import ../profile/[profile, devices] -import ../../eventemitter - -type Signal* = ref object of Args - signalType* {.serializedFieldName("type").}: SignalType - -type StatusGoError* = object - error*: string - -type NodeSignal* = ref object of Signal - event*: StatusGoError - -type WalletSignal* = ref object of Signal - content*: string - eventType*: string - blockNumber*: int - accounts*: seq[string] - # newTransactions*: ??? - erc20*: bool - -type EnvelopeSentSignal* = ref object of Signal - messageIds*: seq[string] - -type EnvelopeExpiredSignal* = ref object of Signal - messageIds*: seq[string] - -type MessageSignal* = ref object of Signal - messages*: seq[Message] - pinnedMessages*: seq[Message] - chats*: seq[Chat] - contacts*: seq[Profile] - installations*: seq[Installation] - emojiReactions*: seq[Reaction] - communities*: seq[Community] - membershipRequests*: seq[CommunityMembershipRequest] - activityCenterNotification*: seq[ActivityCenterNotification] - statusUpdates*: seq[StatusUpdate] - deletedMessages*: seq[RemovedMessage] - -type CommunitySignal* = ref object of Signal - community*: Community - -type MailserverRequestCompletedSignal* = ref object of Signal - requestID*: string - lastEnvelopeHash*: string - cursor*: string - errorMessage*: string - error*: bool - -type MailserverRequestExpiredSignal* = ref object of Signal - # TODO - -type Filter* = object - chatId*: string - symKeyId*: string - listen*: bool - filterId*: string - identity*: string - topic*: string - -type WhisperFilterSignal* = ref object of Signal - filters*: seq[Filter] - -type DiscoverySummarySignal* = ref object of Signal - enodes*: seq[string] - -type Stats* = object - uploadRate*: uint64 - downloadRate*: uint64 - -type StatsSignal* = ref object of Signal - stats*: Stats \ No newline at end of file diff --git a/src/status/status.nim b/src/status/status.nim index 063dccf304..cef1dd326d 100644 --- a/src/status/status.nim +++ b/src/status/status.nim @@ -1,14 +1,13 @@ import libstatus/accounts as libstatus_accounts import libstatus/core as libstatus_core import libstatus/settings as libstatus_settings -import types as libstatus_types import chat, accounts, wallet, wallet2, node, network, messages, contacts, profile, stickers, permissions, fleet, settings, mailservers, browser, tokens, provider import notifications/os_notifications import ../eventemitter -import ./tasks/task_runner_impl import bitops, stew/byteutils, chronicles +import ./types/[setting] -export chat, accounts, node, messages, contacts, profile, network, permissions, fleet, task_runner_impl, eventemitter +export chat, accounts, node, messages, contacts, profile, network, permissions, fleet, eventemitter type Status* = ref object events*: EventEmitter @@ -24,7 +23,6 @@ type Status* = ref object network*: NetworkModel stickers*: StickersModel permissions*: PermissionsModel - tasks*: TaskRunner settings*: SettingsModel mailservers*: MailserversModel browser*: BrowserModel @@ -34,14 +32,13 @@ type Status* = ref object proc newStatusInstance*(fleetConfig: string): Status = result = Status() - result.tasks = newTaskRunner() result.events = createEventEmitter() result.fleet = fleet.newFleetModel(fleetConfig) - result.chat = chat.newChatModel(result.events, result.tasks) + result.chat = chat.newChatModel(result.events) result.accounts = accounts.newAccountModel(result.events) result.wallet = wallet.newWalletModel(result.events) result.wallet.initEvents() - result.wallet2 = wallet2.newStatusWalletController(result.events, result.tasks) + result.wallet2 = wallet2.newStatusWalletController(result.events) result.node = node.newNodeModel() result.messages = messages.newMessagesModel(result.events) result.profile = profile.newProfileModel() @@ -57,7 +54,6 @@ proc newStatusInstance*(fleetConfig: string): Status = result.osnotifications = newOsNotifications(result.events) proc initNode*(self: Status) = - self.tasks.init() libstatus_accounts.initNode() proc startMessenger*(self: Status) = diff --git a/src/status/stickers.nim b/src/status/stickers.nim index 141fd7bf3c..fae2d6d2ab 100644 --- a/src/status/stickers.nim +++ b/src/status/stickers.nim @@ -5,9 +5,10 @@ import # project deps chronicles, web3/[ethtypes, conversions], stint import # local deps - types, libstatus/eth/contracts as status_contracts, + libstatus/eth/contracts as status_contracts, libstatus/stickers as status_stickers, transactions, libstatus/wallet, ../eventemitter +import ./types/[sticker, transaction, rpc_response] from utils as libstatus_utils import eth2Wei, gwei2Wei, toUInt64, parseAddress diff --git a/src/status/tasks/task_runner_impl.nim b/src/status/tasks/task_runner_impl.nim deleted file mode 100644 index 395da24f52..0000000000 --- a/src/status/tasks/task_runner_impl.nim +++ /dev/null @@ -1,28 +0,0 @@ -import # vendor libs - chronicles, task_runner - -import # status-desktop libs - ./marathon, ./threadpool - -export marathon, task_runner, threadpool - -logScope: - topics = "task-runner" - -type - TaskRunner* = ref object - threadpool*: ThreadPool - marathon*: Marathon - -proc newTaskRunner*(): TaskRunner = - new(result) - result.threadpool = newThreadPool() - result.marathon = newMarathon() - -proc init*(self: TaskRunner) = - self.threadpool.init() - self.marathon.init() - -proc teardown*(self: TaskRunner) = - self.threadpool.teardown() - self.marathon.teardown() diff --git a/src/status/tokens.nim b/src/status/tokens.nim index 062aa6dc87..21c5fb1188 100644 --- a/src/status/tokens.nim +++ b/src/status/tokens.nim @@ -1,15 +1,6 @@ -import json, json_serialization - -import - sugar, sequtils, strutils, atomics - import libstatus/tokens as status_tokens import libstatus/eth/contracts import ../eventemitter -import signals/types - -#TODO: temporary? -import types as LibStatusTypes type TokensModel* = ref object diff --git a/src/status/transactions.nim b/src/status/transactions.nim index dda00095d2..c41801009e 100644 --- a/src/status/transactions.nim +++ b/src/status/transactions.nim @@ -4,8 +4,6 @@ import import stint, web3/ethtypes -import - types from utils as status_utils import toUInt64, gwei2Wei, parseAddress proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", data = ""): EthSend = diff --git a/src/status/types.nim b/src/status/types.nim deleted file mode 100644 index 252b2d928e..0000000000 --- a/src/status/types.nim +++ /dev/null @@ -1,285 +0,0 @@ -{.used.} - -import json, options, typetraits, tables, sequtils, strutils -import web3/ethtypes, json_serialization, stint -import libstatus/accounts/constants -import ../eventemitter - -type SignalType* {.pure.} = enum - Message = "messages.new" - Wallet = "wallet" - NodeReady = "node.ready" - NodeCrashed = "node.crashed" - NodeStarted = "node.started" - NodeStopped = "node.stopped" - NodeLogin = "node.login" - EnvelopeSent = "envelope.sent" - EnvelopeExpired = "envelope.expired" - MailserverRequestCompleted = "mailserver.request.completed" - MailserverRequestExpired = "mailserver.request.expired" - DiscoveryStarted = "discovery.started" - DiscoveryStopped = "discovery.stopped" - DiscoverySummary = "discovery.summary" - SubscriptionsData = "subscriptions.data" - SubscriptionsError = "subscriptions.error" - WhisperFilterAdded = "whisper.filter.added" - CommunityFound = "community.found" - Stats = "stats" - Unknown - -proc event*(self:SignalType):string = - result = "signal:" & $self - -type GasPricePrediction* = object - safeLow*: float - standard*: float - fast*: float - fastest*: float - currentBaseFee*: float - recommendedBaseFee*: float - -type DerivedAccount* = object - publicKey*: string - address*: string - derivationPath*: string - -type MultiAccounts* = object - whisper* {.serializedFieldName(PATH_WHISPER).}: DerivedAccount - walletRoot* {.serializedFieldName(PATH_WALLET_ROOT).}: DerivedAccount - defaultWallet* {.serializedFieldName(PATH_DEFAULT_WALLET).}: DerivedAccount - eip1581* {.serializedFieldName(PATH_EIP_1581).}: DerivedAccount - -type - IdentityImage* = ref object - thumbnail*: string - large*: string - -type - Account* = ref object of RootObj - name*: string - keyUid* {.serializedFieldName("key-uid").}: string - identityImage*: IdentityImage - identicon*: string - -type - NodeAccount* = ref object of Account - timestamp*: int - keycardPairing* {.serializedFieldName("keycard-pairing").}: string - -type - GeneratedAccount* = ref object - publicKey*: string - address*: string - id*: string - mnemonic*: string - derived*: MultiAccounts - # FIXME: should inherit from Account but multiAccountGenerateAndDeriveAddresses - # response has a camel-cased properties like "publicKey" and "keyUid", so the - # serializedFieldName pragma would need to be different - name*: string - keyUid*: string - identicon*: string - identityImage*: IdentityImage - -type RpcError* = ref object - code*: int - message*: string - -type - RpcResponse* = ref object - jsonrpc*: string - result*: string - id*: int - error*: RpcError - # TODO: replace all RpcResponse and RpcResponseTyped occurances with a generic - # form of RpcReponse. IOW, rename RpceResponseTyped*[T] to RpcResponse*[T] and - # remove RpcResponse. - RpcResponseTyped*[T] = object - jsonrpc*: string - result*: T - id*: int - error*: RpcError - -proc toAccount*(account: GeneratedAccount): Account = - result = Account(name: account.name, identityImage: account.identityImage, identicon: account.identicon, keyUid: account.keyUid) - -proc toAccount*(account: NodeAccount): Account = - result = Account(name: account.name, identityImage: account.identityImage, identicon: account.identicon, keyUid: account.keyUid) - -type AccountArgs* = ref object of Args - account*: Account - -type - StatusGoException* = object of CatchableError - -type - Transaction* = ref object - id*: string - typeValue*: string - address*: string - blockNumber*: string - blockHash*: string - contract*: string - timestamp*: string - gasPrice*: string - gasLimit*: string - gasUsed*: string - nonce*: string - txStatus*: string - value*: string - fromAddress*: string - to*: string - -proc cmpTransactions*(x, y: Transaction): int = - # Sort proc to compare transactions from a single account. - # Compares first by block number, then by nonce - result = cmp(x.blockNumber.parseHexInt, y.blockNumber.parseHexInt) - if result == 0: - result = cmp(x.nonce, y.nonce) - -type - RpcException* = object of CatchableError - -type Sticker* = object - hash*: string - packId*: int - -type StickerPack* = object - author*: string - id*: int - name*: string - price*: Stuint[256] - preview*: string - stickers*: seq[Sticker] - thumbnail*: string - -proc `%`*(stuint256: Stuint[256]): JsonNode = - newJString($stuint256) - -proc readValue*(reader: var JsonReader, value: var Stuint[256]) - {.raises: [IOError, SerializationError, Defect].} = - try: - let strVal = reader.readValue(string) - value = strVal.parse(Stuint[256]) - except: - try: - let intVal = reader.readValue(int) - value = intVal.stuint(256) - except: - raise newException(SerializationError, "Expected string or int representation of Stuint[256]") - -type - Network* {.pure.} = enum - Mainnet = "mainnet_rpc", - Testnet = "testnet_rpc", - Rinkeby = "rinkeby_rpc", - Goerli = "goerli_rpc", - XDai = "xdai_rpc", - Poa = "poa_rpc", - Other = "other" - - Setting* {.pure.} = enum - Appearance = "appearance", - Bookmarks = "bookmarks", - Currency = "currency" - EtherscanLink = "etherscan-link" - InstallationId = "installation-id" - MessagesFromContactsOnly = "messages-from-contacts-only" - Mnemonic = "mnemonic" - Networks_Networks = "networks/networks" - Networks_CurrentNetwork = "networks/current-network" - NodeConfig = "node-config" - PublicKey = "public-key" - DappsAddress = "dapps-address" - Stickers_PacksInstalled = "stickers/packs-installed" - Stickers_Recent = "stickers/recent-stickers" - Gifs_Recent = "gifs/recent-gifs" - Gifs_Favorite = "gifs/favorite-gifs" - WalletRootAddress = "wallet-root-address" - LatestDerivedPath = "latest-derived-path" - PreferredUsername = "preferred-name" - Usernames = "usernames" - SigningPhrase = "signing-phrase" - Fleet = "fleet" - VisibleTokens = "wallet/visible-tokens" - PinnedMailservers = "pinned-mailservers" - WakuBloomFilterMode = "waku-bloom-filter-mode" - SendUserStatus = "send-status-updates?" - CurrentUserStatus = "current-user-status" - - - UpstreamConfig* = ref object - enabled* {.serializedFieldName("Enabled").}: bool - url* {.serializedFieldName("URL").}: string - - NodeConfig* = ref object - networkId* {.serializedFieldName("NetworkId").}: int - dataDir* {.serializedFieldName("DataDir").}: string - upstreamConfig* {.serializedFieldName("UpstreamConfig").}: UpstreamConfig - - NetworkDetails* = ref object - id*: string - name*: string - etherscanLink* {.serializedFieldName("etherscan-link").}: string - config*: NodeConfig - -type PendingTransactionType* {.pure.} = enum - RegisterENS = "RegisterENS", - SetPubKey = "SetPubKey", - ReleaseENS = "ReleaseENS", - BuyStickerPack = "BuyStickerPack" - WalletTransfer = "WalletTransfer" - -type Bookmark* = ref object - name*: string - url*: string - imageUrl*: string - -type - Fleet* {.pure.} = enum - Prod = "eth.prod", - Staging = "eth.staging", - Test = "eth.test", - WakuV2Prod = "wakuv2.prod" - WakuV2Test = "wakuv2.test" - - FleetNodes* {.pure.} = enum - Bootnodes = "boot", - Mailservers = "mail", - Rendezvous = "rendezvous", - Whisper = "whisper", - Waku = "waku" - - FleetMeta* = object - hostname*: string - timestamp*: uint64 - - FleetConfig* = object - fleet*: Table[string, Table[string, Table[string, string]]] - meta*: FleetMeta - - -proc toFleetConfig*(jsonString: string): FleetConfig = - let fleetJson = jsonString.parseJSON - result.meta.hostname = fleetJson["meta"]["hostname"].getStr - result.meta.timestamp = fleetJson["meta"]["timestamp"].getBiggestInt.uint64 - result.fleet = initTable[string, Table[string, Table[string, string]]]() - - for fleet in fleetJson["fleets"].keys(): - result.fleet[fleet] = initTable[string, Table[string, string]]() - for nodes in fleetJson["fleets"][fleet].keys(): - result.fleet[fleet][nodes] = initTable[string, string]() - for server in fleetJson["fleets"][fleet][nodes].keys(): - result.fleet[fleet][nodes][server] = fleetJson["fleets"][fleet][nodes][server].getStr - - -proc getNodes*(self: FleetConfig, fleet: Fleet, nodeType: FleetNodes = FleetNodes.Bootnodes): seq[string] = - if not self.fleet[$fleet].hasKey($nodeType): return - result = toSeq(self.fleet[$fleet][$nodeType].values) - -proc getMailservers*(self: FleetConfig, fleet: Fleet): Table[string, string] = - if not self.fleet[$fleet].hasKey($FleetNodes.Mailservers): - result = initTable[string,string]() - return - result = self.fleet[$fleet][$FleetNodes.Mailservers] - diff --git a/src/status/types/account.nim b/src/status/types/account.nim new file mode 100644 index 0000000000..de1eaac65a --- /dev/null +++ b/src/status/types/account.nim @@ -0,0 +1,46 @@ +{.used.} + +import json_serialization + +import ../../eventemitter +import identity_image + +include multi_accounts + +export identity_image + +type + Account* = ref object of RootObj + name*: string + keyUid* {.serializedFieldName("key-uid").}: string + identityImage*: IdentityImage + identicon*: string + +type + NodeAccount* = ref object of Account + timestamp*: int + keycardPairing* {.serializedFieldName("keycard-pairing").}: string + +type + GeneratedAccount* = ref object + publicKey*: string + address*: string + id*: string + mnemonic*: string + derived*: MultiAccounts + # FIXME: should inherit from Account but multiAccountGenerateAndDeriveAddresses + # response has a camel-cased properties like "publicKey" and "keyUid", so the + # serializedFieldName pragma would need to be different + name*: string + keyUid*: string + identicon*: string + identityImage*: IdentityImage + +proc toAccount*(account: GeneratedAccount): Account = + result = Account(name: account.name, identityImage: account.identityImage, identicon: account.identicon, keyUid: account.keyUid) + +proc toAccount*(account: NodeAccount): Account = + result = Account(name: account.name, identityImage: account.identityImage, identicon: account.identicon, keyUid: account.keyUid) + +type AccountArgs* = ref object of Args + account*: Account \ No newline at end of file diff --git a/src/status/types/activity_center_notification.nim b/src/status/types/activity_center_notification.nim new file mode 100644 index 0000000000..5a988edbda --- /dev/null +++ b/src/status/types/activity_center_notification.nim @@ -0,0 +1,49 @@ +{.used.} + +import json, chronicles +import message + +export message + +type ActivityCenterNotificationType* {.pure.}= enum + Unknown = 0, + NewOneToOne = 1, + NewPrivateGroupChat = 2, + Mention = 3 + Reply = 4 + +type ActivityCenterNotification* = ref object of RootObj + id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat + chatId*: string + name*: string + author*: string + notificationType*: ActivityCenterNotificationType + message*: Message + timestamp*: int64 + read*: bool + dismissed*: bool + accepted*: bool + +proc toActivityCenterNotification*(jsonNotification: JsonNode): ActivityCenterNotification = + var activityCenterNotificationType: ActivityCenterNotificationType + try: + activityCenterNotificationType = ActivityCenterNotificationType(jsonNotification{"type"}.getInt) + except: + warn "Unknown notification type received", type = jsonNotification{"type"}.getInt + activityCenterNotificationType = ActivityCenterNotificationType.Unknown + result = ActivityCenterNotification( + id: jsonNotification{"id"}.getStr, + chatId: jsonNotification{"chatId"}.getStr, + name: jsonNotification{"name"}.getStr, + author: jsonNotification{"author"}.getStr, + notificationType: activityCenterNotificationType, + timestamp: jsonNotification{"timestamp"}.getInt, + read: jsonNotification{"read"}.getBool, + dismissed: jsonNotification{"dismissed"}.getBool, + accepted: jsonNotification{"accepted"}.getBool + ) + + if jsonNotification.contains("message") and jsonNotification{"message"}.kind != JNull: + result.message = jsonNotification{"message"}.toMessage() + elif activityCenterNotificationType == ActivityCenterNotificationType.NewOneToOne and jsonNotification.contains("lastMessage") and jsonNotification{"lastMessage"}.kind != JNull: + result.message = jsonNotification{"lastMessage"}.toMessage() \ No newline at end of file diff --git a/src/status/types/bookmark.nim b/src/status/types/bookmark.nim new file mode 100644 index 0000000000..4da7bf9f93 --- /dev/null +++ b/src/status/types/bookmark.nim @@ -0,0 +1,7 @@ +{.used.} + +type Bookmark* = ref object + name*: string + url*: string + imageUrl*: string + diff --git a/src/status/types/chat.nim b/src/status/types/chat.nim new file mode 100644 index 0000000000..b310654934 --- /dev/null +++ b/src/status/types/chat.nim @@ -0,0 +1,170 @@ +{.used.} + +import strutils, random, strformat, json + +import ../libstatus/accounts as status_accounts +import message + +include chat_member +include chat_membership_event + +type ChatType* {.pure.}= enum + Unknown = 0, + OneToOne = 1, + Public = 2, + PrivateGroupChat = 3, + Profile = 4, + Timeline = 5 + CommunityChat = 6 + +proc isOneToOne*(self: ChatType): bool = self == ChatType.OneToOne +proc isTimeline*(self: ChatType): bool = self == ChatType.Timeline + +type Chat* = ref object + id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat + communityId*: string + private*: bool + categoryId*: string + name*: string + description*: string + color*: string + identicon*: string + isActive*: bool # indicates whether the chat has been soft deleted + chatType*: ChatType + timestamp*: int64 # indicates the last time this chat has received/sent a message + joined*: int64 # indicates when the user joined the chat last time + lastClockValue*: int64 # indicates the last clock value to be used when sending messages + deletedAtClockValue*: int64 # indicates the clock value at time of deletion, messages with lower clock value of this should be discarded + unviewedMessagesCount*: int + unviewedMentionsCount*: int + lastMessage*: Message + members*: seq[ChatMember] + membershipUpdateEvents*: seq[ChatMembershipEvent] + mentionsCount*: int # Using this is not a good approach, we should instead use unviewedMentionsCount and refer to it always. + muted*: bool + canPost*: bool + ensName*: string + position*: int + +proc `$`*(self: Chat): string = + result = fmt"Chat(id:{self.id}, name:{self.name}, active:{self.isActive}, type:{self.chatType})" + +proc toJsonNode*(self: Chat): JsonNode = + result = %* { + "active": self.isActive, + "chatType": self.chatType.int, + "color": self.color, + "deletedAtClockValue": self.deletedAtClockValue, + "id": self.id, + "lastClockValue": self.lastClockValue, + "lastMessage": nil, + "members": self.members.toJsonNode, + "membershipUpdateEvents": self.membershipUpdateEvents.toJsonNode, + "name": (if self.ensName != "": self.ensName else: self.name), + "timestamp": self.timestamp, + "unviewedMessagesCount": self.unviewedMessagesCount, + "joined": self.joined, + "position": self.position + } + +proc toChatMember*(jsonMember: JsonNode): ChatMember = + let pubkey = jsonMember["id"].getStr + + result = ChatMember( + admin: jsonMember["admin"].getBool, + id: pubkey, + joined: jsonMember["joined"].getBool, + identicon: generateIdenticon(pubkey), + userName: generateAlias(pubkey) + ) + +proc toChatMembershipEvent*(jsonMembership: JsonNode): ChatMembershipEvent = + result = ChatMembershipEvent( + chatId: jsonMembership["chatId"].getStr, + clockValue: jsonMembership["clockValue"].getBiggestInt, + fromKey: jsonMembership["from"].getStr, + rawPayload: jsonMembership["rawPayload"].getStr, + signature: jsonMembership["signature"].getStr, + eventType: jsonMembership["type"].getInt, + name: jsonMembership{"name"}.getStr, + members: @[] + ) + if jsonMembership{"members"} != nil: + for member in jsonMembership["members"]: + result.members.add(member.getStr) + +proc toChat*(jsonChat: JsonNode): Chat = + + let chatTypeInt = jsonChat{"chatType"}.getInt + let chatType: ChatType = if chatTypeInt >= ord(low(ChatType)) or chatTypeInt <= ord(high(ChatType)): ChatType(chatTypeInt) else: ChatType.Unknown + + result = Chat( + id: jsonChat{"id"}.getStr, + communityId: jsonChat{"communityId"}.getStr, + name: jsonChat{"name"}.getStr, + description: jsonChat{"description"}.getStr, + identicon: "", + color: jsonChat{"color"}.getStr, + isActive: jsonChat{"active"}.getBool, + chatType: chatType, + timestamp: jsonChat{"timestamp"}.getBiggestInt, + lastClockValue: jsonChat{"lastClockValue"}.getBiggestInt, + deletedAtClockValue: jsonChat{"deletedAtClockValue"}.getBiggestInt, + unviewedMessagesCount: jsonChat{"unviewedMessagesCount"}.getInt, + unviewedMentionsCount: jsonChat{"unviewedMentionsCount"}.getInt, + mentionsCount: 0, + muted: false, + ensName: "", + joined: 0, + private: jsonChat{"private"}.getBool + ) + + if jsonChat.hasKey("muted") and jsonChat["muted"].kind != JNull: + result.muted = jsonChat["muted"].getBool + + if jsonChat["lastMessage"].kind != JNull: + result.lastMessage = jsonChat{"lastMessage"}.toMessage() + + if jsonChat.hasKey("joined") and jsonChat["joined"].kind != JNull: + result.joined = jsonChat{"joined"}.getInt + + if result.chatType == ChatType.OneToOne: + result.identicon = generateIdenticon(result.id) + if result.name.endsWith(".eth"): + result.ensName = result.name + if result.name == "": + result.name = generateAlias(result.id) + + if jsonChat["members"].kind != JNull: + result.members = @[] + for jsonMember in jsonChat["members"]: + result.members.add(jsonMember.toChatMember) + + if jsonChat["membershipUpdateEvents"].kind != JNull: + result.membershipUpdateEvents = @[] + for jsonMember in jsonChat["membershipUpdateEvents"]: + result.membershipUpdateEvents.add(jsonMember.toChatMembershipEvent) + +const channelColors* = ["#fa6565", "#7cda00", "#887af9", "#51d0f0", "#FE8F59", "#d37ef4"] + +proc newChat*(id: string, chatType: ChatType): Chat = + randomize() + + result = Chat( + id: id, + color: channelColors[rand(channelColors.len - 1)], + isActive: true, + chatType: chatType, + timestamp: 0, + lastClockValue: 0, + deletedAtClockValue: 0, + unviewedMessagesCount: 0, + mentionsCount: 0, + members: @[] + ) + + if chatType == ChatType.OneToOne: + result.identicon = generateIdenticon(id) + result.name = generateAlias(id) + else: + result.name = id \ No newline at end of file diff --git a/src/status/types/chat_member.nim b/src/status/types/chat_member.nim new file mode 100644 index 0000000000..718cb659ce --- /dev/null +++ b/src/status/types/chat_member.nim @@ -0,0 +1,21 @@ +{.used.} + +import strformat, json, sequtils + +type ChatMember* = object + admin*: bool + id*: string + joined*: bool + identicon*: string + userName*: string + localNickname*: string + +proc toJsonNode*(self: ChatMember): JsonNode = + result = %* { + "id": self.id, + "admin": self.admin, + "joined": self.joined + } + +proc toJsonNode*(self: seq[ChatMember]): seq[JsonNode] = + result = map(self, proc(x: ChatMember): JsonNode = x.toJsonNode) \ No newline at end of file diff --git a/src/status/types/chat_membership_event.nim b/src/status/types/chat_membership_event.nim new file mode 100644 index 0000000000..d0b048c069 --- /dev/null +++ b/src/status/types/chat_membership_event.nim @@ -0,0 +1,28 @@ +{.used.} + +import strformat, json, sequtils + +type ChatMembershipEvent* = object + chatId*: string + clockValue*: int64 + fromKey*: string + name*: string + members*: seq[string] + rawPayload*: string + signature*: string + eventType*: int + +proc toJsonNode*(self: ChatMembershipEvent): JsonNode = + result = %* { + "chatId": self.chatId, + "name": self.name, + "clockValue": self.clockValue, + "from": self.fromKey, + "members": self.members, + "rawPayload": self.rawPayload, + "signature": self.signature, + "type": self.eventType + } + +proc toJsonNode*(self: seq[ChatMembershipEvent]): seq[JsonNode] = + result = map(self, proc(x: ChatMembershipEvent): JsonNode = x.toJsonNode) \ No newline at end of file diff --git a/src/status/types/community.nim b/src/status/types/community.nim new file mode 100644 index 0000000000..dbec307493 --- /dev/null +++ b/src/status/types/community.nim @@ -0,0 +1,91 @@ +{.used.} + +import json, strformat, tables +import chat, status_update, identity_image + +include community_category +include community_membership_request + +type Community* = object + id*: string + name*: string + lastChannelSeen*: string + description*: string + chats*: seq[Chat] + categories*: seq[CommunityCategory] + members*: seq[string] + access*: int + unviewedMessagesCount*: int + unviewedMentionsCount*: int + admin*: bool + joined*: bool + verified*: bool + ensOnly*: bool + canRequestAccess*: bool + canManageUsers*: bool + canJoin*: bool + isMember*: bool + muted*: bool + communityImage*: IdentityImage + membershipRequests*: seq[CommunityMembershipRequest] + communityColor*: string + memberStatus*: OrderedTable[string, StatusUpdate] + +proc `$`*(self: Community): string = + result = fmt"Community(id:{self.id}, name:{self.name}, description:{self.description}" + +proc toCommunity*(jsonCommunity: JsonNode): Community = + result = Community( + id: jsonCommunity{"id"}.getStr, + name: jsonCommunity{"name"}.getStr, + description: jsonCommunity{"description"}.getStr, + access: jsonCommunity{"permissions"}{"access"}.getInt, + admin: jsonCommunity{"admin"}.getBool, + joined: jsonCommunity{"joined"}.getBool, + verified: jsonCommunity{"verified"}.getBool, + ensOnly: jsonCommunity{"permissions"}{"ens_only"}.getBool, + canRequestAccess: jsonCommunity{"canRequestAccess"}.getBool, + canManageUsers: jsonCommunity{"canManageUsers"}.getBool, + canJoin: jsonCommunity{"canJoin"}.getBool, + isMember: jsonCommunity{"isMember"}.getBool, + muted: jsonCommunity{"muted"}.getBool, + chats: newSeq[Chat](), + members: newSeq[string](), + communityColor: jsonCommunity{"color"}.getStr, + communityImage: IdentityImage() + ) + + result.memberStatus = initOrderedTable[string, StatusUpdate]() + + if jsonCommunity.hasKey("images") and jsonCommunity["images"].kind != JNull: + if jsonCommunity["images"].hasKey("thumbnail"): + result.communityImage.thumbnail = jsonCommunity["images"]["thumbnail"]["uri"].str + if jsonCommunity["images"].hasKey("large"): + result.communityImage.large = jsonCommunity["images"]["large"]["uri"].str + + if jsonCommunity.hasKey("chats") and jsonCommunity["chats"].kind != JNull: + for chatId, chat in jsonCommunity{"chats"}: + result.chats.add(Chat( + id: result.id & chatId, + categoryId: chat{"categoryID"}.getStr(), + communityId: result.id, + name: chat{"name"}.getStr, + description: chat{"description"}.getStr, + canPost: chat{"canPost"}.getBool, + chatType: ChatType.CommunityChat, + private: chat{"permissions"}{"private"}.getBool, + position: chat{"position"}.getInt + )) + + if jsonCommunity.hasKey("categories") and jsonCommunity["categories"].kind != JNull: + for catId, cat in jsonCommunity{"categories"}: + result.categories.add(CommunityCategory( + id: catId, + name: cat{"name"}.getStr, + position: cat{"position"}.getInt + )) + + if jsonCommunity.hasKey("members") and jsonCommunity["members"].kind != JNull: + # memberInfo is empty for now + for memberPubKey, memeberInfo in jsonCommunity{"members"}: + result.members.add(memberPubKey) \ No newline at end of file diff --git a/src/status/types/community_category.nim b/src/status/types/community_category.nim new file mode 100644 index 0000000000..49cab308cb --- /dev/null +++ b/src/status/types/community_category.nim @@ -0,0 +1,6 @@ +{.used.} + +type CommunityCategory* = object + id*: string + name*: string + position*: int \ No newline at end of file diff --git a/src/status/types/community_membership_request.nim b/src/status/types/community_membership_request.nim new file mode 100644 index 0000000000..7add50c156 --- /dev/null +++ b/src/status/types/community_membership_request.nim @@ -0,0 +1,21 @@ +{.used.} + +import json + +type CommunityMembershipRequest* = object + id*: string + publicKey*: string + chatId*: string + communityId*: string + state*: int + our*: string + +proc toCommunityMembershipRequest*(jsonCommunityMembershipRequest: JsonNode): CommunityMembershipRequest = + result = CommunityMembershipRequest( + id: jsonCommunityMembershipRequest{"id"}.getStr, + publicKey: jsonCommunityMembershipRequest{"publicKey"}.getStr, + chatId: jsonCommunityMembershipRequest{"chatId"}.getStr, + state: jsonCommunityMembershipRequest{"state"}.getInt, + communityId: jsonCommunityMembershipRequest{"communityId"}.getStr, + our: jsonCommunityMembershipRequest{"our"}.getStr, + ) \ No newline at end of file diff --git a/src/status/types/derived_account.nim b/src/status/types/derived_account.nim new file mode 100644 index 0000000000..fd47d198e4 --- /dev/null +++ b/src/status/types/derived_account.nim @@ -0,0 +1,6 @@ +{.used.} + +type DerivedAccount* = object + publicKey*: string + address*: string + derivationPath*: string \ No newline at end of file diff --git a/src/status/types/fleet.nim b/src/status/types/fleet.nim new file mode 100644 index 0000000000..bd5374b67a --- /dev/null +++ b/src/status/types/fleet.nim @@ -0,0 +1,52 @@ +{.used.} + +import json, typetraits, tables, sequtils + +type + Fleet* {.pure.} = enum + Prod = "eth.prod", + Staging = "eth.staging", + Test = "eth.test", + WakuV2Prod = "wakuv2.prod" + WakuV2Test = "wakuv2.test" + + FleetNodes* {.pure.} = enum + Bootnodes = "boot", + Mailservers = "mail", + Rendezvous = "rendezvous", + Whisper = "whisper", + Waku = "waku" + + FleetMeta* = object + hostname*: string + timestamp*: uint64 + + FleetConfig* = object + fleet*: Table[string, Table[string, Table[string, string]]] + meta*: FleetMeta + + +proc toFleetConfig*(jsonString: string): FleetConfig = + let fleetJson = jsonString.parseJSON + result.meta.hostname = fleetJson["meta"]["hostname"].getStr + result.meta.timestamp = fleetJson["meta"]["timestamp"].getBiggestInt.uint64 + result.fleet = initTable[string, Table[string, Table[string, string]]]() + + for fleet in fleetJson["fleets"].keys(): + result.fleet[fleet] = initTable[string, Table[string, string]]() + for nodes in fleetJson["fleets"][fleet].keys(): + result.fleet[fleet][nodes] = initTable[string, string]() + for server in fleetJson["fleets"][fleet][nodes].keys(): + result.fleet[fleet][nodes][server] = fleetJson["fleets"][fleet][nodes][server].getStr + + +proc getNodes*(self: FleetConfig, fleet: Fleet, nodeType: FleetNodes = FleetNodes.Bootnodes): seq[string] = + if not self.fleet[$fleet].hasKey($nodeType): return + result = toSeq(self.fleet[$fleet][$nodeType].values) + +proc getMailservers*(self: FleetConfig, fleet: Fleet): Table[string, string] = + if not self.fleet[$fleet].hasKey($FleetNodes.Mailservers): + result = initTable[string,string]() + return + result = self.fleet[$fleet][$FleetNodes.Mailservers] + diff --git a/src/status/types/gas_prediction.nim b/src/status/types/gas_prediction.nim new file mode 100644 index 0000000000..9ce3f16589 --- /dev/null +++ b/src/status/types/gas_prediction.nim @@ -0,0 +1,9 @@ +{.used.} + +type GasPricePrediction* = object + safeLow*: float + standard*: float + fast*: float + fastest*: float + currentBaseFee*: float + recommendedBaseFee*: float \ No newline at end of file diff --git a/src/status/types/identity_image.nim b/src/status/types/identity_image.nim new file mode 100644 index 0000000000..a77913dcf2 --- /dev/null +++ b/src/status/types/identity_image.nim @@ -0,0 +1,6 @@ +{.used.} + +type + IdentityImage* = ref object + thumbnail*: string + large*: string \ No newline at end of file diff --git a/src/status/profile/devices.nim b/src/status/types/installation.nim similarity index 100% rename from src/status/profile/devices.nim rename to src/status/types/installation.nim diff --git a/src/status/types/message.nim b/src/status/types/message.nim new file mode 100644 index 0000000000..97ed3981ed --- /dev/null +++ b/src/status/types/message.nim @@ -0,0 +1,192 @@ +{.used.} + +import json, strutils, sequtils, sugar, chronicles +import json_serialization +import ../utils +import ../wallet/account +import ../libstatus/accounts/constants as constants +import ../libstatus/wallet as status_wallet +import ../libstatus/settings as status_settings +import ../libstatus/tokens as status_tokens +import ../libstatus/eth/contracts as status_contracts +import web3/conversions +from ../utils import parseAddress, wei2Eth +import setting, network + +include message_command_parameters +include message_reaction +include message_text_item + +type ContentType* {.pure.} = enum + FetchMoreMessagesButton = -2 + ChatIdentifier = -1, + Unknown = 0, + Message = 1, + Sticker = 2, + Status = 3, + Emoji = 4, + Transaction = 5, + Group = 6, + Image = 7, + Audio = 8 + Community = 9 + Gap = 10 + Edit = 11 + +type Message* = object + alias*: string + userName*: string + localName*: string + chatId*: string + clock*: int + gapFrom*: int + gapTo*: int + commandParameters*: CommandParameters + contentType*: ContentType + ensName*: string + fromAuthor*: string + id*: string + identicon*: string + lineCount*: int + localChatId*: string + messageType*: string # ??? + parsedText*: seq[TextItem] + # quotedMessage: # ??? + replace*: string + responseTo*: string + rtl*: bool # ??? + seen*: bool # ??? + sticker*: string + stickerPackId*: int + text*: string + timestamp*: string + editedAt*: string + whisperTimestamp*: string + isCurrentUser*: bool + stickerHash*: string + outgoingStatus*: string + linkUrls*: string + image*: string + audio*: string + communityId*: string + audioDurationMs*: int + hasMention*: bool + isPinned*: bool + pinnedBy*: string + deleted*: bool + +proc `$`*(self: Message): string = + result = fmt"Message(id:{self.id}, chatId:{self.chatId}, clock:{self.clock}, from:{self.fromAuthor}, contentType:{self.contentType})" + +proc currentUserWalletContainsAddress(address: string): bool = + if (address.len == 0): + return false + + let accounts = status_wallet.getWalletAccounts() + for acc in accounts: + if (acc.address == address): + return true + + return false + +proc toMessage*(jsonMsg: JsonNode): Message = + let publicChatKey = status_settings.getSetting[string](Setting.PublicKey, "0x0") + + var contentType: ContentType + try: + contentType = ContentType(jsonMsg{"contentType"}.getInt) + except: + warn "Unknown content type received", type = jsonMsg{"contentType"}.getInt + contentType = ContentType.Message + + var message = Message( + alias: jsonMsg{"alias"}.getStr, + userName: "", + localName: "", + chatId: jsonMsg{"localChatId"}.getStr, + clock: jsonMsg{"clock"}.getInt, + contentType: contentType, + ensName: jsonMsg{"ensName"}.getStr, + fromAuthor: jsonMsg{"from"}.getStr, + id: jsonMsg{"id"}.getStr, + identicon: jsonMsg{"identicon"}.getStr, + lineCount: jsonMsg{"lineCount"}.getInt, + localChatId: jsonMsg{"localChatId"}.getStr, + messageType: jsonMsg{"messageType"}.getStr, + replace: jsonMsg{"replace"}.getStr, + editedAt: $jsonMsg{"editedAt"}.getInt, + responseTo: jsonMsg{"responseTo"}.getStr, + rtl: jsonMsg{"rtl"}.getBool, + seen: jsonMsg{"seen"}.getBool, + text: jsonMsg{"text"}.getStr, + timestamp: $jsonMsg{"timestamp"}.getInt, + whisperTimestamp: $jsonMsg{"whisperTimestamp"}.getInt, + outgoingStatus: $jsonMsg{"outgoingStatus"}.getStr, + isCurrentUser: publicChatKey == jsonMsg{"from"}.getStr, + stickerHash: "", + stickerPackId: -1, + parsedText: @[], + linkUrls: "", + image: $jsonMsg{"image"}.getStr, + audio: $jsonMsg{"audio"}.getStr, + communityId: $jsonMsg{"communityId"}.getStr, + audioDurationMs: jsonMsg{"audioDurationMs"}.getInt, + deleted: jsonMsg{"deleted"}.getBool, + hasMention: false + ) + + if contentType == ContentType.Gap: + message.gapFrom = jsonMsg["gapParameters"]["from"].getInt + message.gapTo = jsonMsg["gapParameters"]["to"].getInt + + if jsonMsg.contains("parsedText") and jsonMsg{"parsedText"}.kind != JNull: + for text in jsonMsg{"parsedText"}: + message.parsedText.add(text.toTextItem) + + message.linkUrls = concat(message.parsedText.map(t => t.children.filter(c => c.textType == "link"))) + .filter(t => t.destination.startsWith("http") or t.destination.startsWith("statusim://")) + .map(t => t.destination) + .join(" ") + + if message.contentType == ContentType.Sticker: + message.stickerHash = jsonMsg["sticker"]["hash"].getStr + message.stickerPackId = jsonMsg["sticker"]["pack"].getInt + + if message.contentType == ContentType.Transaction: + let + allContracts = getErc20Contracts().concat(getCustomTokens()) + ethereum = newErc20Contract("Ethereum", Network.Mainnet, parseAddress(constants.ZERO_ADDRESS), "ETH", 18, true) + tokenAddress = jsonMsg["commandParameters"]["contract"].getStr + tokenContract = if tokenAddress == "": ethereum else: allContracts.getErc20ContractByAddress(parseAddress(tokenAddress)) + tokenContractStr = if tokenContract == nil: "{}" else: $(Json.encode(tokenContract)) + var weiStr = if tokenContract == nil: "0" else: wei2Eth(jsonMsg["commandParameters"]["value"].getStr, tokenContract.decimals) + weiStr.trimZeros() + + # TODO find a way to use json_seralization for this. When I try, I get an error + message.commandParameters = CommandParameters( + id: jsonMsg["commandParameters"]["id"].getStr, + fromAddress: jsonMsg["commandParameters"]["from"].getStr, + address: jsonMsg["commandParameters"]["address"].getStr, + contract: tokenContractStr, + value: weiStr, + transactionHash: jsonMsg["commandParameters"]["transactionHash"].getStr, + commandState: jsonMsg["commandParameters"]["commandState"].getInt, + signature: jsonMsg["commandParameters"]["signature"].getStr + ) + + # This is kind of a workaround in case we're processing a transaction message. The reason for + # that is a message where a recipient accepted to share his address with sender. In that message + # a recipient's public key is set as a "from" property of a "Message" object and we cannot + # determine which of two users has initiated transaction actually. + # + # To overcome this we're checking if the "from" address from the "commandParameters" object of + # the "Message" is contained as an address in the wallet of logged in user. If yes, means that + # currently logged in user has initiated a transaction (he is a sender), otherwise currently + # logged in user is a recipient. + message.isCurrentUser = currentUserWalletContainsAddress(message.commandParameters.fromAddress) + + message.hasMention = concat(message.parsedText.map( + t => t.children.filter( + c => c.textType == "mention" and c.literal == publicChatKey))).len > 0 + + result = message \ No newline at end of file diff --git a/src/status/types/message_command_parameters.nim b/src/status/types/message_command_parameters.nim new file mode 100644 index 0000000000..edb0b3f216 --- /dev/null +++ b/src/status/types/message_command_parameters.nim @@ -0,0 +1,16 @@ +{.used.} + +import strformat + +type CommandParameters* = object + id*: string + fromAddress*: string + address*: string + contract*: string + value*: string + transactionHash*: string + commandState*: int + signature*: string + +proc `$`*(self: CommandParameters): string = + result = fmt"CommandParameters(id:{self.id}, fromAddr:{self.fromAddress}, addr:{self.address}, contract:{self.contract}, value:{self.value}, transactionHash:{self.transactionHash}, commandState:{self.commandState}, signature:{self.signature})" \ No newline at end of file diff --git a/src/status/types/message_reaction.nim b/src/status/types/message_reaction.nim new file mode 100644 index 0000000000..f554aa3140 --- /dev/null +++ b/src/status/types/message_reaction.nim @@ -0,0 +1,21 @@ +{.used.} + +import json + +type Reaction* = object + id*: string + chatId*: string + fromAccount*: string + messageId*: string + emojiId*: int + retracted*: bool + +proc toReaction*(jsonReaction: JsonNode): Reaction = + result = Reaction( + id: jsonReaction{"id"}.getStr, + chatId: jsonReaction{"chatId"}.getStr, + fromAccount: jsonReaction{"from"}.getStr, + messageId: jsonReaction{"messageId"}.getStr, + emojiId: jsonReaction{"emojiId"}.getInt, + retracted: jsonReaction{"retracted"}.getBool + ) \ No newline at end of file diff --git a/src/status/types/message_text_item.nim b/src/status/types/message_text_item.nim new file mode 100644 index 0000000000..210ea49075 --- /dev/null +++ b/src/status/types/message_text_item.nim @@ -0,0 +1,25 @@ +{.used.} + +import json, strutils + +type TextItem* = object + textType*: string + children*: seq[TextItem] + literal*: string + destination*: string + +proc toTextItem*(jsonText: JsonNode): TextItem = + result = TextItem( + literal: jsonText{"literal"}.getStr, + textType: jsonText{"type"}.getStr, + destination: jsonText{"destination"}.getStr, + children: @[] + ) + if (result.literal.startsWith("statusim://")): + result.textType = "link" + # TODO isolate the link only + result.destination = result.literal + + if jsonText.hasKey("children") and jsonText["children"].kind != JNull: + for child in jsonText["children"]: + result.children.add(child.toTextItem) \ No newline at end of file diff --git a/src/status/types/multi_accounts.nim b/src/status/types/multi_accounts.nim new file mode 100644 index 0000000000..f11871230f --- /dev/null +++ b/src/status/types/multi_accounts.nim @@ -0,0 +1,12 @@ +{.used.} + +import json_serialization +import ../libstatus/accounts/constants + +include derived_account + +type MultiAccounts* = object + whisper* {.serializedFieldName(PATH_WHISPER).}: DerivedAccount + walletRoot* {.serializedFieldName(PATH_WALLET_ROOT).}: DerivedAccount + defaultWallet* {.serializedFieldName(PATH_DEFAULT_WALLET).}: DerivedAccount + eip1581* {.serializedFieldName(PATH_EIP_1581).}: DerivedAccount \ No newline at end of file diff --git a/src/status/types/network.nim b/src/status/types/network.nim new file mode 100644 index 0000000000..ddd37b7a48 --- /dev/null +++ b/src/status/types/network.nim @@ -0,0 +1,15 @@ +{.used.} + +include node_config +include network_details +include upstream_config + +type + Network* {.pure.} = enum + Mainnet = "mainnet_rpc", + Testnet = "testnet_rpc", + Rinkeby = "rinkeby_rpc", + Goerli = "goerli_rpc", + XDai = "xdai_rpc", + Poa = "poa_rpc", + Other = "other" \ No newline at end of file diff --git a/src/status/types/network_details.nim b/src/status/types/network_details.nim new file mode 100644 index 0000000000..6e4aeb7bb6 --- /dev/null +++ b/src/status/types/network_details.nim @@ -0,0 +1,12 @@ +{.used.} + +import json_serialization + +import node_config + +type + NetworkDetails* = ref object + id*: string + name*: string + etherscanLink* {.serializedFieldName("etherscan-link").}: string + config*: NodeConfig diff --git a/src/status/types/node_config.nim b/src/status/types/node_config.nim new file mode 100644 index 0000000000..c266e326e4 --- /dev/null +++ b/src/status/types/node_config.nim @@ -0,0 +1,11 @@ +{.used.} + +import json_serialization + +import upstream_config + +type + NodeConfig* = ref object + networkId* {.serializedFieldName("NetworkId").}: int + dataDir* {.serializedFieldName("DataDir").}: string + upstreamConfig* {.serializedFieldName("UpstreamConfig").}: UpstreamConfig \ No newline at end of file diff --git a/src/status/notifications/os_notification_details.nim b/src/status/types/os_notification.nim similarity index 89% rename from src/status/notifications/os_notification_details.nim rename to src/status/types/os_notification.nim index 71b321a213..e9eda5e8ad 100644 --- a/src/status/notifications/os_notification_details.nim +++ b/src/status/types/os_notification.nim @@ -1,5 +1,9 @@ +{.used.} + import json +import ../../eventemitter + type OsNotificationType* {.pure.} = enum NewContactRequest = 1, @@ -15,6 +19,10 @@ type channelId*: string messageId*: string +type + OsNotificationsArgs* = ref object of Args + details*: OsNotificationDetails + proc toOsNotificationDetails*(json: JsonNode): OsNotificationDetails = if (not (json.contains("notificationType") and json.contains("communityId") and diff --git a/src/status/types/pending_transaction_type.nim b/src/status/types/pending_transaction_type.nim new file mode 100644 index 0000000000..fa094a5b7f --- /dev/null +++ b/src/status/types/pending_transaction_type.nim @@ -0,0 +1,8 @@ +{.used.} + +type PendingTransactionType* {.pure.} = enum + RegisterENS = "RegisterENS", + SetPubKey = "SetPubKey", + ReleaseENS = "ReleaseENS", + BuyStickerPack = "BuyStickerPack" + WalletTransfer = "WalletTransfer" diff --git a/src/status/types/profile.nim b/src/status/types/profile.nim new file mode 100644 index 0000000000..540bf37b33 --- /dev/null +++ b/src/status/types/profile.nim @@ -0,0 +1,49 @@ +{.used.} + +import json, strformat +import identity_image + +export identity_image + +type Profile* = ref object + id*, alias*, username*, identicon*, address*, ensName*, localNickname*: string + ensVerified*: bool + messagesFromContactsOnly*: bool + sendUserStatus*: bool + currentUserStatus*: int + identityImage*: IdentityImage + appearance*: int + systemTags*: seq[string] + +proc `$`*(self: Profile): string = + return fmt"Profile(id:{self.id}, username:{self.username})" + +proc toProfileModel*(profile: JsonNode): Profile = + var systemTags: seq[string] = @[] + if profile["systemTags"].kind != JNull: + systemTags = profile["systemTags"].to(seq[string]) + + result = Profile( + id: profile["id"].str, + username: profile["alias"].str, + identicon: profile["identicon"].str, + identityImage: IdentityImage(), + address: profile["id"].str, + alias: profile["alias"].str, + ensName: "", + ensVerified: profile["ensVerified"].getBool, + appearance: 0, + systemTags: systemTags + ) + + if profile.hasKey("name"): + result.ensName = profile["name"].str + + if profile.hasKey("localNickname"): + result.localNickname = profile["localNickname"].str + + if profile.hasKey("images") and profile["images"].kind != JNull: + if profile["images"].hasKey("thumbnail"): + result.identityImage.thumbnail = profile["images"]["thumbnail"]["uri"].str + if profile["images"].hasKey("large"): + result.identityImage.large = profile["images"]["large"]["uri"].str \ No newline at end of file diff --git a/src/status/types/removed_message.nim b/src/status/types/removed_message.nim new file mode 100644 index 0000000000..dffab11e50 --- /dev/null +++ b/src/status/types/removed_message.nim @@ -0,0 +1,13 @@ +{.used.} + +import json + +type RemovedMessage* = object + chatId*: string + messageId*: string + +proc toRemovedMessage*(jsonRemovedMessage: JsonNode): RemovedMessage = + result = RemovedMessage( + chatId: jsonRemovedMessage{"chatId"}.getStr, + messageId: jsonRemovedMessage{"messageId"}.getStr, + ) \ No newline at end of file diff --git a/src/status/types/rpc_response.nim b/src/status/types/rpc_response.nim new file mode 100644 index 0000000000..9a6e30b430 --- /dev/null +++ b/src/status/types/rpc_response.nim @@ -0,0 +1,29 @@ +{.used.} + +type + RpcError* = ref object + code*: int + message*: string + +type + RpcResponse* = ref object + jsonrpc*: string + result*: string + id*: int + error*: RpcError + + # TODO: replace all RpcResponse and RpcResponseTyped occurances with a generic + # form of RpcReponse. IOW, rename RpceResponseTyped*[T] to RpcResponse*[T] and + # remove RpcResponse. +type + RpcResponseTyped*[T] = object + jsonrpc*: string + result*: T + id*: int + error*: RpcError + +type + StatusGoException* = object of CatchableError + +type + RpcException* = object of CatchableError \ No newline at end of file diff --git a/src/status/types/setting.nim b/src/status/types/setting.nim new file mode 100644 index 0000000000..123f58b593 --- /dev/null +++ b/src/status/types/setting.nim @@ -0,0 +1,31 @@ +{.used.} + +type + Setting* {.pure.} = enum + Appearance = "appearance", + Bookmarks = "bookmarks", + Currency = "currency" + EtherscanLink = "etherscan-link" + InstallationId = "installation-id" + MessagesFromContactsOnly = "messages-from-contacts-only" + Mnemonic = "mnemonic" + Networks_Networks = "networks/networks" + Networks_CurrentNetwork = "networks/current-network" + NodeConfig = "node-config" + PublicKey = "public-key" + DappsAddress = "dapps-address" + Stickers_PacksInstalled = "stickers/packs-installed" + Stickers_Recent = "stickers/recent-stickers" + Gifs_Recent = "gifs/recent-gifs" + Gifs_Favorite = "gifs/favorite-gifs" + WalletRootAddress = "wallet-root-address" + LatestDerivedPath = "latest-derived-path" + PreferredUsername = "preferred-name" + Usernames = "usernames" + SigningPhrase = "signing-phrase" + Fleet = "fleet" + VisibleTokens = "wallet/visible-tokens" + PinnedMailservers = "pinned-mailservers" + WakuBloomFilterMode = "waku-bloom-filter-mode" + SendUserStatus = "send-status-updates?" + CurrentUserStatus = "current-user-status" \ No newline at end of file diff --git a/src/status/types/status_update.nim b/src/status/types/status_update.nim new file mode 100644 index 0000000000..56c084f466 --- /dev/null +++ b/src/status/types/status_update.nim @@ -0,0 +1,24 @@ +{.used.} + +import json + +type StatusUpdateType* {.pure.}= enum + Unknown = 0, + Online = 1, + DoNotDisturb = 2 + +type StatusUpdate* = object + publicKey*: string + statusType*: StatusUpdateType + clock*: uint64 + text*: string + +proc toStatusUpdate*(jsonStatusUpdate: JsonNode): StatusUpdate = + let statusTypeInt = jsonStatusUpdate{"statusType"}.getInt + let statusType: StatusUpdateType = if statusTypeInt >= ord(low(StatusUpdateType)) or statusTypeInt <= ord(high(StatusUpdateType)): StatusUpdateType(statusTypeInt) else: StatusUpdateType.Unknown + result = StatusUpdate( + publicKey: jsonStatusUpdate{"publicKey"}.getStr, + statusType: statusType, + clock: uint64(jsonStatusUpdate{"clock"}.getBiggestInt), + text: jsonStatusUpdate{"text"}.getStr + ) \ No newline at end of file diff --git a/src/status/types/sticker.nim b/src/status/types/sticker.nim new file mode 100644 index 0000000000..62055c47e7 --- /dev/null +++ b/src/status/types/sticker.nim @@ -0,0 +1,32 @@ +{.used.} + +import json, options, typetraits +import web3/ethtypes, json_serialization, stint + +type Sticker* = object + hash*: string + packId*: int + +type StickerPack* = object + author*: string + id*: int + name*: string + price*: Stuint[256] + preview*: string + stickers*: seq[Sticker] + thumbnail*: string + +proc `%`*(stuint256: Stuint[256]): JsonNode = + newJString($stuint256) + +proc readValue*(reader: var JsonReader, value: var Stuint[256]) + {.raises: [IOError, SerializationError, Defect].} = + try: + let strVal = reader.readValue(string) + value = strVal.parse(Stuint[256]) + except: + try: + let intVal = reader.readValue(int) + value = intVal.stuint(256) + except: + raise newException(SerializationError, "Expected string or int representation of Stuint[256]") \ No newline at end of file diff --git a/src/status/types/transaction.nim b/src/status/types/transaction.nim new file mode 100644 index 0000000000..d86d77db28 --- /dev/null +++ b/src/status/types/transaction.nim @@ -0,0 +1,30 @@ +{.used.} + +import strutils + +include pending_transaction_type + +type + Transaction* = ref object + id*: string + typeValue*: string + address*: string + blockNumber*: string + blockHash*: string + contract*: string + timestamp*: string + gasPrice*: string + gasLimit*: string + gasUsed*: string + nonce*: string + txStatus*: string + value*: string + fromAddress*: string + to*: string + +proc cmpTransactions*(x, y: Transaction): int = + # Sort proc to compare transactions from a single account. + # Compares first by block number, then by nonce + result = cmp(x.blockNumber.parseHexInt, y.blockNumber.parseHexInt) + if result == 0: + result = cmp(x.nonce, y.nonce) \ No newline at end of file diff --git a/src/status/types/upstream_config.nim b/src/status/types/upstream_config.nim new file mode 100644 index 0000000000..81a513f5de --- /dev/null +++ b/src/status/types/upstream_config.nim @@ -0,0 +1,8 @@ +{.used.} + +import json_serialization + +type + UpstreamConfig* = ref object + enabled* {.serializedFieldName("Enabled").}: bool + url* {.serializedFieldName("URL").}: string \ No newline at end of file diff --git a/src/status/wallet.nim b/src/status/wallet.nim index 35ba990daa..9a62b8c8fc 100644 --- a/src/status/wallet.nim +++ b/src/status/wallet.nim @@ -10,13 +10,14 @@ import libstatus/wallet as status_wallet import libstatus/accounts/constants as constants import libstatus/eth/[eth, contracts] from libstatus/core import getBlockByNumber -from types import PendingTransactionType, GeneratedAccount, DerivedAccount, Transaction, Setting, GasPricePrediction, `%`, StatusGoException, Network, RpcResponse, RpcException from utils as libstatus_utils import eth2Wei, gwei2Wei, first, toUInt64, parseAddress -import wallet/[balance_manager, account, collectibles] +import wallet/[balance_manager, collectibles] +import wallet/account as wallet_account import transactions import ../eventemitter import options -export account, collectibles +import ./types/[account, transaction, network, setting, gas_prediction, rpc_response] +export wallet_account, collectibles export Transaction logScope: @@ -256,7 +257,7 @@ proc addNewGeneratedAccount(self: WalletModel, generatedAccount: GeneratedAccoun # wallet_checkRecentHistory populates the status-go db that # wallet_getTransfersByAddress reads from discard status_wallet.checkRecentHistory(self.accounts.map(account => account.address)) - self.events.emit("newAccountAdded", AccountArgs(account: account)) + self.events.emit("newAccountAdded", wallet_account.AccountArgs(account: account)) except Exception as e: raise newException(StatusGoException, fmt"Error adding new account: {e.msg}") diff --git a/src/status/wallet/account.nim b/src/status/wallet/account.nim index 7ceaa383f2..543e42d244 100644 --- a/src/status/wallet/account.nim +++ b/src/status/wallet/account.nim @@ -1,7 +1,7 @@ import options, json, strformat from ../../eventemitter import Args -import ../types +import ../types/[transaction] type CollectibleList* = ref object collectibleType*, collectiblesJSON*, error*: string diff --git a/src/status/wallet/balance_manager.nim b/src/status/wallet/balance_manager.nim index 74febe37cd..13823da08b 100644 --- a/src/status/wallet/balance_manager.nim +++ b/src/status/wallet/balance_manager.nim @@ -1,7 +1,7 @@ import strformat, strutils, stint, httpclient, json, chronicles, net import ../libstatus/wallet as status_wallet import ../libstatus/tokens as status_tokens -import ../types as status_types +import ../types/[rpc_response] import ../utils/cache import account import options diff --git a/src/status/wallet/collectibles.nim b/src/status/wallet/collectibles.nim index af9ee24d05..768492d172 100644 --- a/src/status/wallet/collectibles.nim +++ b/src/status/wallet/collectibles.nim @@ -7,7 +7,7 @@ import # vendor libs import # status-desktop libs ../libstatus/core as status, ../libstatus/eth/contracts as contracts, - ../stickers as status_stickers, ../types, + ../stickers as status_stickers, web3/[conversions, ethtypes], ../utils, account const CRYPTOKITTY* = "cryptokitty" diff --git a/src/status/wallet2.nim b/src/status/wallet2.nim index 6c12932023..51590adef8 100644 --- a/src/status/wallet2.nim +++ b/src/status/wallet2.nim @@ -1,27 +1,19 @@ -import NimQml import json, strformat, options, chronicles, sugar, sequtils, strutils -import tasks/[qt, task_runner_impl] -import wallet2/[balance_manager, account, collectibles] -import ../eventemitter - -from types import PendingTransactionType, GeneratedAccount, DerivedAccount, - Transaction, Setting, GasPricePrediction, `%`, StatusGoException, Network, - RpcResponse, RpcException - import libstatus/accounts as status_accounts import libstatus/accounts/constants as constants import libstatus/tokens as status_tokens import libstatus/wallet as status_wallet import libstatus/settings as status_settings import libstatus/eth/[contracts] - +import wallet2/[balance_manager, collectibles] +import wallet2/account as wallet_account +import ./types/[account, transaction, network, setting, gas_prediction, rpc_response] +import ../eventemitter from web3/ethtypes import Address from web3/conversions import `$` -export account, collectibles - -include wallet2/async_tasks +export wallet_account, collectibles logScope: topics = "status-wallet2" @@ -30,205 +22,192 @@ type CryptoServicesArg* = ref object of Args services*: JsonNode # an array -QtObject: - type StatusWalletController* = ref object of QObject +type + StatusWalletController* = ref object events: EventEmitter - tasks: TaskRunner accounts: seq[WalletAccount] tokens: seq[Erc20Contract] totalBalance*: float # Forward declarations - proc initEvents*(self: StatusWalletController) - proc generateAccountConfiguredAssets*(self: StatusWalletController, - accountAddress: string): seq[Asset] - proc calculateTotalFiatBalance*(self: StatusWalletController) +proc initEvents*(self: StatusWalletController) +proc generateAccountConfiguredAssets*(self: StatusWalletController, + accountAddress: string): seq[Asset] +proc calculateTotalFiatBalance*(self: StatusWalletController) - proc setup(self: StatusWalletController, events: EventEmitter, tasks: TaskRunner) = - self.QObject.setup - self.events = events - self.tasks = tasks - self.accounts = @[] - self.tokens = @[] - self.totalBalance = 0.0 - self.initEvents() +proc setup(self: StatusWalletController, events: EventEmitter) = + self.events = events + self.accounts = @[] + self.tokens = @[] + self.totalBalance = 0.0 + self.initEvents() - proc delete*(self: StatusWalletController) = - self.QObject.delete +proc delete*(self: StatusWalletController) = + discard - proc newStatusWalletController*(events: EventEmitter, tasks: TaskRunner): - StatusWalletController = - result = StatusWalletController() - result.setup(events, tasks) +proc newStatusWalletController*(events: EventEmitter): + StatusWalletController = + result = StatusWalletController() + result.setup(events) - proc initTokens(self: StatusWalletController) = - self.tokens = status_tokens.getVisibleTokens() +proc initTokens(self: StatusWalletController) = + self.tokens = status_tokens.getVisibleTokens() - proc initAccounts(self: StatusWalletController) = - let accounts = status_wallet.getWalletAccounts() - for acc in accounts: - var assets: seq[Asset] = self.generateAccountConfiguredAssets(acc.address) - var walletAccount = newWalletAccount(acc.name, acc.address, acc.iconColor, - acc.path, acc.walletType, acc.publicKey, acc.wallet, acc.chat, assets) - self.accounts.add(walletAccount) +proc initAccounts(self: StatusWalletController) = + let accounts = status_wallet.getWalletAccounts() + for acc in accounts: + var assets: seq[Asset] = self.generateAccountConfiguredAssets(acc.address) + var walletAccount = newWalletAccount(acc.name, acc.address, acc.iconColor, + acc.path, acc.walletType, acc.publicKey, acc.wallet, acc.chat, assets) + self.accounts.add(walletAccount) - proc init*(self: StatusWalletController) = - self.initTokens() - self.initAccounts() +proc init*(self: StatusWalletController) = + self.initTokens() + self.initAccounts() - proc initEvents*(self: StatusWalletController) = - self.events.on("currencyChanged") do(e: Args): - self.events.emit("accountsUpdated", Args()) +proc initEvents*(self: StatusWalletController) = + self.events.on("currencyChanged") do(e: Args): + self.events.emit("accountsUpdated", Args()) - self.events.on("newAccountAdded") do(e: Args): - self.calculateTotalFiatBalance() + self.events.on("newAccountAdded") do(e: Args): + self.calculateTotalFiatBalance() - proc getAccounts*(self: StatusWalletController): seq[WalletAccount] = - self.accounts +proc getAccounts*(self: StatusWalletController): seq[WalletAccount] = + self.accounts - proc getDefaultCurrency*(self: StatusWalletController): string = - # TODO: this should come from a model? It is going to be used too in the - # profile section and ideally we should not call the settings more than once - status_settings.getSetting[string](Setting.Currency, "usd") +proc getDefaultCurrency*(self: StatusWalletController): string = +# TODO: this should come from a model? It is going to be used too in the +# profile section and ideally we should not call the settings more than once + status_settings.getSetting[string](Setting.Currency, "usd") - proc generateAccountConfiguredAssets*(self: StatusWalletController, - accountAddress: string): seq[Asset] = - var assets: seq[Asset] = @[] - var asset = Asset(name:"Ethereum", symbol: "ETH", value: "0.0", - fiatBalanceDisplay: "0.0", accountAddress: accountAddress) - assets.add(asset) - for token in self.tokens: - var symbol = token.symbol - var existingToken = Asset(name: token.name, symbol: symbol, - value: fmt"0.0", fiatBalanceDisplay: "$0.0", accountAddress: accountAddress, - address: $token.address) - assets.add(existingToken) - assets +proc generateAccountConfiguredAssets*(self: StatusWalletController, + accountAddress: string): seq[Asset] = + var assets: seq[Asset] = @[] + var asset = Asset(name:"Ethereum", symbol: "ETH", value: "0.0", + fiatBalanceDisplay: "0.0", accountAddress: accountAddress) + assets.add(asset) + for token in self.tokens: + var symbol = token.symbol + var existingToken = Asset(name: token.name, symbol: symbol, + value: fmt"0.0", fiatBalanceDisplay: "$0.0", accountAddress: accountAddress, + address: $token.address) + assets.add(existingToken) + assets - proc calculateTotalFiatBalance*(self: StatusWalletController) = - self.totalBalance = 0.0 - for account in self.accounts: - if account.realFiatBalance.isSome: - self.totalBalance += account.realFiatBalance.get() +proc calculateTotalFiatBalance*(self: StatusWalletController) = + self.totalBalance = 0.0 + for account in self.accounts: + if account.realFiatBalance.isSome: + self.totalBalance += account.realFiatBalance.get() - proc newAccount*(self: StatusWalletController, walletType: string, derivationPath: string, - name: string, address: string, iconColor: string, balance: string, - publicKey: string): WalletAccount = - var assets: seq[Asset] = self.generateAccountConfiguredAssets(address) - var account = WalletAccount(name: name, path: derivationPath, walletType: walletType, - address: address, iconColor: iconColor, balance: none[string](), assetList: assets, - realFiatBalance: none[float](), publicKey: publicKey) - updateBalance(account, self.getDefaultCurrency()) - account +proc newAccount*(self: StatusWalletController, walletType: string, derivationPath: string, + name: string, address: string, iconColor: string, balance: string, + publicKey: string): WalletAccount = + var assets: seq[Asset] = self.generateAccountConfiguredAssets(address) + var account = WalletAccount(name: name, path: derivationPath, walletType: walletType, + address: address, iconColor: iconColor, balance: none[string](), assetList: assets, + realFiatBalance: none[float](), publicKey: publicKey) + updateBalance(account, self.getDefaultCurrency()) + account - proc addNewGeneratedAccount(self: StatusWalletController, generatedAccount: GeneratedAccount, - password: string, accountName: string, color: string, accountType: string, - isADerivedAccount = true, walletIndex: int = 0) = - try: - generatedAccount.name = accountName - var derivedAccount: DerivedAccount = status_accounts.saveAccount(generatedAccount, - password, color, accountType, isADerivedAccount, walletIndex) - var account = self.newAccount(accountType, derivedAccount.derivationPath, - accountName, derivedAccount.address, color, fmt"0.00 {self.getDefaultCurrency()}", - derivedAccount.publicKey) +proc addNewGeneratedAccount(self: StatusWalletController, generatedAccount: GeneratedAccount, + password: string, accountName: string, color: string, accountType: string, + isADerivedAccount = true, walletIndex: int = 0) = + try: + generatedAccount.name = accountName + var derivedAccount: DerivedAccount = status_accounts.saveAccount(generatedAccount, + password, color, accountType, isADerivedAccount, walletIndex) + var account = self.newAccount(accountType, derivedAccount.derivationPath, + accountName, derivedAccount.address, color, fmt"0.00 {self.getDefaultCurrency()}", + derivedAccount.publicKey) - self.accounts.add(account) - # wallet_checkRecentHistory is required to be called when a new account is - # added before wallet_getTransfersByAddress can be called. This is because - # wallet_checkRecentHistory populates the status-go db that - # wallet_getTransfersByAddress reads from - discard status_wallet.checkRecentHistory(self.accounts.map(account => account.address)) - self.events.emit("newAccountAdded", AccountArgs(account: account)) - except Exception as e: - raise newException(StatusGoException, fmt"Error adding new account: {e.msg}") + self.accounts.add(account) + # wallet_checkRecentHistory is required to be called when a new account is + # added before wallet_getTransfersByAddress can be called. This is because + # wallet_checkRecentHistory populates the status-go db that + # wallet_getTransfersByAddress reads from + discard status_wallet.checkRecentHistory(self.accounts.map(account => account.address)) + self.events.emit("newAccountAdded", wallet_account.AccountArgs(account: account)) + except Exception as e: + raise newException(StatusGoException, fmt"Error adding new account: {e.msg}") - proc generateNewAccount*(self: StatusWalletController, password: string, accountName: string, color: string) = - let - walletRootAddress = status_settings.getSetting[string](Setting.WalletRootAddress, "") - walletIndex = status_settings.getSetting[int](Setting.LatestDerivedPath) + 1 - loadedAccount = status_accounts.loadAccount(walletRootAddress, password) - derivedAccount = status_accounts.deriveWallet(loadedAccount.id, walletIndex) - generatedAccount = GeneratedAccount( - id: loadedAccount.id, - publicKey: derivedAccount.publicKey, - address: derivedAccount.address - ) - - # if we've gotten here, the password is ok (loadAccount requires a valid password) - # so no need to check for a valid password - self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.GENERATED, true, walletIndex) - - let statusGoResult = status_settings.saveSetting(Setting.LatestDerivedPath, $walletIndex) - if statusGoResult.error != "": - error "Error storing the latest wallet index", msg=statusGoResult.error - - proc addAccountsFromSeed*(self: StatusWalletController, seed: string, password: string, accountName: string, color: string) = - let mnemonic = replace(seed, ',', ' ') - var generatedAccount = status_accounts.multiAccountImportMnemonic(mnemonic) - generatedAccount.derived = status_accounts.deriveAccounts(generatedAccount.id) - - let - defaultAccount = status_accounts.getDefaultAccount() - isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password) - if not isPasswordOk: - raise newException(StatusGoException, "Error generating new account: invalid password") - - self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.SEED) - - proc addAccountsFromPrivateKey*(self: StatusWalletController, privateKey: string, password: string, accountName: string, color: string) = - let - generatedAccount = status_accounts.MultiAccountImportPrivateKey(privateKey) - defaultAccount = status_accounts.getDefaultAccount() - isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password) - - if not isPasswordOk: - raise newException(StatusGoException, "Error generating new account: invalid password") - - self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.KEY, false) - - proc addWatchOnlyAccount*(self: StatusWalletController, address: string, accountName: string, color: string) = - let account = GeneratedAccount(address: address) - self.addNewGeneratedAccount(account, "", accountName, color, constants.WATCH, false) - - proc changeAccountSettings*(self: StatusWalletController, address: string, accountName: string, color: string): string = - var selectedAccount: WalletAccount - for account in self.accounts: - if (account.address == address): - selectedAccount = account - break - if (isNil(selectedAccount)): - result = "No account found with that address" - error "No account found with that address", address - selectedAccount.name = accountName - selectedAccount.iconColor = color - result = status_accounts.changeAccount(selectedAccount.name, selectedAccount.address, - selectedAccount.publicKey, selectedAccount.walletType, selectedAccount.iconColor) - - proc deleteAccount*(self: StatusWalletController, address: string): string = - result = status_accounts.deleteAccount(address) - self.accounts = self.accounts.filter(acc => acc.address.toLowerAscii != address.toLowerAscii) - - proc getOpenseaCollections*(address: string): string = - result = status_wallet.getOpenseaCollections(address) - - proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string = - result = status_wallet.getOpenseaAssets(address, collectionSlug, limit) - - proc asyncFetchCryptoServices*(self: StatusWalletController) = - ## Asynchronous request for the list of services to buy/sell crypto. - let arg = QObjectTaskArg( - tptr: cast[ByteAddress](asyncGetCryptoServicesTask), - vptr: cast[ByteAddress](self.vptr), - slot: "onAsyncFetchCryptoServices" +proc generateNewAccount*(self: StatusWalletController, password: string, accountName: string, color: string) = + let + walletRootAddress = status_settings.getSetting[string](Setting.WalletRootAddress, "") + walletIndex = status_settings.getSetting[int](Setting.LatestDerivedPath) + 1 + loadedAccount = status_accounts.loadAccount(walletRootAddress, password) + derivedAccount = status_accounts.deriveWallet(loadedAccount.id, walletIndex) + generatedAccount = GeneratedAccount( + id: loadedAccount.id, + publicKey: derivedAccount.publicKey, + address: derivedAccount.address ) - self.tasks.threadpool.start(arg) - proc onAsyncFetchCryptoServices*(self: StatusWalletController, - response: string) {.slot.} = - let responseArray = response.parseJson - if (responseArray.kind != JArray): - info "received crypto services is not a json array" - self.events.emit("cryptoServicesFetched", CryptoServicesArg()) - return + # if we've gotten here, the password is ok (loadAccount requires a valid password) + # so no need to check for a valid password + self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.GENERATED, true, walletIndex) + + let statusGoResult = status_settings.saveSetting(Setting.LatestDerivedPath, $walletIndex) + if statusGoResult.error != "": + error "Error storing the latest wallet index", msg=statusGoResult.error - self.events.emit("cryptoServicesFetched", CryptoServicesArg(services: responseArray)) \ No newline at end of file +proc addAccountsFromSeed*(self: StatusWalletController, seed: string, password: string, accountName: string, color: string) = + let mnemonic = replace(seed, ',', ' ') + var generatedAccount = status_accounts.multiAccountImportMnemonic(mnemonic) + generatedAccount.derived = status_accounts.deriveAccounts(generatedAccount.id) + + let + defaultAccount = status_accounts.getDefaultAccount() + isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password) + if not isPasswordOk: + raise newException(StatusGoException, "Error generating new account: invalid password") + + self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.SEED) + +proc addAccountsFromPrivateKey*(self: StatusWalletController, privateKey: string, password: string, accountName: string, color: string) = + let + generatedAccount = status_accounts.MultiAccountImportPrivateKey(privateKey) + defaultAccount = status_accounts.getDefaultAccount() + isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password) + + if not isPasswordOk: + raise newException(StatusGoException, "Error generating new account: invalid password") + + self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.KEY, false) + +proc addWatchOnlyAccount*(self: StatusWalletController, address: string, accountName: string, color: string) = + let account = GeneratedAccount(address: address) + self.addNewGeneratedAccount(account, "", accountName, color, constants.WATCH, false) + +proc changeAccountSettings*(self: StatusWalletController, address: string, accountName: string, color: string): string = + var selectedAccount: WalletAccount + for account in self.accounts: + if (account.address == address): + selectedAccount = account + break + if (isNil(selectedAccount)): + result = "No account found with that address" + error "No account found with that address", address + selectedAccount.name = accountName + selectedAccount.iconColor = color + result = status_accounts.changeAccount(selectedAccount.name, selectedAccount.address, + selectedAccount.publicKey, selectedAccount.walletType, selectedAccount.iconColor) + +proc deleteAccount*(self: StatusWalletController, address: string): string = + result = status_accounts.deleteAccount(address) + self.accounts = self.accounts.filter(acc => acc.address.toLowerAscii != address.toLowerAscii) + +proc getOpenseaCollections*(address: string): string = + result = status_wallet.getOpenseaCollections(address) + +proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string = + result = status_wallet.getOpenseaAssets(address, collectionSlug, limit) + +proc onAsyncFetchCryptoServices*(self: StatusWalletController, response: string) = + let responseArray = response.parseJson + if (responseArray.kind != JArray): + info "received crypto services is not a json array" + self.events.emit("cryptoServicesFetched", CryptoServicesArg()) + return + + self.events.emit("cryptoServicesFetched", CryptoServicesArg(services: responseArray)) \ No newline at end of file diff --git a/src/status/wallet2/account.nim b/src/status/wallet2/account.nim index 8a87dc9937..deeb86bef8 100644 --- a/src/status/wallet2/account.nim +++ b/src/status/wallet2/account.nim @@ -1,7 +1,7 @@ import options, json, strformat from ../../eventemitter import Args -import ../types +import ../types/[transaction] type CollectibleList* = ref object collectibleType*, collectiblesJSON*, error*: string diff --git a/src/status/wallet2/balance_manager.nim b/src/status/wallet2/balance_manager.nim index 9f8e4d97f8..d51b91fc7d 100644 --- a/src/status/wallet2/balance_manager.nim +++ b/src/status/wallet2/balance_manager.nim @@ -1,7 +1,7 @@ import strformat, strutils, stint, httpclient, json, chronicles, net import ../libstatus/wallet as status_wallet import ../libstatus/tokens as status_tokens -import ../types as status_types +import ../types/[rpc_response] import ../utils/cache import account import options diff --git a/src/status/wallet2/collectibles.nim b/src/status/wallet2/collectibles.nim index af9ee24d05..768492d172 100644 --- a/src/status/wallet2/collectibles.nim +++ b/src/status/wallet2/collectibles.nim @@ -7,7 +7,7 @@ import # vendor libs import # status-desktop libs ../libstatus/core as status, ../libstatus/eth/contracts as contracts, - ../stickers as status_stickers, ../types, + ../stickers as status_stickers, web3/[conversions, ethtypes], ../utils, account const CRYPTOKITTY* = "cryptokitty"