feat(desktop/general): app support for status-im:// URIs - mac

Fixes #3375
This commit is contained in:
Sale Djenic 2022-03-17 17:24:50 +01:00 committed by saledjenic
parent 7c74a0942d
commit 698374b91c
11 changed files with 209 additions and 6 deletions

View File

@ -16,6 +16,19 @@
<string>StatusDev</string> <string>StatusDev</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>status.im.customurl</string>
<key>CFBundleURLSchemes</key>
<array>
<string>status-im</string>
</array>
</dict>
</array>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.0</string> <string>1.0.0</string>
<key>IFMajorVersion</key> <key>IFMajorVersion</key>

View File

@ -16,6 +16,19 @@
<string>Status</string> <string>Status</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>status.im.customurl</string>
<key>CFBundleURLSchemes</key>
<array>
<string>status-im</string>
</array>
</dict>
</array>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.0</string> <string>1.0.0</string>
<key>IFMajorVersion</key> <key>IFMajorVersion</key>

View File

@ -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 // 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 <QString> #include <QString>
const QString CHAT_SECTION_ID = "chat";
const QString CHAT_SECTION_NAME = "Chat"; const QString CHAT_SECTION_NAME = "Chat";
const QString CHAT_SECTION_ICON = "chat"; const QString CHAT_SECTION_ICON = "chat";

View File

@ -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)

View File

@ -1,10 +1,11 @@
import task_runner import NimQml, task_runner
import import
eventemitter, eventemitter,
./fleets/fleet_configuration, ./fleets/fleet_configuration,
./tasks/marathon, ./tasks/marathon,
./tasks/threadpool, ./tasks/threadpool,
./signals/signals_manager ./signals/signals_manager,
./custom_urls/urls_manager
export eventemitter export eventemitter
export marathon, task_runner, signals_manager, fleet_configuration export marathon, task_runner, signals_manager, fleet_configuration
@ -14,6 +15,7 @@ type StatusFoundation* = ref object
fleetConfiguration*: FleetConfiguration fleetConfiguration*: FleetConfiguration
threadpool*: ThreadPool threadpool*: ThreadPool
signalsManager*: SignalsManager signalsManager*: SignalsManager
urlsManager: UrlsManager
proc newStatusFoundation*(fleetConfig: string): StatusFoundation = proc newStatusFoundation*(fleetConfig: string): StatusFoundation =
result = StatusFoundation() result = StatusFoundation()
@ -25,4 +27,8 @@ proc newStatusFoundation*(fleetConfig: string): StatusFoundation =
proc delete*(self: StatusFoundation) = proc delete*(self: StatusFoundation) =
self.threadpool.teardown() self.threadpool.teardown()
self.fleetConfiguration.delete() self.fleetConfiguration.delete()
self.signalsManager.delete() self.signalsManager.delete()
self.urlsManager.delete()
proc initUrlSchemeManager*(self: StatusFoundation, urlSchemeEvent: StatusEvent) =
self.urlsManager = newUrlsManager(self.events, urlSchemeEvent)

View File

@ -21,4 +21,28 @@ type
const SIGNAL_MAKE_SECTION_CHAT_ACTIVE* = "makeSectionChatActive" const SIGNAL_MAKE_SECTION_CHAT_ACTIVE* = "makeSectionChatActive"
## Emmiting this signal will switch the app to passed `sectionId`, after that if `chatId` is set ## 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 ## it will make that chat an active one and at the end if `messageId` is set it will point to
## that message. ## 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"

View File

@ -20,4 +20,7 @@ method providerDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method viewDidLoad*(self: AccessInterface) {.base.} = method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method openUrl*(self: AccessInterface, url: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -92,3 +92,6 @@ method dappsDidLoad*(self: Module) =
method viewDidLoad*(self: Module) = method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad() self.checkIfModuleDidLoad()
method openUrl*(self: Module, url: string) =
self.view.sendOpenUrlSignal(url)

View File

@ -19,3 +19,7 @@ QtObject:
proc load*(self: View) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
proc openUrl*(self: View, url: string) {.signal.}
proc sendOpenUrlSignal*(self: View, url: string) =
self.openUrl(url)

View File

@ -14,6 +14,7 @@ type
value: string value: string
uuid: string uuid: string
chainId: int chainId: int
reason: string
const lookupContactTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = const lookupContactTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[LookupContactTaskArg](argEncoded) let arg = decode[LookupContactTaskArg](argEncoded)
@ -35,7 +36,8 @@ const lookupContactTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let output = %*{ let output = %*{
"id": pubkey, "id": pubkey,
"address": address, "address": address,
"uuid": arg.uuid "uuid": arg.uuid,
"reason": arg.reason
} }
arg.finish(output) arg.finish(output)

View File

@ -107,6 +107,9 @@ proc mainProc() =
# Register events objects # Register events objects
let dockShowAppEvent = newStatusDockShowAppEventObject(singletonInstance.engine) let dockShowAppEvent = newStatusDockShowAppEventObject(singletonInstance.engine)
let osThemeEvent = newStatusOSThemeEventObject(singletonInstance.engine) let osThemeEvent = newStatusOSThemeEventObject(singletonInstance.engine)
let urlSchemeEvent = newStatusUrlSchemeEventObject()
statusFoundation.initUrlSchemeManager(urlSchemeEvent)
if not defined(macosx): if not defined(macosx):
app.icon(app.applicationDirPath & statusAppIconPath) app.icon(app.applicationDirPath & statusAppIconPath)
@ -125,6 +128,7 @@ proc mainProc() =
app.installEventFilter(dockShowAppEvent) app.installEventFilter(dockShowAppEvent)
app.installEventFilter(osThemeEvent) app.installEventFilter(osThemeEvent)
app.installEventFilter(urlSchemeEvent)
defer: defer:
info "shutting down..." info "shutting down..."