Skip to content

Commit

Permalink
Render service URL input in registration form
Browse files Browse the repository at this point in the history
If none can be determined via settings of XEP-0156
  • Loading branch information
jcbrand committed Feb 18, 2025
1 parent 5d40a1f commit 7451d44
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 64 deletions.
17 changes: 11 additions & 6 deletions src/headless/shared/connection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,17 @@ export class Connection extends Strophe.Connection {
async discoverConnectionMethods (domain) {
// Use XEP-0156 to check whether this host advertises websocket or BOSH connection methods.
const options = {
'mode': /** @type {RequestMode} */('cors'),
'headers': {
'Accept': 'application/xrd+xml, text/xml'
mode: /** @type {RequestMode} */('cors'),
headers: {
Accept: 'application/xrd+xml, text/xml'
}
};
const url = `https://${domain}/.well-known/host-meta`;
let response;
try {
response = await fetch(url, options);
} catch (e) {
log.error(`Failed to discover alternative connection methods at ${url}`);
log.info(`Failed to discover alternative connection methods at ${url}`);
log.error(e);
return;
}
Expand All @@ -114,7 +114,7 @@ export class Connection extends Strophe.Connection {
* @param {Function} callback
*/
async connect (jid, password, callback) {
const { api } = _converse;
const { __, api } = _converse;

if (api.settings.get("discover_connection_methods")) {
const domain = Strophe.getDomainFromJid(jid);
Expand All @@ -124,6 +124,11 @@ export class Connection extends Strophe.Connection {
// If we don't have a connection URL, we show an input for the user
// to manually provide it.
api.settings.set('show_connection_url_input', true);
(callback || this.onConnectStatusChanged)(
Strophe.Status.DISCONNECTED,
__('Could not automatically determine a connection URL')
);
return;
}
super.connect(jid, password, callback || this.onConnectStatusChanged, BOSH_WAIT);
}
Expand Down Expand Up @@ -346,7 +351,7 @@ export class Connection extends Strophe.Connection {
* through various states while establishing or tearing down a
* connection.
* @param {Number} status
* @param {String} message
* @param {String} [message]
*/
onConnectStatusChanged (status, message) {
const { __ } = _converse;
Expand Down
4 changes: 2 additions & 2 deletions src/headless/types/shared/connection/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ export class Connection extends Connection_base {
* through various states while establishing or tearing down a
* connection.
* @param {Number} status
* @param {String} message
* @param {String} [message]
*/
onConnectStatusChanged(status: number, message: string): void;
onConnectStatusChanged(status: number, message?: string): void;
/**
* @param {string} type
*/
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/controlbox/loginform.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,5 @@ class LoginForm extends CustomElement {
}

api.elements.define('converse-login-form', LoginForm);

export default LoginForm;
32 changes: 19 additions & 13 deletions src/plugins/controlbox/templates/loginform.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { __ } from 'i18n';

const { ANONYMOUS, EXTERNAL, LOGIN, PREBIND, CONNECTION_STATUS } = constants;

const trust_checkbox = (checked) => {
/**
* @param {boolean} checked
*/
function tplTrustCheckbox(checked) {
const i18n_hint_trusted = __(
'To improve performance, we cache your data in this browser. ' +
'Uncheck this box if this is a public computer or if you want your data to be deleted when you log out. ' +
Expand Down Expand Up @@ -35,7 +38,7 @@ const trust_checkbox = (checked) => {
`;
};

const connection_url_input = () => {
export function tplConnectionURLInput() {
const i18n_connection_url = __('Connection URL');
const i18n_form_help = __('HTTP or websocket URL that is used to connect to your XMPP server');
const i18n_placeholder = __('e.g. wss://example.org/xmpp-websocket');
Expand All @@ -44,6 +47,7 @@ const connection_url_input = () => {
<label for="converse-conn-url" class="form-label">${i18n_connection_url}</label>
<p class="form-help instructions">${i18n_form_help}</p>
<input
required
id="converse-conn-url"
class="form-control"
type="url"
Expand All @@ -54,7 +58,7 @@ const connection_url_input = () => {
`;
};

const password_input = () => {
function tplPasswordInput() {
const i18n_password = __('Password');
return html`
<div class="mb-3">
Expand All @@ -72,7 +76,7 @@ const password_input = () => {
`;
};

const tplRegisterLink = () => {
function tplRegisterLink() {
const i18n_create_account = __('Create an account');
const i18n_hint_no_account = __("Don't have a chat account?");
return html`
Expand All @@ -85,15 +89,15 @@ const tplRegisterLink = () => {
`;
};

const tplShowRegisterLink = () => {
function tplShowRegisterLink() {
return (
api.settings.get('allow_registration') &&
!api.settings.get('auto_login') &&
_converse.pluggable.plugins['converse-register'].enabled(_converse)
);
};

const auth_fields = (el) => {
function tplAuthFields() {
const authentication = api.settings.get('authentication');
const i18n_login = __('Log in');
const i18n_xmpp_address = __('XMPP Address');
Expand All @@ -109,7 +113,6 @@ const auth_fields = (el) => {
<input
id="converse-login-jid"
?autofocus=${api.settings.get('auto_focus') ? true : false}
@changed=${el.validate}
value="${api.settings.get('jid') ?? ''}"
required
class="form-control"
Expand All @@ -118,9 +121,9 @@ const auth_fields = (el) => {
placeholder="${placeholder_username}"
/>
</div>
${authentication !== EXTERNAL ? password_input() : ''}
${api.settings.get('show_connection_url_input') ? connection_url_input() : ''}
${show_trust_checkbox ? trust_checkbox(show_trust_checkbox === 'off' ? false : true) : ''}
${authentication !== EXTERNAL ? tplPasswordInput() : ''}
${api.settings.get('show_connection_url_input') ? tplConnectionURLInput() : ''}
${show_trust_checkbox ? tplTrustCheckbox(show_trust_checkbox === 'off' ? false : true) : ''}
</fieldset>
<fieldset class="form-group buttons">
<input class="btn btn-primary" type="submit" value="${i18n_login}" />
Expand All @@ -129,19 +132,22 @@ const auth_fields = (el) => {
`;
};

const form_fields = (el) => {
function tplFormFields() {
const authentication = api.settings.get('authentication');
const i18n_disconnected = __('Disconnected');
const i18n_anon_login = __('Click here to log in anonymously');
return html`
${authentication == LOGIN || authentication == EXTERNAL ? auth_fields(el) : ''}
${authentication == LOGIN || authentication == EXTERNAL ? tplAuthFields() : ''}
${authentication == ANONYMOUS
? html`<input class="btn btn-primary login-anon" type="submit" value="${i18n_anon_login}" />`
: ''}
${authentication == PREBIND ? html`<p>${i18n_disconnected}</p>` : ''}
`;
};

/**
* @param {import('../loginform.js').default} el
*/
export default (el) => {
const { connfeedback } = _converse.state;
const connection_status = connfeedback.get('connection_status');
Expand All @@ -159,6 +165,6 @@ export default (el) => {
</div>
${CONNECTION_STATUS[connection_status] === 'CONNECTING'
? tplSpinner()
: form_fields(el)}
: tplFormFields()}
</form>`;
};
91 changes: 70 additions & 21 deletions src/plugins/register/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class RegistrationForm extends CustomElement {
static get properties () {
return {
status : { type: String },
domain: { type: String },
service_url: { type: String },
alert_message: { type: String },
alert_type: { type: String },
}
Expand All @@ -38,14 +40,17 @@ class RegistrationForm extends CustomElement {
this.fields = {};
this.domain = null;
this.alert_type = 'info';
this.setErrorMessage = (m) => this.setMessage(m, 'danger');
this.setFeedbackMessage = (m) => this.setMessage(m, 'info');
this.setErrorMessage = /** @param {string} m */(m) => this.setMessage(m, 'danger');
this.setFeedbackMessage = /** @param {string} m */(m) => this.setMessage(m, 'info');
}

initialize () {
this.reset();
this.listenTo(_converse, 'connectionInitialized', () => this.registerHooks());

const settings = api.settings.get();
this.listenTo(settings, 'change:show_connection_url_input', () => this.requestUpdate());

const domain = api.settings.get('registration_domain');
if (domain) {
this.fetchRegistrationForm(domain);
Expand All @@ -58,6 +63,10 @@ class RegistrationForm extends CustomElement {
return tplChooseProvider(this);
}

/**
* @param {string} message
* @param {'info'|'danger'} type
*/
setMessage(message, type) {
this.alert_type = type;
this.alert_message = message;
Expand Down Expand Up @@ -168,61 +177,92 @@ class RegistrationForm extends CustomElement {
onFormSubmission (ev) {
ev?.preventDefault?.();
const form = /** @type {HTMLFormElement} */(ev.target);
if (form.querySelector('input[name=domain]') === null) {

const domain_input = /** @type {HTMLInputElement} */(form.querySelector('input[name=domain]'));
if (domain_input === null) {
this.submitRegistrationForm(form);
} else {
this.onProviderChosen(form);
}

}

/**
* Callback method that gets called when the user has chosen an XMPP provider
* @method _converse.RegistrationForm#onProviderChosen
* @param {HTMLElement} form - The form that was submitted
* @param {HTMLFormElement} form - The form that was submitted
*/
onProviderChosen (form) {
const domain = /** @type {HTMLInputElement} */(form.querySelector('input[name=domain]'))?.value;
if (domain) this.fetchRegistrationForm(domain.trim());
if (domain) {
const form_data = new FormData(form);
let service_url = null;
if (api.settings.get('show_connection_url_input')) {
service_url = /** @type {string} */(form_data.get('connection-url'));
if (service_url.startsWith('wss:')) {
api.settings.set("websocket_url", service_url);
} else if (service_url.startsWith('https:')) {
api.settings.set('bosh_service_url', service_url);
} else {
this.alert_message = __('Invalid connection URL, only HTTPS and WSS accepted');
this.alert_type = 'danger';
this.status = CHOOSE_PROVIDER;
this.requestUpdate();
return;
}
}
this.fetchRegistrationForm(domain.trim(), service_url?.trim());
} else {
this.status = CHOOSE_PROVIDER;
}
}

/**
* Fetch a registration form from the requested domain
* @method _converse.RegistrationForm#fetchRegistrationForm
* @param {string} domain_name - XMPP server domain
* @param {string|null} [service_url]
*/
fetchRegistrationForm (domain_name) {
fetchRegistrationForm (domain_name, service_url) {
this.status = FETCHING_FORM;
this.reset({
'domain': Strophe.getDomainFromJid(domain_name),
'_registering': true
_registering: true,
domain: Strophe.getDomainFromJid(domain_name),
service_url,
});

api.connection.init();

// When testing, the test tears down before the async function
// above finishes. So we use optional chaining here
api.connection.get()?.connect(this.domain, "", (s) => this.onConnectStatusChanged(s));
api.connection.get()?.connect(
this.domain,
'',
/**
* @param {number} s
* @param {string} m
*/
(s, m) => this.onConnectStatusChanged(s, m)
);
return false;
}

/**
* Callback function called by Strophe whenever the connection status changes.
* Passed to Strophe specifically during a registration attempt.
* @method _converse.RegistrationForm#onConnectStatusChanged
* @param {number} status_code - The Strophe.Status status code
* @param {string} message
*/
onConnectStatusChanged(status_code) {
onConnectStatusChanged(status_code, message) {
log.debug('converse-register: onConnectStatusChanged');
if ([Strophe.Status.DISCONNECTED,
Strophe.Status.CONNFAIL,
Strophe.Status.REGIFAIL,
Strophe.Status.CONNFAIL,
Strophe.Status.REGIFAIL,
Strophe.Status.NOTACCEPTABLE,
Strophe.Status.CONFLICT
].includes(status_code)) {

log.error(
log.warn(
`Problem during registration: Strophe.Status is ${CONNECTION_STATUS[status_code]}`
);
this.abortRegistration();
this.abortRegistration(message);
} else if (status_code === Strophe.Status.REGISTERED) {
log.debug("Registered successfully.");
api.connection.get().reset();
Expand Down Expand Up @@ -313,6 +353,9 @@ class RegistrationForm extends CustomElement {
}
}

/**
* @param {Event} ev
*/
renderProviderChoiceForm (ev) {
ev?.preventDefault?.();
const connection = api.connection.get();
Expand All @@ -321,17 +364,23 @@ class RegistrationForm extends CustomElement {
this.status = CHOOSE_PROVIDER;
}

abortRegistration () {
/**
* @param {string} message
*/
abortRegistration (message) {
const connection = api.connection.get();
connection._proto._abortAllRequests();
connection.reset();
if ([FETCHING_FORM, REGISTRATION_FORM].includes(this.status)) {
if (api.settings.get('registration_domain')) {
this.fetchRegistrationForm(api.settings.get('registration_domain'));
return;
}
} else {
this.requestUpdate();
}
this.alert_message = message;
this.alert_type = 'danger';
this.status = CHOOSE_PROVIDER;
this.requestUpdate();
}

/**
Expand Down
Loading

0 comments on commit 7451d44

Please sign in to comment.