From 698374b91ced86774717f34133ba4d0614273bce Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 17 Mar 2022 17:24:50 +0100 Subject: [PATCH] feat(desktop/general): app support for status-im:// URIs - mac Fixes #3375 --- Info.dev.plist | 13 ++ Info.plist | 13 ++ src-cpp/app/global/app_sections_config.h | 1 - src/app/core/custom_urls/urls_manager.nim | 132 ++++++++++++++++++ src/app/core/main.nim | 12 +- src/app/global/app_signals.nim | 26 +++- .../main/browser_section/io_interface.nim | 3 + .../modules/main/browser_section/module.nim | 3 + src/app/modules/main/browser_section/view.nim | 4 + .../service/contacts/async_tasks.nim | 4 +- src/nim_status_client.nim | 4 + 11 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 src/app/core/custom_urls/urls_manager.nim diff --git a/Info.dev.plist b/Info.dev.plist index 46a4d869a8..ebf354ef47 100644 --- a/Info.dev.plist +++ b/Info.dev.plist @@ -16,6 +16,19 @@ StatusDev CFBundlePackageType APPL + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + status.im.customurl + CFBundleURLSchemes + + status-im + + + CFBundleShortVersionString 1.0.0 IFMajorVersion diff --git a/Info.plist b/Info.plist index b5e434afe3..bde2d54e6c 100644 --- a/Info.plist +++ b/Info.plist @@ -16,6 +16,19 @@ Status CFBundlePackageType APPL + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + status.im.customurl + CFBundleURLSchemes + + status-im + + + CFBundleShortVersionString 1.0.0 IFMajorVersion diff --git a/src-cpp/app/global/app_sections_config.h b/src-cpp/app/global/app_sections_config.h index 55584e1d74..35f01a2755 100644 --- a/src-cpp/app/global/app_sections_config.h +++ b/src-cpp/app/global/app_sections_config.h @@ -3,7 +3,6 @@ // To Do: Currently this gets added to eahc file that its imported into need to create as enums in calss when some works on this potentially #include -const QString CHAT_SECTION_ID = "chat"; const QString CHAT_SECTION_NAME = "Chat"; const QString CHAT_SECTION_ICON = "chat"; diff --git a/src/app/core/custom_urls/urls_manager.nim b/src/app/core/custom_urls/urls_manager.nim new file mode 100644 index 0000000000..ec05e656d3 --- /dev/null +++ b/src/app/core/custom_urls/urls_manager.nim @@ -0,0 +1,132 @@ +import NimQml, strutils, chronicles +import ../eventemitter + +import ../../global/app_signals + +logScope: + topics = "urls-manager" + +const UriFormatBrowserShort = "status-im://b/" +const UriFormatBrowserLong = "status-im://browser/" + +const UriFormatUserProfileShort = "status-im://u/" +const UriFormatUserProfileLong = "status-im://user/" + +const UriFormatPrivateChatShort = "status-im://pm/" +const UriFormatPrivateChatLong = "status-im://private-message/" + +const UriFormatPublicChatShort = "status-im://p/" +const UriFormatPublicChatLong = "status-im://public/" + +const UriFormatGroupChatShort = "status-im://g/" +const UriFormatGroupChatLong = "status-im://group/" + +const UriFormatCommunityRequestsShort = "status-im://cr/" +const UriFormatCommunityRequestsLong = "status-im://community-requests/" + +const UriFormatCommunityShort = "status-im://c/" +const UriFormatCommunityLong = "status-im://community/" + +const UriFormatCommunityChannelShort = "status-im://cc/" +const UriFormatCommunityChannelLong = "status-im://community-channel/" + +QtObject: + type UrlsManager* = ref object of QObject + events: EventEmitter + + proc setup(self: UrlsManager, urlSchemeEvent: StatusEvent) = + self.QObject.setup + signalConnect(urlSchemeEvent, "urlActivated(QString)", self, "onUrlActivated(QString)", 2) + + proc delete*(self: UrlsManager) = + self.QObject.delete + + proc newUrlsManager*(events: EventEmitter, urlSchemeEvent: StatusEvent): UrlsManager = + new(result) + result.setup(urlSchemeEvent) + result.events = events + + proc prepareGroupChatDetails(self: UrlsManager, urlQuery: string, data: var StatusUrlArgs) = + var urlParams = rsplit(urlQuery, "/u/") + if(urlParams.len > 0): + data.groupName = urlParams[0] + urlParams.delete(0) + data.listOfUserIds = urlParams + else: + info "wrong url format for group chat" + + proc onUrlActivated*(self: UrlsManager, url: string) {.slot.} = + var data = StatusUrlArgs() + + # Open `url` in the app's browser + if url.startsWith(UriFormatBrowserShort): + data.action = StatusUrlAction.OpenLinkInBrowser + data.url = url[UriFormatBrowserShort.len .. url.len-1] + elif url.startsWith(UriFormatBrowserLong): + data.action = StatusUrlAction.OpenLinkInBrowser + data.url = url[UriFormatBrowserLong.len .. url.len-1] + + # Display user profile popup for user with `user_pk` or `ens_name` + elif url.startsWith(UriFormatUserProfileShort): + data.action = StatusUrlAction.DisplayUserProfile + data.userId = url[UriFormatUserProfileShort.len .. url.len-1] + elif url.startsWith(UriFormatUserProfileLong): + data.action = StatusUrlAction.DisplayUserProfile + data.userId = url[UriFormatUserProfileLong.len .. url.len-1] + + # Open or create 1:1 chat with user with `user_pk` or `ens_name` + elif url.startsWith(UriFormatPrivateChatShort): + data.action = StatusUrlAction.OpenOrCreatePrivateChat + data.chatId = url[UriFormatPrivateChatShort.len .. url.len-1] + elif url.startsWith(UriFormatPrivateChatLong): + data.action = StatusUrlAction.OpenOrCreatePrivateChat + data.chatId = url[UriFormatPrivateChatLong.len .. url.len-1] + + # Open public chat with `chat_key` + elif url.startsWith(UriFormatPublicChatShort): + data.action = StatusUrlAction.OpenOrJoinPublicChat + data.chatId = url[UriFormatPublicChatShort.len .. url.len-1] + elif url.startsWith(UriFormatPublicChatLong): + data.action = StatusUrlAction.OpenOrJoinPublicChat + data.chatId = url[UriFormatPublicChatLong.len .. url.len-1] + + # Open a group chat with named `group_name`, adding up to 19 participants with their `user_pk` or `ens_name`. + # Group chat may have up to 20 participants including the admin of a group + elif url.startsWith(UriFormatGroupChatShort): + data.action = StatusUrlAction.OpenOrCreateGroupChat + let urlQuery = url[UriFormatGroupChatShort.len .. url.len-1] + self.prepareGroupChatDetails(urlQuery, data) + elif url.startsWith(UriFormatGroupChatLong): + data.action = StatusUrlAction.OpenOrCreateGroupChat + let urlQuery = url[UriFormatGroupChatLong.len .. url.len-1] + self.prepareGroupChatDetails(urlQuery, data) + + # Send a join community request to a community with `community_key` + elif url.startsWith(UriFormatCommunityRequestsShort): + data.action = StatusUrlAction.RequestToJoinCommunity + data.communityId = url[UriFormatCommunityRequestsShort.len .. url.len-1] + elif url.startsWith(UriFormatCommunityRequestsLong): + data.action = StatusUrlAction.RequestToJoinCommunity + data.communityId = url[UriFormatCommunityRequestsLong.len .. url.len-1] + + # Open community with `community_key` + elif url.startsWith(UriFormatCommunityShort): + data.action = StatusUrlAction.OpenCommunity + data.communityId = url[UriFormatCommunityShort.len .. url.len-1] + elif url.startsWith(UriFormatCommunityLong): + data.action = StatusUrlAction.OpenCommunity + data.communityId = url[UriFormatCommunityLong.len .. url.len-1] + + # Open community which has a channel with `channel_key` and makes that channel active + elif url.startsWith(UriFormatCommunityChannelShort): + data.action = StatusUrlAction.OpenCommunityChannel + data.chatId = url[UriFormatCommunityChannelShort.len .. url.len-1] + elif url.startsWith(UriFormatCommunityChannelLong): + data.action = StatusUrlAction.OpenCommunityChannel + data.chatId = url[UriFormatCommunityChannelLong.len .. url.len-1] + + else: + info "Unsupported deep link structure: ", url + return + + self.events.emit(SIGNAL_STATUS_URL_REQUESTED, data) \ No newline at end of file diff --git a/src/app/core/main.nim b/src/app/core/main.nim index 48e4657f37..a032439c26 100644 --- a/src/app/core/main.nim +++ b/src/app/core/main.nim @@ -1,10 +1,11 @@ -import task_runner +import NimQml, task_runner import eventemitter, ./fleets/fleet_configuration, ./tasks/marathon, ./tasks/threadpool, - ./signals/signals_manager + ./signals/signals_manager, + ./custom_urls/urls_manager export eventemitter export marathon, task_runner, signals_manager, fleet_configuration @@ -14,6 +15,7 @@ type StatusFoundation* = ref object fleetConfiguration*: FleetConfiguration threadpool*: ThreadPool signalsManager*: SignalsManager + urlsManager: UrlsManager proc newStatusFoundation*(fleetConfig: string): StatusFoundation = result = StatusFoundation() @@ -25,4 +27,8 @@ proc newStatusFoundation*(fleetConfig: string): StatusFoundation = proc delete*(self: StatusFoundation) = self.threadpool.teardown() self.fleetConfiguration.delete() - self.signalsManager.delete() \ No newline at end of file + self.signalsManager.delete() + self.urlsManager.delete() + +proc initUrlSchemeManager*(self: StatusFoundation, urlSchemeEvent: StatusEvent) = + self.urlsManager = newUrlsManager(self.events, urlSchemeEvent) \ No newline at end of file diff --git a/src/app/global/app_signals.nim b/src/app/global/app_signals.nim index 34ff6d02c9..aebdc0dced 100644 --- a/src/app/global/app_signals.nim +++ b/src/app/global/app_signals.nim @@ -21,4 +21,28 @@ type const SIGNAL_MAKE_SECTION_CHAT_ACTIVE* = "makeSectionChatActive" ## Emmiting this signal will switch the app to passed `sectionId`, after that if `chatId` is set ## it will make that chat an active one and at the end if `messageId` is set it will point to -## that message. \ No newline at end of file +## that message. + + +type + StatusUrlAction* {.pure.} = enum + OpenLinkInBrowser = 0 + DisplayUserProfile, + OpenOrCreatePrivateChat, + OpenOrJoinPublicChat, + OpenOrCreateGroupChat, + RequestToJoinCommunity, + OpenCommunity, + OpenCommunityChannel + +type + StatusUrlArgs* = ref object of Args + action*: StatusUrlAction + communityId*:string + chatId*: string + url*: string + userId*: string # can be public key or ens name + groupName*: string + listOfUserIds*: seq[string] # used for creating group chat + +const SIGNAL_STATUS_URL_REQUESTED* = "statusUrlRequested" \ No newline at end of file diff --git a/src/app/modules/main/browser_section/io_interface.nim b/src/app/modules/main/browser_section/io_interface.nim index b476c85178..a63910abba 100644 --- a/src/app/modules/main/browser_section/io_interface.nim +++ b/src/app/modules/main/browser_section/io_interface.nim @@ -20,4 +20,7 @@ method providerDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") method viewDidLoad*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method openUrl*(self: AccessInterface, url: string) {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/browser_section/module.nim b/src/app/modules/main/browser_section/module.nim index 8a554ccf6f..ca06f0d7d7 100644 --- a/src/app/modules/main/browser_section/module.nim +++ b/src/app/modules/main/browser_section/module.nim @@ -92,3 +92,6 @@ method dappsDidLoad*(self: Module) = method viewDidLoad*(self: Module) = self.checkIfModuleDidLoad() + +method openUrl*(self: Module, url: string) = + self.view.sendOpenUrlSignal(url) \ No newline at end of file diff --git a/src/app/modules/main/browser_section/view.nim b/src/app/modules/main/browser_section/view.nim index cef1b648f3..363debbf80 100644 --- a/src/app/modules/main/browser_section/view.nim +++ b/src/app/modules/main/browser_section/view.nim @@ -19,3 +19,7 @@ QtObject: proc load*(self: View) = self.delegate.viewDidLoad() + + proc openUrl*(self: View, url: string) {.signal.} + proc sendOpenUrlSignal*(self: View, url: string) = + self.openUrl(url) \ No newline at end of file diff --git a/src/app_service/service/contacts/async_tasks.nim b/src/app_service/service/contacts/async_tasks.nim index 38c9b523c9..fd6881304a 100644 --- a/src/app_service/service/contacts/async_tasks.nim +++ b/src/app_service/service/contacts/async_tasks.nim @@ -14,6 +14,7 @@ type value: string uuid: string chainId: int + reason: string const lookupContactTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[LookupContactTaskArg](argEncoded) @@ -35,7 +36,8 @@ const lookupContactTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let output = %*{ "id": pubkey, "address": address, - "uuid": arg.uuid + "uuid": arg.uuid, + "reason": arg.reason } arg.finish(output) diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index ca0a4a49e8..39b69dc6a8 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -107,6 +107,9 @@ proc mainProc() = # Register events objects let dockShowAppEvent = newStatusDockShowAppEventObject(singletonInstance.engine) let osThemeEvent = newStatusOSThemeEventObject(singletonInstance.engine) + let urlSchemeEvent = newStatusUrlSchemeEventObject() + + statusFoundation.initUrlSchemeManager(urlSchemeEvent) if not defined(macosx): app.icon(app.applicationDirPath & statusAppIconPath) @@ -125,6 +128,7 @@ proc mainProc() = app.installEventFilter(dockShowAppEvent) app.installEventFilter(osThemeEvent) + app.installEventFilter(urlSchemeEvent) defer: info "shutting down..."