fix(@desktop/general): Fix clicking deep links in chat
Clicking any deep-link in chat: /u, /c, /cc does not open browser but executes instantly Fix: #6302
This commit is contained in:
parent
3b3b737956
commit
6d2a2e6e03
|
@ -202,6 +202,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
|
||||||
result.mainModule = main_module.newModule[AppController](
|
result.mainModule = main_module.newModule[AppController](
|
||||||
result,
|
result,
|
||||||
statusFoundation.events,
|
statusFoundation.events,
|
||||||
|
statusFoundation.urlsManager,
|
||||||
result.keychainService,
|
result.keychainService,
|
||||||
result.accountsService,
|
result.accountsService,
|
||||||
result.chatService,
|
result.chatService,
|
||||||
|
|
|
@ -6,15 +6,18 @@ import ../../global/app_signals
|
||||||
logScope:
|
logScope:
|
||||||
topics = "urls-manager"
|
topics = "urls-manager"
|
||||||
|
|
||||||
const UriFormatUserProfile = "status-im://u/"
|
const StatusInternalLink = "status-im"
|
||||||
|
const StatusExternalLink = "join.status.im"
|
||||||
|
|
||||||
const UriFormatCommunity = "status-im://c/"
|
const UriFormatUserProfile = StatusInternalLink & "://u/"
|
||||||
|
|
||||||
const UriFormatCommunityChannel = "status-im://cc/"
|
const UriFormatCommunity = StatusInternalLink & "://c/"
|
||||||
|
|
||||||
const UriFormatGroupChat = "status-im://g/"
|
const UriFormatCommunityChannel = StatusInternalLink & "://cc/"
|
||||||
|
|
||||||
const UriFormatBrowser = "status-im://b/"
|
# enable after MVP
|
||||||
|
#const UriFormatGroupChat = StatusInternalLink & "://g/"
|
||||||
|
#const UriFormatBrowser = StatusInternalLink & "://b/"
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type UrlsManager* = ref object of QObject
|
type UrlsManager* = ref object of QObject
|
||||||
|
@ -71,7 +74,6 @@ QtObject:
|
||||||
#elif url.startsWith(UriFormatBrowser):
|
#elif url.startsWith(UriFormatBrowser):
|
||||||
# data.action = StatusUrlAction.OpenLinkInBrowser
|
# data.action = StatusUrlAction.OpenLinkInBrowser
|
||||||
# data.url = url[UriFormatBrowser.len .. url.len-1]
|
# data.url = url[UriFormatBrowser.len .. url.len-1]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
info "Unsupported deep link structure: ", url
|
info "Unsupported deep link structure: ", url
|
||||||
return
|
return
|
||||||
|
@ -83,3 +85,10 @@ QtObject:
|
||||||
if self.protocolUriOnStart != "":
|
if self.protocolUriOnStart != "":
|
||||||
self.onUrlActivated(self.protocolUriOnStart)
|
self.onUrlActivated(self.protocolUriOnStart)
|
||||||
self.protocolUriOnStart = ""
|
self.protocolUriOnStart = ""
|
||||||
|
|
||||||
|
proc convertExternalLinkToInternal*(self: UrlsManager, statusDeepLink: string): string =
|
||||||
|
let idx = find(statusDeepLink, StatusExternalLink)
|
||||||
|
result = statusDeepLink
|
||||||
|
if idx != -1:
|
||||||
|
result = statusDeepLink[idx + StatusExternalLink.len .. ^1]
|
||||||
|
result = StatusInternalLink & ":/" & result
|
|
@ -15,7 +15,7 @@ type StatusFoundation* = ref object
|
||||||
fleetConfiguration*: FleetConfiguration
|
fleetConfiguration*: FleetConfiguration
|
||||||
threadpool*: ThreadPool
|
threadpool*: ThreadPool
|
||||||
signalsManager*: SignalsManager
|
signalsManager*: SignalsManager
|
||||||
urlsManager: UrlsManager
|
urlsManager*: UrlsManager
|
||||||
|
|
||||||
proc newStatusFoundation*(fleetConfig: string): StatusFoundation =
|
proc newStatusFoundation*(fleetConfig: string): StatusFoundation =
|
||||||
result = StatusFoundation()
|
result = StatusFoundation()
|
||||||
|
|
|
@ -226,6 +226,9 @@ method runAuthenticationPopup*(self: AccessInterface, keyUid: string, bip44Path:
|
||||||
method onMyRequestAdded*(self: AccessInterface) {.base.} =
|
method onMyRequestAdded*(self: AccessInterface) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
|
method activateStatusDeepLink*(self: AccessInterface, statusDeepLink: string) {.base.} =
|
||||||
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
# This way (using concepts) is used only for the modules managed by AppController
|
# This way (using concepts) is used only for the modules managed by AppController
|
||||||
type
|
type
|
||||||
DelegateInterface* = concept c
|
DelegateInterface* = concept c
|
||||||
|
|
|
@ -59,6 +59,7 @@ import ../../../app_service/common/social_links
|
||||||
|
|
||||||
import ../../core/notifications/details
|
import ../../core/notifications/details
|
||||||
import ../../core/eventemitter
|
import ../../core/eventemitter
|
||||||
|
import ../../core/custom_urls/urls_manager
|
||||||
|
|
||||||
export io_interface
|
export io_interface
|
||||||
|
|
||||||
|
@ -75,6 +76,7 @@ type
|
||||||
controller: Controller
|
controller: Controller
|
||||||
channelGroupModules: OrderedTable[string, chat_section_module.AccessInterface]
|
channelGroupModules: OrderedTable[string, chat_section_module.AccessInterface]
|
||||||
events: EventEmitter
|
events: EventEmitter
|
||||||
|
urlsManager: UrlsManager
|
||||||
keycardService: keycard_service.Service
|
keycardService: keycard_service.Service
|
||||||
settingsService: settings_service.Service
|
settingsService: settings_service.Service
|
||||||
privacyService: privacy_service.Service
|
privacyService: privacy_service.Service
|
||||||
|
@ -100,6 +102,7 @@ method calculateProfileSectionHasNotification*[T](self: Module[T]): bool
|
||||||
proc newModule*[T](
|
proc newModule*[T](
|
||||||
delegate: T,
|
delegate: T,
|
||||||
events: EventEmitter,
|
events: EventEmitter,
|
||||||
|
urlsManager: UrlsManager,
|
||||||
keychainService: keychain_service.Service,
|
keychainService: keychain_service.Service,
|
||||||
accountsService: accounts_service.Service,
|
accountsService: accounts_service.Service,
|
||||||
chatService: chat_service.Service,
|
chatService: chat_service.Service,
|
||||||
|
@ -153,6 +156,7 @@ proc newModule*[T](
|
||||||
result.moduleLoaded = false
|
result.moduleLoaded = false
|
||||||
|
|
||||||
result.events = events
|
result.events = events
|
||||||
|
result.urlsManager = urlsManager
|
||||||
result.keycardService = keycardService
|
result.keycardService = keycardService
|
||||||
result.settingsService = settingsService
|
result.settingsService = settingsService
|
||||||
result.privacyService = privacyService
|
result.privacyService = privacyService
|
||||||
|
@ -970,3 +974,7 @@ method runAuthenticationPopup*[T](self: Module[T], keyUid: string, bip44Path: st
|
||||||
|
|
||||||
method onDisplayKeycardSharedModuleFlow*[T](self: Module[T]) =
|
method onDisplayKeycardSharedModuleFlow*[T](self: Module[T]) =
|
||||||
self.view.emitDisplayKeycardSharedModuleFlow()
|
self.view.emitDisplayKeycardSharedModuleFlow()
|
||||||
|
|
||||||
|
method activateStatusDeepLink*[T](self: Module[T], statusDeepLink: string) =
|
||||||
|
let linkToActivate = self.urlsManager.convertExternalLinkToInternal(statusDeepLink)
|
||||||
|
self.urlsManager.onUrlActivated(linkToActivate)
|
||||||
|
|
|
@ -227,6 +227,9 @@ QtObject:
|
||||||
QtProperty[QVariant] keycardSharedModule:
|
QtProperty[QVariant] keycardSharedModule:
|
||||||
read = getKeycardSharedModule
|
read = getKeycardSharedModule
|
||||||
|
|
||||||
|
proc activateStatusDeepLink*(self: View, statusDeepLink: string) {.slot.} =
|
||||||
|
self.delegate.activateStatusDeepLink(statusDeepLink)
|
||||||
|
|
||||||
proc displayKeycardSharedModuleFlow*(self: View) {.signal.}
|
proc displayKeycardSharedModuleFlow*(self: View) {.signal.}
|
||||||
proc emitDisplayKeycardSharedModuleFlow*(self: View) =
|
proc emitDisplayKeycardSharedModuleFlow*(self: View) =
|
||||||
self.displayKeycardSharedModuleFlow()
|
self.displayKeycardSharedModuleFlow()
|
||||||
|
|
|
@ -104,8 +104,13 @@ proc mainProc() =
|
||||||
# We increase js stack size to prevent "Maximum call stack size exceeded" on UI loading.
|
# We increase js stack size to prevent "Maximum call stack size exceeded" on UI loading.
|
||||||
os.putEnv("QV4_JS_MAX_STACK_SIZE", "10485760")
|
os.putEnv("QV4_JS_MAX_STACK_SIZE", "10485760")
|
||||||
os.putEnv("QT_QUICK_CONTROLS_HOVER_ENABLED", "1")
|
os.putEnv("QT_QUICK_CONTROLS_HOVER_ENABLED", "1")
|
||||||
let appController = newAppController(statusFoundation)
|
|
||||||
let singleInstance = newSingleInstance($toMD5(DATADIR), openUri)
|
let singleInstance = newSingleInstance($toMD5(DATADIR), openUri)
|
||||||
|
let urlSchemeEvent = newStatusUrlSchemeEventObject()
|
||||||
|
# init url manager before app controller
|
||||||
|
statusFoundation.initUrlSchemeManager(urlSchemeEvent, singleInstance, openUri)
|
||||||
|
|
||||||
|
let appController = newAppController(statusFoundation)
|
||||||
let networkAccessFactory = newQNetworkAccessManagerFactory(TMPDIR & "netcache")
|
let networkAccessFactory = newQNetworkAccessManagerFactory(TMPDIR & "netcache")
|
||||||
|
|
||||||
let isProductionQVariant = newQVariant(if defined(production): true else: false)
|
let isProductionQVariant = newQVariant(if defined(production): true else: false)
|
||||||
|
@ -116,9 +121,6 @@ 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, singleInstance, openUri)
|
|
||||||
|
|
||||||
if not defined(macosx):
|
if not defined(macosx):
|
||||||
app.icon(app.applicationDirPath & statusAppIconPath)
|
app.icon(app.applicationDirPath & statusAppIconPath)
|
||||||
|
|
|
@ -50,6 +50,10 @@ QtObject {
|
||||||
mainModule.setActiveSectionById(communityId);
|
mainModule.setActiveSectionById(communityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activateStatusDeepLink(link) {
|
||||||
|
mainModuleInst.activateStatusDeepLink(link)
|
||||||
|
}
|
||||||
|
|
||||||
function setObservedCommunity(communityId) {
|
function setObservedCommunity(communityId) {
|
||||||
communitiesModuleInst.setObservedCommunity(communityId);
|
communitiesModuleInst.setObservedCommunity(communityId);
|
||||||
}
|
}
|
||||||
|
@ -394,29 +398,20 @@ QtObject {
|
||||||
callback: null
|
callback: null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link to send a direct message
|
// User profile
|
||||||
let index = link.indexOf("/u/")
|
// There is invitation bubble only for /c/ link for now
|
||||||
if (index === -1) {
|
/*let index = link.indexOf("/u/")
|
||||||
// Try /p/ as well
|
|
||||||
index = link.indexOf("/p/")
|
|
||||||
}
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
const pk = link.substring(index + 3)
|
//const pk = link.substring(index + 3)
|
||||||
result.title = qsTr("Start a 1-on-1 chat with %1")
|
result.title = qsTr("Display user profile")
|
||||||
.arg(Utils.isChatKey(pk) ? globalUtils.generateAlias(pk) : ("@" + Utils.removeStatusEns(pk)))
|
|
||||||
result.callback = function () {
|
result.callback = function () {
|
||||||
if (Utils.isChatKey(pk)) {
|
mainModuleInst.activateStatusDeepLink(link)
|
||||||
chatCommunitySectionModule.createOneToOneChat("", pk, "")
|
|
||||||
} else {
|
|
||||||
// Not Refactored Yet
|
|
||||||
// chatsModel.channelView.joinWithENS(pk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Community
|
// Community
|
||||||
index = link.lastIndexOf("/c/")
|
let index = link.lastIndexOf("/c/")
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
const communityId = link.substring(index + 3)
|
const communityId = link.substring(index + 3)
|
||||||
|
|
||||||
|
@ -448,42 +443,16 @@ QtObject {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group chat
|
|
||||||
index = link.lastIndexOf("/g/")
|
|
||||||
if (index > -1) {
|
|
||||||
let indexAdminPk = link.lastIndexOf("a=")
|
|
||||||
let indexChatName = link.lastIndexOf("a1=")
|
|
||||||
let indexChatId = link.lastIndexOf("a2=")
|
|
||||||
const pubKey = link.substring(indexAdminPk + 2, indexChatName - 1)
|
|
||||||
const chatName = link.substring(indexChatName + 3, indexChatId - 1)
|
|
||||||
const chatId = link.substring(indexChatId + 3, link.length)
|
|
||||||
result.title = qsTr("Join the %1 group chat").arg(chatName)
|
|
||||||
result.callback = function () {
|
|
||||||
// Not Refactored Yet
|
|
||||||
// chatsModel.groups.joinGroupChatFromInvitation(chatName, chatId, pubKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not Refactored Yet (when we get to this we will most likely remove it, since other approach will be used)
|
function isStatusDeepLink(link) {
|
||||||
// // Public chat
|
return link.includes(Constants.deepLinkPrefix) || link.includes(Constants.joinStatusLink)
|
||||||
// // This needs to be the last check because it is as VERY loose check
|
|
||||||
// index = link.lastIndexOf("/")
|
|
||||||
// if (index > -1) {
|
|
||||||
// const chatId = link.substring(index + 1)
|
|
||||||
// result.title = qsTr("Join the %1 public channel").arg(chatId)
|
|
||||||
// result.callback = function () {
|
|
||||||
// chatsModel.channelView.joinPublicChat(chatId);
|
|
||||||
// }
|
|
||||||
// return result
|
|
||||||
// }
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLinkDataForStatusLinks(link) {
|
function getLinkDataForStatusLinks(link) {
|
||||||
if (!link.includes(Constants.deepLinkPrefix) && !link.includes(Constants.joinStatusLink)) {
|
if (!isStatusDeepLink(link)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,8 +120,8 @@ Column {
|
||||||
if (!data.fetching && data.communityId) {
|
if (!data.fetching && data.communityId) {
|
||||||
return linkMessageLoader.sourceComponent = invitationBubble
|
return linkMessageLoader.sourceComponent = invitationBubble
|
||||||
}
|
}
|
||||||
|
// do not show unfurledLinkComponent
|
||||||
return linkMessageLoader.sourceComponent = unfurledLinkComponent
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,13 +144,13 @@ Column {
|
||||||
// Reset the height in case we set it to 0 below. See note below
|
// Reset the height in case we set it to 0 below. See note below
|
||||||
// for more information
|
// for more information
|
||||||
this.height = undefined
|
this.height = undefined
|
||||||
|
|
||||||
const linkHostname = Utils.getHostname(link)
|
const linkHostname = Utils.getHostname(link)
|
||||||
if (!localAccountSensitiveSettings.whitelistedUnfurlingSites) {
|
if (!localAccountSensitiveSettings.whitelistedUnfurlingSites) {
|
||||||
localAccountSensitiveSettings.whitelistedUnfurlingSites = {}
|
localAccountSensitiveSettings.whitelistedUnfurlingSites = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const whitelistHosts = Object.keys(localAccountSensitiveSettings.whitelistedUnfurlingSites)
|
const whitelistHosts = Object.keys(localAccountSensitiveSettings.whitelistedUnfurlingSites)
|
||||||
|
|
||||||
const linkExists = whitelistHosts.some(hostname => linkHostname.endsWith(hostname))
|
const linkExists = whitelistHosts.some(hostname => linkHostname.endsWith(hostname))
|
||||||
|
|
||||||
const linkWhiteListed = linkExists && whitelistHosts.some(hostname =>
|
const linkWhiteListed = linkExists && whitelistHosts.some(hostname =>
|
||||||
|
@ -181,7 +181,8 @@ Column {
|
||||||
return invitationBubble
|
return invitationBubble
|
||||||
}
|
}
|
||||||
|
|
||||||
return unfurledLinkComponent
|
// do not show unfurledLinkComponent
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
linkFetchConnections.enabled = true
|
linkFetchConnections.enabled = true
|
||||||
|
@ -316,7 +317,6 @@ Column {
|
||||||
if (!!linkData.callback) {
|
if (!!linkData.callback) {
|
||||||
return linkData.callback()
|
return linkData.callback()
|
||||||
}
|
}
|
||||||
|
|
||||||
Global.openLink(linkData.address)
|
Global.openLink(linkData.address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -518,12 +518,13 @@ Loader {
|
||||||
if (link.startsWith('//')) {
|
if (link.startsWith('//')) {
|
||||||
const pubkey = link.replace("//", "");
|
const pubkey = link.replace("//", "");
|
||||||
Global.openProfilePopup(pubkey)
|
Global.openProfilePopup(pubkey)
|
||||||
return;
|
return
|
||||||
}
|
} else if (link.startsWith('#')) {
|
||||||
|
|
||||||
if (link.startsWith('#')) {
|
|
||||||
rootStore.chatCommunitySectionModule.switchToChannel(link.replace("#", ""))
|
rootStore.chatCommunitySectionModule.switchToChannel(link.replace("#", ""))
|
||||||
return;
|
return
|
||||||
|
} else if (rootStore.isStatusDeepLink(link)) {
|
||||||
|
rootStore.activateStatusDeepLink(link)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Global.openLink(link)
|
Global.openLink(link)
|
||||||
|
|
|
@ -618,7 +618,7 @@ QtObject {
|
||||||
|
|
||||||
readonly property int repeatHeaderInterval: 2
|
readonly property int repeatHeaderInterval: 2
|
||||||
|
|
||||||
readonly property string deepLinkPrefix: 'statusim://'
|
readonly property string deepLinkPrefix: 'status-im://'
|
||||||
readonly property string joinStatusLink: 'join.status.im'
|
readonly property string joinStatusLink: 'join.status.im'
|
||||||
readonly property string communityLinkPrefix: 'https://join.status.im/c/'
|
readonly property string communityLinkPrefix: 'https://join.status.im/c/'
|
||||||
readonly property string userLinkPrefix: 'https://join.status.im/u/'
|
readonly property string userLinkPrefix: 'https://join.status.im/u/'
|
||||||
|
|
Loading…
Reference in New Issue