Ask for server URL before showing sign in box
Contact the server's .well-known API before anything to get available login flows instead of blindly assuming it will be m.login.password, and to get the server's real URL instead of requiring users to remember that e.g. it's "chat.privacytools.io" and not just "privacytools.io" despite user IDs making it look like so. The server field will also now remember the last accepted URL.
This commit is contained in:
@@ -5,7 +5,6 @@ import QtQuick.Layouts 1.12
|
||||
|
||||
HFlickableColumnPage {
|
||||
implicitWidth: Math.min(parent.width, theme.controls.box.defaultWidth)
|
||||
implicitHeight: Math.min(parent.height, flickable.contentHeight)
|
||||
|
||||
background: Rectangle {
|
||||
color: theme.controls.box.background
|
||||
|
@@ -18,6 +18,7 @@ HPage {
|
||||
|
||||
|
||||
implicitWidth: theme.controls.box.defaultWidth
|
||||
implicitHeight: contentHeight + implicitHeaderHeight + implicitFooterHeight
|
||||
contentHeight:
|
||||
flickable.contentHeight + flickable.topMargin + flickable.bottomMargin
|
||||
|
||||
|
@@ -4,22 +4,48 @@ import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../../Base"
|
||||
|
||||
HPage {
|
||||
id: page
|
||||
HSwipeView {
|
||||
id: swipeView
|
||||
clip: true
|
||||
interactive: currentIndex !== 0 || signIn.serverUrl
|
||||
onCurrentItemChanged: if (currentIndex === 0) serverBrowser.takeFocus()
|
||||
Component.onCompleted: serverBrowser.takeFocus()
|
||||
|
||||
HTabbedBox {
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(implicitWidth, page.availableWidth)
|
||||
height: Math.min(implicitHeight, page.availableHeight)
|
||||
HPage {
|
||||
id: serverPage
|
||||
|
||||
header: HTabBar {
|
||||
HTabButton { text: qsTr("Sign in") }
|
||||
HTabButton { text: qsTr("Register") }
|
||||
HTabButton { text: qsTr("Reset") }
|
||||
ServerBrowser {
|
||||
id: serverBrowser
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(implicitWidth, serverPage.availableWidth)
|
||||
height: Math.min(implicitHeight, serverPage.availableHeight)
|
||||
onAccepted: swipeView.currentIndex = 1
|
||||
}
|
||||
}
|
||||
|
||||
SignIn {}
|
||||
Register {}
|
||||
Reset {}
|
||||
HPage {
|
||||
id: tabPage
|
||||
|
||||
HTabbedBox {
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(implicitWidth, tabPage.availableWidth)
|
||||
height: Math.min(implicitHeight, tabPage.availableHeight)
|
||||
|
||||
header: HTabBar {
|
||||
HTabButton { text: qsTr("Sign in") }
|
||||
HTabButton { text: qsTr("Register") }
|
||||
HTabButton { text: qsTr("Reset") }
|
||||
}
|
||||
|
||||
SignIn {
|
||||
id: signIn
|
||||
serverUrl: serverBrowser.acceptedUrl
|
||||
displayUrl: serverBrowser.acceptedUserUrl
|
||||
onExitRequested: swipeView.currentIndex = 0
|
||||
}
|
||||
|
||||
Register {}
|
||||
Reset {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
155
src/gui/Pages/AddAccount/ServerBrowser.qml
Normal file
155
src/gui/Pages/AddAccount/ServerBrowser.qml
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../../Base"
|
||||
import "../../Base/Buttons"
|
||||
import "../../PythonBridge"
|
||||
|
||||
HBox {
|
||||
id: page
|
||||
|
||||
property string acceptedUserUrl: ""
|
||||
property string acceptedUrl: ""
|
||||
property var loginFlows: ["m.login.password"]
|
||||
|
||||
property string saveName: "serverBrowser"
|
||||
property var saveProperties: ["acceptedUserUrl"]
|
||||
property Future connectFuture: null
|
||||
|
||||
signal accepted()
|
||||
|
||||
function takeFocus() { serverField.item.forceActiveFocus() }
|
||||
|
||||
function connect() {
|
||||
if (connectFuture) connectFuture.cancel()
|
||||
connectTimeout.restart()
|
||||
|
||||
const args = [serverField.item.cleanText]
|
||||
|
||||
connectFuture = py.callCoro("server_info", args, ([url, flows]) => {
|
||||
connectTimeout.stop()
|
||||
errorMessage.text = ""
|
||||
|
||||
connectFuture = null
|
||||
acceptedUrl = url
|
||||
acceptedUserUrl = args[0]
|
||||
loginFlows = flows
|
||||
accepted()
|
||||
|
||||
}, (type, args, error, traceback, uuid) => {
|
||||
connectTimeout.stop()
|
||||
connectFuture = null
|
||||
|
||||
let text = qsTr("Unexpected error: %1 [%2]").arg(type).arg(args)
|
||||
|
||||
type === "MatrixNotFound" ?
|
||||
text = qsTr("Invalid homeserver address") :
|
||||
|
||||
type.startsWith("Matrix") ?
|
||||
text = qsTr("Error contacting server: %1").arg(type) :
|
||||
|
||||
utils.showError(type, traceback, uuid)
|
||||
|
||||
errorMessage.text = text
|
||||
})
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
if (page.connectFuture) return
|
||||
|
||||
connectTimeout.stop()
|
||||
connectFuture.cancel()
|
||||
connectFuture = null
|
||||
}
|
||||
|
||||
|
||||
footer: AutoDirectionLayout {
|
||||
ApplyButton {
|
||||
id: applyButton
|
||||
enabled: serverField.item.cleanText && ! serverField.item.error
|
||||
text: qsTr("Connect")
|
||||
icon.name: "server-connect"
|
||||
loading: page.connectFuture !== null
|
||||
disableWhileLoading: false
|
||||
onClicked: page.connect()
|
||||
}
|
||||
|
||||
CancelButton {
|
||||
id: cancelButton
|
||||
enabled: page.connectFuture !== null
|
||||
onClicked: page.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardAccept: if (applyButton.enabled) page.connect()
|
||||
onKeyboardCancel: if (cancelButton.enabled) page.cancel()
|
||||
onAccepted: window.saveState(this)
|
||||
|
||||
Timer {
|
||||
id: connectTimeout
|
||||
interval: 30 * 1000
|
||||
onTriggered: {
|
||||
errorMessage.text =
|
||||
serverField.knownServerChosen ?
|
||||
|
||||
qsTr("This homeserver seems unavailable. Verify your inter" +
|
||||
"net connection or try again in a few minutes.") :
|
||||
|
||||
qsTr("This homeserver seems unavailable. Verify the " +
|
||||
"entered address, your internet connection or try " +
|
||||
"again in a few minutes.")
|
||||
}
|
||||
}
|
||||
|
||||
HLabeledItem {
|
||||
id: serverField
|
||||
|
||||
// 2019-11-11 https://www.hello-matrix.net/public_servers.php
|
||||
readonly property var knownServers: [
|
||||
"https://matrix.org",
|
||||
"https://chat.weho.st",
|
||||
"https://tchncs.de",
|
||||
"https://chat.privacytools.io",
|
||||
"https://hackerspaces.be",
|
||||
"https://matrix.allmende.io",
|
||||
"https://feneas.org",
|
||||
"https://junta.pl",
|
||||
"https://perthchat.org",
|
||||
"https://matrix.tedomum.net",
|
||||
"https://converser.eu",
|
||||
"https://ru-matrix.org",
|
||||
"https://matrix.sibnsk.net",
|
||||
"https://alternanet.fr",
|
||||
]
|
||||
|
||||
readonly property bool knownServerChosen:
|
||||
knownServers.includes(item.cleanText)
|
||||
|
||||
label.text: qsTr("Homeserver:")
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
HTextField {
|
||||
readonly property string cleanText: text.toLowerCase().trim()
|
||||
|
||||
width: parent.width
|
||||
error: ! /https?:\/\/.+/.test(cleanText)
|
||||
defaultText:
|
||||
window.getState(page, "acceptedUserUrl", "https://matrix.org")
|
||||
}
|
||||
}
|
||||
|
||||
HLabel {
|
||||
id: errorMessage
|
||||
wrapMode: HLabel.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: theme.colors.errorText
|
||||
|
||||
visible: Layout.maximumHeight > 0
|
||||
Layout.maximumHeight: text ? implicitHeight : 0
|
||||
Behavior on Layout.maximumHeight { HNumberAnimation {} }
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
@@ -8,33 +8,42 @@ import "../../Base/Buttons"
|
||||
HFlickableColumnPage {
|
||||
id: page
|
||||
|
||||
enum Security { Insecure, LocalHttp, Secure }
|
||||
|
||||
property string serverUrl
|
||||
property string displayUrl: serverUrl
|
||||
property var loginFuture: null
|
||||
|
||||
property string signInWith: "username"
|
||||
signal exitRequested()
|
||||
|
||||
readonly property bool canSignIn:
|
||||
serverField.item.text.trim() && idField.item.text.trim() &&
|
||||
passwordField.item.text && ! serverField.item.error
|
||||
readonly property int security:
|
||||
serverUrl.startsWith("https://") ?
|
||||
SignIn.Security.Secure :
|
||||
|
||||
["//localhost", "//127.0.0.1", "//:1"].includes(
|
||||
serverUrl.split(":")[1],
|
||||
) ?
|
||||
SignIn.Security.LocalHttp :
|
||||
|
||||
SignIn.Security.Insecure
|
||||
|
||||
function takeFocus() { idField.item.forceActiveFocus() }
|
||||
|
||||
function signIn() {
|
||||
if (page.loginFuture) page.loginFuture.cancel()
|
||||
|
||||
signInTimeout.restart()
|
||||
|
||||
errorMessage.text = ""
|
||||
|
||||
const args = [
|
||||
idField.item.text.trim(), passwordField.item.text,
|
||||
undefined, serverField.item.text.trim(),
|
||||
undefined, page.serverUrl,
|
||||
]
|
||||
|
||||
page.loginFuture = py.callCoro("login_client", args, userId => {
|
||||
signInTimeout.stop()
|
||||
errorMessage.text = ""
|
||||
page.loginFuture = null
|
||||
|
||||
print(rememberAccount.checked)
|
||||
py.callCoro(
|
||||
rememberAccount.checked ?
|
||||
"saved_accounts.add": "saved_accounts.delete",
|
||||
@@ -48,7 +57,6 @@ HFlickableColumnPage {
|
||||
|
||||
}, (type, args, error, traceback, uuid) => {
|
||||
page.loginFuture = null
|
||||
signInTimeout.stop()
|
||||
|
||||
let txt = qsTr(
|
||||
"Invalid request, login type or unknown error: %1",
|
||||
@@ -67,17 +75,23 @@ HFlickableColumnPage {
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
if (! page.loginFuture) return
|
||||
if (! page.loginFuture) {
|
||||
page.exitRequested()
|
||||
return
|
||||
}
|
||||
|
||||
signInTimeout.stop()
|
||||
page.loginFuture.cancel()
|
||||
page.loginFuture = null
|
||||
}
|
||||
|
||||
|
||||
flickable.topMargin: theme.spacing * 1.5
|
||||
flickable.bottomMargin: flickable.topMargin
|
||||
|
||||
footer: AutoDirectionLayout {
|
||||
ApplyButton {
|
||||
enabled: page.canSignIn
|
||||
id: applyButton
|
||||
enabled: idField.item.text.trim() && passwordField.item.text
|
||||
text: qsTr("Sign in")
|
||||
icon.name: "sign-in"
|
||||
loading: page.loginFuture !== null
|
||||
@@ -90,54 +104,39 @@ HFlickableColumnPage {
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardAccept: page.signIn()
|
||||
onKeyboardAccept: if (applyButton.enabled) page.signIn()
|
||||
onKeyboardCancel: page.cancel()
|
||||
|
||||
Timer {
|
||||
id: signInTimeout
|
||||
interval: 30 * 1000
|
||||
onTriggered: {
|
||||
errorMessage.text =
|
||||
serverField.knownServerChosen ?
|
||||
HButton {
|
||||
icon.name: "sign-in-" + (
|
||||
page.security === SignIn.Security.Insecure ? "insecure" :
|
||||
page.security === SignIn.Security.LocalHttp ? "local-http" :
|
||||
"secure"
|
||||
)
|
||||
|
||||
qsTr("This server seems unavailable. Verify your inter" +
|
||||
"net connection or try again in a few minutes.") :
|
||||
icon.color:
|
||||
page.security === SignIn.Security.Insecure ?
|
||||
theme.colors.negativeBackground :
|
||||
|
||||
qsTr("This server seems unavailable. Verify the " +
|
||||
"entered URL, your internet connection or try " +
|
||||
"again in a few minutes.")
|
||||
}
|
||||
}
|
||||
page.security === SignIn.Security.LocalHttp ?
|
||||
theme.colors.middleBackground :
|
||||
|
||||
HRowLayout {
|
||||
visible: false // TODO
|
||||
spacing: theme.spacing * 1.25
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
theme.colors.positiveBackground
|
||||
|
||||
Layout.topMargin: theme.spacing
|
||||
Layout.bottomMargin: Layout.topMargin
|
||||
text:
|
||||
page.security === SignIn.Security.Insecure ?
|
||||
page.serverUrl :
|
||||
page.displayUrl.replace(/^(https?:\/\/)?(www\.)?/, "")
|
||||
|
||||
Repeater {
|
||||
model: ["username", "email", "phone"]
|
||||
onClicked: page.exitRequested()
|
||||
|
||||
HButton {
|
||||
icon.name: modelData
|
||||
circle: true
|
||||
checked: page.signInWith === modelData
|
||||
enabled: modelData === "username"
|
||||
autoExclusive: true
|
||||
onClicked: page.signInWith = modelData
|
||||
}
|
||||
}
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.maximumWidth: parent.width
|
||||
}
|
||||
|
||||
HLabeledItem {
|
||||
id: idField
|
||||
label.text: qsTr(
|
||||
page.signInWith === "email" ? "Email:" :
|
||||
page.signInWith === "phone" ? "Phone:" :
|
||||
"Username:"
|
||||
)
|
||||
label.text: qsTr("Username:")
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -158,43 +157,6 @@ HFlickableColumnPage {
|
||||
}
|
||||
}
|
||||
|
||||
HLabeledItem {
|
||||
id: serverField
|
||||
|
||||
// 2019-11-11 https://www.hello-matrix.net/public_servers.php
|
||||
readonly property var knownServers: [
|
||||
"https://matrix.org",
|
||||
"https://chat.weho.st",
|
||||
"https://tchncs.de",
|
||||
"https://chat.privacytools.io",
|
||||
"https://hackerspaces.be",
|
||||
"https://matrix.allmende.io",
|
||||
"https://feneas.org",
|
||||
"https://junta.pl",
|
||||
"https://perthchat.org",
|
||||
"https://matrix.tedomum.net",
|
||||
"https://converser.eu",
|
||||
"https://ru-matrix.org",
|
||||
"https://matrix.sibnsk.net",
|
||||
"https://alternanet.fr",
|
||||
]
|
||||
|
||||
readonly property bool knownServerChosen:
|
||||
knownServers.includes(item.cleanText)
|
||||
|
||||
label.text: qsTr("Homeserver:")
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
HTextField {
|
||||
readonly property string cleanText: text.toLowerCase().trim()
|
||||
|
||||
width: parent.width
|
||||
text: "https://matrix.org"
|
||||
error: ! /.+:\/\/.+/.test(cleanText)
|
||||
}
|
||||
}
|
||||
|
||||
HCheckBox {
|
||||
id: rememberAccount
|
||||
checked: true
|
||||
@@ -206,7 +168,6 @@ HFlickableColumnPage {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: theme.spacing / 2
|
||||
Layout.bottomMargin: Layout.topMargin
|
||||
}
|
||||
|
||||
HLabel {
|
||||
|
@@ -440,6 +440,7 @@ QtObject {
|
||||
|
||||
flickable.maximumFlickVelocity = 5000
|
||||
|
||||
|
||||
flickable.flickDeceleration = Math.max(
|
||||
goFaster ? normalDecel : -Infinity,
|
||||
Math.abs(normalDecel * magicNumber * pages),
|
||||
|
Reference in New Issue
Block a user