634 lines
18 KiB
C++
634 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2019 Adrien Destugues <pulkomandy@pulkomandy.tk>
|
|
*
|
|
* Distributed under terms of the MIT license.
|
|
*/
|
|
|
|
#include "RegisterAccountWindow.h"
|
|
|
|
#include "jabber/BlabberSettings.h"
|
|
|
|
#include "network/BobStore.h"
|
|
|
|
#include "ui/MainWindow.h"
|
|
|
|
#include "PictureView.h"
|
|
|
|
#include <Button.h>
|
|
#include <Country.h>
|
|
#include <private/netservices/Geolocation.h>
|
|
#include <LayoutBuilder.h>
|
|
#include <Rect.h>
|
|
#include <ScrollView.h>
|
|
#include <StringItem.h>
|
|
#include <TextView.h>
|
|
|
|
#include <random>
|
|
|
|
|
|
using BPrivate::Network::BGeolocation;
|
|
|
|
|
|
enum {
|
|
kGeolocalize = 'gloc',
|
|
kShowServerList = 'srvl',
|
|
kSelectServer = 'sels',
|
|
kGetUserInfo = 'gusi',
|
|
kCreateAccount = 'crea',
|
|
};
|
|
|
|
|
|
struct ServerInfo {
|
|
const char* hostname;
|
|
const char* countryCode;
|
|
};
|
|
|
|
static const ServerInfo kServerInfos[] = {
|
|
// TODO find more servers that work, try to cover more countries
|
|
// { "jabber.at", "AT" }, Web registration only (also for jabber.zone)
|
|
// { "chinwag.im", "AU" }, Web registration only (also for jabberzac.org)
|
|
{ "rows.im", "CA" },
|
|
{ "stefgo.net", "CH" },
|
|
{ "jabber.cz", "CZ" },
|
|
{ "jabber.de", "DE" },
|
|
// { "jabberes.org", "ES" }, no registrations
|
|
{ "a3.pm", "FI" },
|
|
{ "jabber.fr", "FR" },
|
|
// { "616.pub", "HK" },
|
|
{ "step.im", "JP" },
|
|
// { "eigenlab.org", "LV" },
|
|
{ "4ept.net", "NL" },
|
|
// { "jabber.sytes24.pl", "PL" },
|
|
// { "xmpp.is", "RO" },
|
|
{ "lsd-25.ru", "RU" },
|
|
{ "xmpp.international", "SC" },
|
|
// { "default.rs", "SR" },
|
|
// { "jix.im", "UK" }, Web registrations only
|
|
{ "jabber.today", "US" },
|
|
};
|
|
|
|
|
|
RegisterAccountWindow::RegisterAccountWindow(BHandler* target __attribute__((unused)))
|
|
: BWindow(BRect(0, 0, 100, 100), "Create an XMPP account", B_TITLED_WINDOW,
|
|
B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE)
|
|
{
|
|
fLayout = new BCardLayout();
|
|
SetLayout(fLayout);
|
|
|
|
// TODO check that we are online, otherwise there's no point in attempting
|
|
// a registration.
|
|
|
|
// Card 0 - Agree to be geolocalized
|
|
// TODO remove this, instead pick a server matching the system locale/language settings
|
|
BGroupView* card0 = new BGroupView(B_VERTICAL);
|
|
AddChild(card0);
|
|
BTextView* agree = new BTextView("agree");
|
|
agree->SetText("In order to find a nearby server, the application will now "
|
|
"attempt to geolocalize you. A list of wifi networks within reach of "
|
|
"your computer will be sent to Mozilla Location Services. The "
|
|
"resulting estimated latitude and longitude will be sent to Geonames "
|
|
"to deduce the country you are in. If you don't want this to happen, "
|
|
"you will have to pick a server yourself.");
|
|
float charSize = agree->StringWidth("W");
|
|
agree->SetExplicitMinSize(BSize(charSize * 30, charSize * 7));
|
|
agree->MakeEditable(false);
|
|
agree->MakeSelectable(false);
|
|
agree->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
|
|
// TODO add hyperlinks to MLS and Geonames
|
|
|
|
BButton* yes = new BButton("yes", "Yes, please", new BMessage(kGeolocalize));
|
|
BButton* no = new BButton("no", "No thanks!", new BMessage(kShowServerList));
|
|
|
|
BLayoutBuilder::Group<>(card0)
|
|
.SetInsets(B_USE_WINDOW_INSETS)
|
|
.Add(agree)
|
|
.AddGlue()
|
|
.AddGroup(B_HORIZONTAL)
|
|
.AddGlue()
|
|
.Add(no)
|
|
.Add(yes)
|
|
.End()
|
|
.End();
|
|
|
|
// Card 1 - Welcome message + server list
|
|
BGroupView* card1 = new BGroupView(B_VERTICAL);
|
|
AddChild(card1);
|
|
|
|
fWelcome = new BTextView("welcome");
|
|
fWelcome->SetText("Welcome to the XMPP network! We have chosen a server for "
|
|
"you, but you can select another one if you prefer to. The server name "
|
|
"will be part of your XMPP identifier, so you may pick one with a "
|
|
"short and catchy name.");
|
|
fWelcome->SetExplicitMinSize(BSize(charSize * 30, charSize * 7));
|
|
fWelcome->MakeEditable(false);
|
|
fWelcome->MakeSelectable(false);
|
|
fWelcome->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
|
|
|
|
fServerBox = new BTextControl("", "", NULL);
|
|
|
|
fServerList = new BListView("server list");
|
|
fServerList->SetSelectionMessage(new BMessage(kSelectServer));
|
|
BScrollView* serverScroll = new BScrollView("server scroll", fServerList, 0, false, true);
|
|
fServerList->SetExplicitMinSize(BSize(B_SIZE_UNSET, charSize * 12));
|
|
|
|
for (auto i: kServerInfos) {
|
|
fServerList->AddItem(new BStringItem(i.hostname));
|
|
}
|
|
|
|
BButton* next = new BButton("Next", "Next", new BMessage(kGetUserInfo));
|
|
|
|
BLayoutBuilder::Group<>(card1)
|
|
.SetInsets(B_USE_WINDOW_INSETS)
|
|
.Add(fWelcome)
|
|
.AddGrid(B_VERTICAL)
|
|
.SetVerticalSpacing(0)
|
|
.Add(fServerBox, 0, 0)
|
|
.Add(serverScroll, 0, 1)
|
|
.End()
|
|
.AddGroup(B_HORIZONTAL)
|
|
.AddGlue()
|
|
.Add(next)
|
|
.End()
|
|
.End();
|
|
|
|
// Card 2 - Waiting for server
|
|
BGroupView* card2 = new BGroupView(B_VERTICAL);
|
|
AddChild(card2);
|
|
|
|
fWaitingMessage = new BStringView("wait", "Please wait, connecting to server…");
|
|
// TODO add a BarberPole or throbber something
|
|
|
|
BLayoutBuilder::Group<>(card2)
|
|
.AddGroup(B_HORIZONTAL)
|
|
.AddGlue()
|
|
.Add(fWaitingMessage)
|
|
.AddGlue()
|
|
.End()
|
|
.End();
|
|
|
|
// Card 3 - Username + password (or whatever the server wants to know)
|
|
BGroupView* card3 = new BGroupView(B_VERTICAL);
|
|
AddChild(card3);
|
|
fRegistrationForm = new BGridView();
|
|
|
|
#if 0
|
|
// TODO Create all well-known fields and hide them by default
|
|
fUsername = new BTextControl("Username", "", NULL); // TODO show @host
|
|
fNickname = new BTextControl("Nickname", "", NULL);
|
|
fPassword = new BTextControl("Password", "", NULL); // TODO hide typing
|
|
fFirstName = new BTextControl("First name", "", NULL);
|
|
fLastName = new BTextControl("Last name", "", NULL);
|
|
fEmail = new BTextControl("E-mail address", "", NULL);
|
|
fPostalAddress = new BTextControl("Postal address", "", NULL);
|
|
fCity = new BTextControl("City", "", NULL);
|
|
fState = new BTextControl("State", "", NULL);
|
|
fZip = new BTextControl("ZIP Code", "", NULL);
|
|
fPhoneNumber = new BTextControl("Phone number", "", NULL);
|
|
fUrl = new BTextControl("Homepage", "", NULL);
|
|
fDate = new BTextControl("Date", "", NULL);
|
|
fMisc = new BTextControl("Misc.", "", NULL);
|
|
fExtra = new BTextControl("Extra", "", NULL);
|
|
#endif
|
|
|
|
BButton* back = new BButton("Previous", "Previous", new BMessage(kShowServerList));
|
|
BButton* create = new BButton("create", "Register", new BMessage(kCreateAccount));
|
|
|
|
BLayoutBuilder::Group<>(card3)
|
|
.SetInsets(B_USE_WINDOW_SPACING)
|
|
.Add(fRegistrationForm)
|
|
.AddGroup(B_HORIZONTAL)
|
|
.Add(back)
|
|
.AddGlue()
|
|
.Add(create)
|
|
.End()
|
|
.End();
|
|
|
|
fLayout->SetVisibleItem((int32)0);
|
|
}
|
|
|
|
|
|
void
|
|
RegisterAccountWindow::MessageReceived(BMessage* message)
|
|
{
|
|
switch (message->what) {
|
|
case kGeolocalize:
|
|
{
|
|
#if 0 // BGeolocation::Country not yet part of Haiku
|
|
float lat, lon;
|
|
BGeolocation geolocation;
|
|
BCountry country;
|
|
if (geolocation.LocateSelf(lat, lon) == B_OK
|
|
&& geolocation.Country(lat, lon, country) == B_OK)
|
|
{
|
|
for (int i = 0; i < fServerList->CountItems; i++) {
|
|
if (strcmp(kServerInfos[i].country, country.Code()) == 0) {
|
|
fServerList->Select(i);
|
|
|
|
fLayout->SetVisibleItem(1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
/* fallthrough */
|
|
}
|
|
case kShowServerList:
|
|
{
|
|
// User did not want to be geolocalized, or no suitable server was
|
|
// found: select at random
|
|
if (fServerList->CurrentSelection() < 0) {
|
|
std::random_device generator;
|
|
std::uniform_int_distribution<int> distribution(0,
|
|
fServerList->CountItems());
|
|
int random = distribution(generator);
|
|
fServerList->Select(random);
|
|
}
|
|
fLayout->SetVisibleItem(1);
|
|
break;
|
|
}
|
|
|
|
case kSelectServer:
|
|
{
|
|
BStringItem* item = dynamic_cast<BStringItem*>(
|
|
fServerList->ItemAt(fServerList->CurrentSelection()));
|
|
if (item) {
|
|
fServerBox->SetText(item->Text());
|
|
fServerBox->MarkAsInvalid(false);
|
|
} else {
|
|
fServerBox->SetText("");
|
|
fServerBox->MarkAsInvalid(true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kGetUserInfo:
|
|
{
|
|
fLayout->SetVisibleItem(2);
|
|
|
|
// Cleanup the registration form from previous attempts, if any
|
|
for (int32 count = fRegistrationForm->CountChildren(); --count >= 0;)
|
|
{
|
|
fRegistrationForm->RemoveChild(fRegistrationForm->ChildAt(count));
|
|
}
|
|
|
|
// connect to server and ask which fields are required
|
|
gloox::Client* client = new gloox::Client(fServerBox->Text());
|
|
fConnection = new GlooxHandler(client);
|
|
fConnection->StartWatchingAll(this);
|
|
fConnection->Run();
|
|
|
|
break;
|
|
}
|
|
|
|
case kCreateAccount:
|
|
{
|
|
BView* formContainer = getRegistrationView();
|
|
|
|
if (formContainer) {
|
|
gloox::DataForm* dataForm = new gloox::DataForm(gloox::TypeSubmit);
|
|
for (int32 count = formContainer->CountChildren(); --count >= 0;) {
|
|
BView* entry = formContainer->ChildAt(count);
|
|
|
|
if (entry->Name() == BString("gloox::title")) {
|
|
BStringView* title = dynamic_cast<BStringView*>(entry);
|
|
dataForm->setTitle(title->Text());
|
|
} else {
|
|
BTextControl* c = dynamic_cast<BTextControl*>(entry);
|
|
if (c) {
|
|
dataForm->addField(gloox::DataFormField::TypeNone,
|
|
c->Name(), c->Text());
|
|
|
|
// Extract these fields for populating the main
|
|
// window when registration completes.
|
|
if (c->Name() == BString("username"))
|
|
fUsername = c->Text();
|
|
if (c->Name() == BString("password"))
|
|
fPassword = c->Text();
|
|
}
|
|
}
|
|
|
|
// TODO serialize all fields (name and value only)
|
|
}
|
|
fConnection->createAccount(dataForm);
|
|
} else {
|
|
// TODO try to locate a FixedField form view instead and use that
|
|
fprintf(stderr, "Don't know how to create an account without a dataform, yet\n");
|
|
}
|
|
// see you in handleRegistrationResult!
|
|
break;
|
|
}
|
|
|
|
case B_OBSERVER_NOTICE_CHANGE:
|
|
{
|
|
int32 orig_what = message->FindInt32("be:observe_change_what");
|
|
switch (orig_what) {
|
|
case kConnect:
|
|
{
|
|
// TODO request registration fields here instead of in GlooxHandler
|
|
fWaitingMessage->SetText("Setting up secure connection…");
|
|
break;
|
|
}
|
|
case kTLSConnect:
|
|
{
|
|
// TODO confirm TLS certificate, when gloox handler supports that
|
|
fWaitingMessage->SetText("Getting registration form from server…");
|
|
break;
|
|
}
|
|
case kDisconnect:
|
|
{
|
|
gloox::ConnectionError error = (gloox::ConnectionError)
|
|
message->FindInt32("gloox::ConnectionError");
|
|
gloox::StreamError streamError = (gloox::StreamError)
|
|
message->FindInt32("gloox::StreamError");
|
|
onDisconnect(error, streamError);
|
|
break;
|
|
}
|
|
case kRegistrationFields:
|
|
{
|
|
// TODO handle only if there is no dataform
|
|
break;
|
|
}
|
|
case kDataForm:
|
|
{
|
|
BString str;
|
|
message->FindString("gloox::JID", &str);
|
|
gloox::JID from(str.String());
|
|
BView* view = new BView(message);
|
|
handleDataForm(from, view);
|
|
break;
|
|
}
|
|
case kMedia:
|
|
{
|
|
BString uri = message->FindString("uri");
|
|
BString type = message->FindString("type");
|
|
handleMedia(type, BUrl(uri));
|
|
break;
|
|
}
|
|
case kOOB:
|
|
{
|
|
// TODO handle out of band data
|
|
message->PrintToStream();
|
|
//handleOOB(...);
|
|
break;
|
|
}
|
|
|
|
|
|
case kRegistrationResult:
|
|
{
|
|
BString jidString = message->FindString("gloox::JID");
|
|
gloox::JID jid(jidString.String());
|
|
gloox::RegistrationResult result = (gloox::RegistrationResult)
|
|
message->FindInt32("gloox::RegistrationResult");
|
|
handleRegistrationResult(jid, result);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
message->PrintToStream();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
RegisterAccountWindow::Show()
|
|
{
|
|
BWindow::Show();
|
|
CenterOnScreen();
|
|
}
|
|
|
|
|
|
#if 0
|
|
void RegisterAccountWindow::handleRegistrationFields(const gloox::JID& from __attribute__((unused)), int requiredFields,
|
|
std::string instructions)
|
|
{
|
|
// FIXME if there are any required fields besides username and password,
|
|
// ask the user to fill those in. Again I need a server which does this
|
|
// to test this, and jabber.fr doesn't.
|
|
fprintf(stderr, "%s (%x) -> %s %s\n", instructions.c_str(), requiredFields, fUsername->Text(), fPassword->Text());
|
|
gloox::RegistrationFields fields;
|
|
gloox::JID jid(fUsername->Text()); // FIXME append servername
|
|
fields.username = jid.username();
|
|
fields.password = fPassword->Text();
|
|
// TODO this can return an error
|
|
fRegistration->createAccount(
|
|
gloox::Registration::FieldUsername | gloox::Registration::FieldPassword,
|
|
fields);
|
|
}
|
|
#endif
|
|
|
|
|
|
void RegisterAccountWindow::handleRegistrationResult(const gloox::JID&, gloox::RegistrationResult r)
|
|
{
|
|
switch (r)
|
|
{
|
|
case gloox::RegistrationSuccess:
|
|
{
|
|
// Put login in password into configuration
|
|
BString jid;
|
|
jid.SetToFormat("%s@%s", fUsername.String(), fServerBox->Text());
|
|
BlabberSettings::Instance()->SetData("last-login", fUsername);
|
|
BlabberSettings::Instance()->SetData("last-password", fPassword);
|
|
|
|
// Refresh main window (disconnects a running session, but that should be ok)
|
|
BlabberMainWindow::Instance()->PostMessage(kResetWindow);
|
|
|
|
// And finally, we can close ourselves, the registration is complete
|
|
PostMessage(B_QUIT_REQUESTED);
|
|
return;
|
|
}
|
|
|
|
case gloox::RegistrationNotAcceptable:
|
|
{
|
|
BView* formContainer = getRegistrationView();
|
|
if (formContainer) {
|
|
// Probably a required field was not filled, highlight all empty ones
|
|
bool required = false;
|
|
for (int32 count = formContainer->CountChildren(); --count >= 0;) {
|
|
BView* entry = formContainer->ChildAt(count);
|
|
|
|
if (entry->Name() == BString("gloox::requiredMarker")) {
|
|
required = true;
|
|
continue;
|
|
} else {
|
|
BTextControl* control = dynamic_cast<BTextControl*>(entry);
|
|
if (control) {
|
|
bool valid = (!required) || !BString(control->Text()).IsEmpty();
|
|
control->MarkAsInvalid(!valid);
|
|
if (!valid)
|
|
control->SetToolTip("This field is required.");
|
|
else
|
|
control->SetToolTip((const char*)NULL);
|
|
}
|
|
required = false;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case gloox::RegistrationConflict:
|
|
{
|
|
BView* formContainer = getRegistrationView();
|
|
if (formContainer) {
|
|
BView* v = formContainer->FindView("username");
|
|
BTextControl* c = dynamic_cast<BTextControl*>(v);
|
|
if (c) {
|
|
c->MarkAsInvalid(true);
|
|
c->SetToolTip("This username is already registered. Pick another one.");
|
|
} else {
|
|
fprintf(stderr, "username not found\n");
|
|
}
|
|
} else {
|
|
fprintf(stderr, "registrationview not found\n");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case gloox::RegistrationNotAllowed:
|
|
{
|
|
fWelcome->SetText("The server currently does not allow registration.\n"
|
|
"Try another one.");
|
|
fServerList->ItemAt(fServerList->CurrentSelection())->SetEnabled(false);
|
|
fLayout->SetVisibleItem(1);
|
|
break;
|
|
}
|
|
|
|
case gloox::RegistrationUnknownError:
|
|
{
|
|
// Unknown error, better try another server that works more sanely.
|
|
fWelcome->SetText("The server did not accept our registration request.\n"
|
|
"Try another one.");
|
|
fServerList->ItemAt(fServerList->CurrentSelection())->SetEnabled(false);
|
|
fLayout->SetVisibleItem(1);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// TODO something went wrong, go back to the previous screen + highlight problems
|
|
fprintf(stderr, "%s(%d)\n", __PRETTY_FUNCTION__, r);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RegisterAccountWindow::handleDataForm(const gloox::JID&, BView* form)
|
|
{
|
|
fRegistrationForm->AddChild(form);
|
|
fLayout->SetVisibleItem(3);
|
|
}
|
|
|
|
|
|
void RegisterAccountWindow::handleMedia(BString type __attribute__((unused)), BUrl uri)
|
|
{
|
|
BPositionIO* data = NULL;
|
|
std::string storage;
|
|
|
|
if (uri.Protocol() == "cid") {
|
|
// Get the data from bob registry
|
|
storage = BobStore::Instance()->Get(uri.Path().String());
|
|
data = new BMemoryIO(storage.c_str(), storage.length());
|
|
} else {
|
|
fprintf(stderr, "Unhandled protocol for %s\n", uri.UrlString().String());
|
|
// TODO get the data using UrlRoster if it can handle it
|
|
}
|
|
|
|
if (data) {
|
|
PictureView* pic = new PictureView(data);
|
|
fRegistrationForm->AddChild(pic);
|
|
}
|
|
|
|
delete data;
|
|
}
|
|
|
|
|
|
#if 0
|
|
void RegisterAccountWindow::handleOOB(const gloox::JID&, const gloox::OOB& oob)
|
|
{
|
|
// TODO this will usually provide an URL for out-of-band registration.
|
|
// If this is all we get, it means the registration process cannot be
|
|
// completed using RegistrationFields or DataForm, so we should redirect
|
|
// the user to the registration web page for that server.
|
|
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
|
|
}
|
|
#endif
|
|
|
|
|
|
void RegisterAccountWindow::onDisconnect(gloox::ConnectionError error,
|
|
gloox::StreamError streamError)
|
|
{
|
|
fServerBox->MarkAsInvalid(true);
|
|
|
|
switch (error)
|
|
{
|
|
case gloox::ConnStreamError:
|
|
{
|
|
switch (streamError) {
|
|
case gloox::ConnStreamClosed:
|
|
{
|
|
fWelcome->SetText("The server closed the connection unexpectedly.\n");
|
|
fServerList->ItemAt(fServerList->CurrentSelection())->SetEnabled(false);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
BString message;
|
|
message.SetToFormat("Could not understand the reply from the server.\n"
|
|
"Stream error %d", streamError);
|
|
fWelcome->SetText(message);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case gloox::ConnStreamClosed:
|
|
fWelcome->SetText("The server closed the connection unexpectedly. "
|
|
"Maybe your internet access is too slow to use XMPP reliably.");
|
|
break;
|
|
case gloox::ConnStreamVersionError:
|
|
fWelcome->SetText("The server stream version is not compatible. "
|
|
"Try another server.");
|
|
fServerList->ItemAt(fServerList->CurrentSelection())->SetEnabled(false);
|
|
break;
|
|
case gloox::ConnConnectionRefused:
|
|
fWelcome->SetText("Could not connect to the server.\nCheck you are "
|
|
"online, then try another one.");
|
|
// Do not disable the selected server, allow to retry when online
|
|
break;
|
|
case gloox::ConnTlsFailed:
|
|
fWelcome->SetText("Failed to setup a secure TLS communication channel. "
|
|
"Try another server.");
|
|
fServerList->ItemAt(fServerList->CurrentSelection())->SetEnabled(false);
|
|
break;
|
|
default:
|
|
BString message;
|
|
message.SetToFormat("Could not connect to the server (error code: %d). "
|
|
"Try another one.", error);
|
|
fWelcome->SetText(message);
|
|
fServerList->ItemAt(fServerList->CurrentSelection())->SetEnabled(false);
|
|
break;
|
|
}
|
|
fLayout->SetVisibleItem(1);
|
|
}
|
|
|
|
|
|
BView* RegisterAccountWindow::getRegistrationView()
|
|
{
|
|
BView* formContainer = NULL;
|
|
for (int32 count = fRegistrationForm->CountChildren(); --count >= 0;)
|
|
{
|
|
formContainer = fRegistrationForm->ChildAt(count);
|
|
if (formContainer->Name() == BString("gloox::DataForm"))
|
|
break;
|
|
formContainer = nullptr;
|
|
}
|
|
|
|
return formContainer;
|
|
}
|