~emersion/gamja

4be64d1ddc2a4b1ad8869464e203917591411c6f — Simon Ser 18 days ago 870a9b6 register-account
wip: implement account registration

Implement the draft account registration spec [1]. before-connect
is unimplemented.

TODO:

- Check and request caps
- Add support for email-required

[1]: https://github.com/ircv3/ircv3-specifications/pull/435
M components/app.js => components/app.js +57 -0
@@ 6,6 6,8 @@ import BufferHeader from "./buffer-header.js";
import MemberList from "./member-list.js";
import ConnectForm from "./connect-form.js";
import JoinForm from "./join-form.js";
import RegisterForm from "./register-form.js";
import VerifyForm from "./verify-form.js";
import Help from "./help.js";
import Composer from "./composer.js";
import ScrollManager from "./scroll-manager.js";


@@ 162,6 164,8 @@ export default class App extends Component {

		this.handleConnectSubmit = this.handleConnectSubmit.bind(this);
		this.handleJoinSubmit = this.handleJoinSubmit.bind(this);
		this.handleRegisterSubmit = this.handleRegisterSubmit.bind(this);
		this.handleVerifySubmit = this.handleVerifySubmit.bind(this);
		this.handleBufferListClick = this.handleBufferListClick.bind(this);
		this.handleComposerSubmit = this.handleComposerSubmit.bind(this);
		this.handleNickClick = this.handleNickClick.bind(this);


@@ 848,6 852,44 @@ export default class App extends Component {
		this.setState({ dialog: null, joinDialog: null });
	}

	handleRegisterClick(netID) {
		this.setState({ dialog: "register", registerDialog: { network: netID } });
	}

	handleRegisterSubmit(data) {
		var netID = this.state.registerDialog.network;
		var client = this.clients.get(netID);

		client
			.registerAccount(data.email, data.password)
			.then((data) => {
				this.setState({ dialog: null, registerDialog: null });

				if (data.result == "VERIFICATION_REQUIRED") {
					this.setState({
						dialog: "verify",
						verifyDialog: { network: netID, account: data.account },
					});
				}
			})
			.catch((err) => {
				this.setState({ error: err });
			});
	}

	handleVerifySubmit(data) {
		var client = this.clients.get(this.state.verifyDialog.network);

		client
			.verifyAccount(this.state.verifyDialog.account, data.code)
			.then((data) => {
				this.setState({ dialog: null, verifyDialog: null });
			})
			.catch((err) => {
				this.setState({ error: err });
			});
	}

	autocomplete(prefix) {
		function fromList(l, prefix) {
			prefix = prefix.toLowerCase();


@@ 953,6 995,7 @@ export default class App extends Component {
						network=${activeNetwork}
						onClose=${() => this.close(activeBuffer)}
						onJoin=${() => this.handleJoinClick(activeBuffer.network)}
						onRegister=${() => this.handleRegisterClick(activeBuffer.network)}
					/>
				</section>
			`;


@@ 989,6 1032,20 @@ export default class App extends Component {
				</>
			`;
			break;
		case "register":
			dialog = html`
				<${Dialog} title="Register account" onDismiss=${this.handleDialogDismiss}>
					<${RegisterForm} error=${this.state.error} onSubmit=${this.handleRegisterSubmit}/>
				</>
			`;
			break;
		case "verify":
			dialog = html`
				<${Dialog} title="Verify account" onDismiss=${this.handleDialogDismiss}>
					<${VerifyForm} error=${this.state.error} onSubmit=${this.handleVerifySubmit}/>
				</>
			`;
			break;
		}

		var error = null;

M components/buffer-header.js => components/buffer-header.js +9 -1
@@ 28,6 28,10 @@ export default function BufferHeader(props) {
		event.preventDefault();
		props.onJoin();
	}
	function handleRegisterClick(event) {
		event.preventDefault();
		props.onRegister();
	}

	var description = null;
	if (props.buffer.serverInfo) {


@@ 71,7 75,11 @@ export default function BufferHeader(props) {
	var closeText = "Close";
	switch (props.buffer.type) {
	case BufferType.SERVER:
		actions = html`<a href="#" onClick=${handleJoinClick}>Join</a>`;
		actions = html`
			<a href="#" onClick=${handleJoinClick}>Join</a>
			${" "}
			<a href="#" onClick=${handleRegisterClick}>Register</a>
		`;
		closeText = "Disconnect";
		break;
	case BufferType.CHANNEL:

A components/register-form.js => components/register-form.js +52 -0
@@ 0,0 1,52 @@
import { html, Component } from "../lib/index.js";

export default class RegisterForm extends Component {
	state = {
		email: "",
		password: "",
	};

	constructor(props) {
		super(props);

		this.handleChange = this.handleChange.bind(this);
		this.handleSubmit = this.handleSubmit.bind(this);
	}

	handleChange(event) {
		var target = event.target;
		this.setState({ [target.name]: target.value });
	}

	handleSubmit(event) {
		event.preventDefault();
		this.props.onSubmit({
			email: this.state.email,
			password: this.state.password,
		});
	}

	render() {
		return html`
			<form onChange=${this.handleChange} onSubmit=${this.handleSubmit}>
				<label>
					Email:<br/>
					<input type="email" name="email" value=${this.state.email} autofocus/>
				</label>
				<br/>

				<label>
					Password:<br/>
					<input type="password" name="password" value=${this.state.password} required/>
				</label>
				<br/>

				<br/>
				${this.props.error ? html`
					<p class="error-text">${this.props.error}</p>
				` : null}
				<button>Register</button>
			</form>
		`;
	}
}

A components/verify-form.js => components/verify-form.js +43 -0
@@ 0,0 1,43 @@
import { html, Component } from "../lib/index.js";

export default class VerifyForm extends Component {
	state = {
		code: "",
	};

	constructor(props) {
		super(props);

		this.handleChange = this.handleChange.bind(this);
		this.handleSubmit = this.handleSubmit.bind(this);
	}

	handleChange(event) {
		var target = event.target;
		this.setState({ [target.name]: target.value });
	}

	handleSubmit(event) {
		event.preventDefault();

		this.props.onSubmit({ code: this.state.code });
	}

	render() {
		return html`
			<form onChange=${this.handleChange} onSubmit=${this.handleSubmit}>
				<label>
					Code:<br/>
					<input type="text" name="code" value=${this.state.code} autofocus required/>
				</label>
				<br/>

				<br/>
				${this.props.error ? html`
					<p class="error-text">${this.props.error}</p>
				` : null}
				<button>Verify</button>
			</form>
		`;
	}
}

M lib/client.js => lib/client.js +50 -0
@@ 438,4 438,54 @@ export default class Client extends EventTarget {
			return null;
		});
	}

	registerAccount(email, password) {
		var msg = {
			command: "REGISTER",
			params: [email || "*", password],
		};
		return this.roundtrip(msg, (event) => {
			var msg = event.detail.message;

			switch (msg.command) {
			case "REGISTER":
				return {
					result: msg.params[0],
					account: msg.params[1],
					message: msg.params[2],
				};
			case "FAIL":
				if (msg.params[0] === msg.command) {
					throw msg;
				}
				break;
			}
		});
	}

	verifyAccount(account, code) {
		var msg = {
			command: "VERIFY",
			params: [account, code],
		};
		return this.roundtrip(msg, (event) => {
			var msg = event.detail.message;

			switch (msg.command) {
			case "VERIFY":
				if (msg.params[1] != account) {
					break;
				}
				if (msg.params[0] != "SUCCESS") {
					throw msg;
				}
				return { message: msg.params[2] };
			case "FAIL":
				if (msg.params[0] === msg.command) {
					throw msg;
				}
				break;
			}
		});
	}
}