From 63b4be6c53d3b79e7d9dc24c61cf83649175b265 Mon Sep 17 00:00:00 2001 From: Li Zhineng Date: Sat, 12 Jul 2025 18:10:51 +0800 Subject: fetch device key --- main.mjs | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) (limited to 'main.mjs') diff --git a/main.mjs b/main.mjs index 9d0d025..4bc1ce5 100644 --- a/main.mjs +++ b/main.mjs @@ -580,6 +580,13 @@ class CommunicationForm extends Form { #retryLink = null #retryLinkClassName = `retry-link` + /** + * The callback handles the registered device. + * + * @type {CallableFunction|null} + */ + #pairHandler = null + /** * @param {string} id - The ID of the form. * @param {BluetoothHandler} handler - The Bluetooth handler to manage the connection. @@ -702,6 +709,16 @@ class CommunicationForm extends Form { this.#progress.markAsComplete('identity') this.#progress.clear() this.disconnectIfNeeded() + + if (this.#pairHandler) { + const length = message.payload[0] + let deviceId = 0 + for (let i = 0; i < length; i++) { + deviceId = (deviceId << 8) | message.payload[1 + i] + } + this.#pairHandler(deviceId) + } + this.transitToSuccessResultForm() } @@ -736,6 +753,14 @@ class CommunicationForm extends Form { return this } + /** + * @param {CallableFunction} handler + */ + onPair(handler) { + this.#pairHandler = handler + return this + } + transitToSuccessResultForm() { this.hide() this.#successForm?.display() @@ -747,6 +772,126 @@ class CommunicationForm extends Form { } } +class SuccessForm extends Form { + /** @type {HTMLElement} */ + #inputGroup + + /** @type {HTMLElement} */ + #input + + /** @type {HTMLElement} */ + #button + + /** @type {string|null} */ + #deviceId = null + + /** @type {string|null} */ + #key = null + + constructor(id) { + super(id) + this.#inputGroup = this.form.querySelector('[data-key]') + if (this.#inputGroup === null) { + throw new Error('Could not find the input group for device key.') + } + this.#renderInput() + } + + #renderInput() { + this.#input = document.createElement('input') + this.#input.classList.add('input') + this.#input.setAttribute('type', 'text') + this.#input.setAttribute('name', 'key') + this.#input.setAttribute('value', 'Loading...') + this.#input.setAttribute('readonly', '') + this.#inputGroup.appendChild(this.#input) + } + + #renderButton() { + this.#button = document.createElement('button') + this.#button.classList.add('button') + this.#button.setAttribute('type', 'button') + this.#button.addEventListener('click', this.#handleButtonClick.bind(this)) + this.#button.innerText = 'Copy' + this.#inputGroup.appendChild(this.#button) + } + + onDisplay() { + if (this.#key === null) { + this.#initializeDeviceKey() + } + } + + async #initializeDeviceKey() { + try { + this.#key = await this.#fetchDeviceKey() + + if (this.#key === null) { + this.#handleDeviceKeyRetrievalFailure() + return + } + + this.#input.value = this.#key + this.#renderButton() + } catch { + this.#handleDeviceKeyRetrievalFailure() + } + } + + async #fetchDeviceKey() { + if (this.#deviceId === null) { + return null + } + + const response = await fetch(`https://i.airmx.cn/exchange?device=${this.#deviceId}`) + + if (! response.ok) { + throw new Error('Could not retrieve the device key.') + } + + const data = await response.json() + return data.key + } + + async #handleButtonClick() { + if (! this.#input) { + return + } + + try { + await navigator.clipboard.writeText(this.#input.value) + this.#alert('Copied to the clipboard.') + } catch { + this.#alert('Unable to copy to the clipboard because of permission issues.') + } + } + + /** + * @param {string} message + */ + #alert(message) { + this.form.querySelector('.help-text')?.remove() + + const element = document.createElement('div') + element.classList.add('help-text') + element.textContent = message + + this.form.appendChild(element) + } + + #handleDeviceKeyRetrievalFailure() { + this.#input.value = 'Could not retrieve the device key.' + } + + /** + * @param {number} deviceId + */ + deviceIdUsing(deviceId) { + this.#deviceId = deviceId + return this + } +} + class FailureForm extends ProgressibleForm { constructor(id) { super(id) @@ -852,11 +997,14 @@ class Application { const handler = new BluetoothHandler(Device.airmxPro()) - const successForm = new Form('form-result-success') + const successForm = new SuccessForm('form-result-success') const failureForm = new FailureForm('form-result-failure') const communicationForm = new CommunicationForm('form-communication', handler) .succeedTo(successForm) .failTo(failureForm) + .onPair((deviceId) => { + successForm.deviceIdUsing(deviceId) + }) const pairingActivationForm = new PairingActivationForm('form-pairing-activation') .nextTo(communicationForm) const wifiCredentialsForm = new WifiCredentialsForm('form-wifi-credentials') -- cgit v1.2.3