feat(desktop/general): app support for status-im:// URIs - mac
Fixes #3375
This commit is contained in:
parent
7c74a0942d
commit
698374b91c
|
@ -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>
|
||||||
|
|
13
Info.plist
13
Info.plist
|
@ -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>
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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"
|
|
@ -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")
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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..."
|
||||||
|
|
Loading…
Reference in New Issue