diff --git a/src/formHandler.ts b/src/formHandler.ts index 12d06f7..8205510 100644 --- a/src/formHandler.ts +++ b/src/formHandler.ts @@ -44,3 +44,24 @@ export function getFormValues() { ignoreMqtt, }; } + +/** + * Populate the form with values from the configuration. + * @param formValues - The object containing values to populate the form. + */ +export function populateForm(formValues: any) { + const form = document.getElementById('meshtasticForm') as HTMLFormElement | null; + if (!form) throw new Error('Form element not found.'); + + (form.elements.namedItem('channelName') as HTMLInputElement).value = formValues.channelName || ''; + (form.elements.namedItem('psk') as HTMLInputElement).value = formValues.psk || ''; + (form.elements.namedItem('uplinkEnabled') as HTMLInputElement).checked = formValues.uplinkEnabled || false; + (form.elements.namedItem('downlinkEnabled') as HTMLInputElement).checked = formValues.downlinkEnabled || false; + (form.elements.namedItem('positionPrecision') as HTMLInputElement).value = String(formValues.positionPrecision || 0); + (form.elements.namedItem('isClientMuted') as HTMLInputElement).checked = formValues.isClientMuted || false; + (form.elements.namedItem('region') as HTMLSelectElement).value = String(formValues.region || 0); + (form.elements.namedItem('modemPreset') as HTMLSelectElement).value = String(formValues.modemPreset || 0); + (form.elements.namedItem('hopLimit') as HTMLInputElement).value = String(formValues.hopLimit || 3); + (form.elements.namedItem('ignoreMqtt') as HTMLInputElement).checked = formValues.ignoreMqtt || false; + (form.elements.namedItem('configOkToMqtt') as HTMLInputElement).checked = formValues.configOkToMqtt || false; +} diff --git a/src/main.ts b/src/main.ts index 030be82..b7fabd5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,24 @@ -import { getFormValues } from './formHandler'; +import { getFormValues, populateForm } from './formHandler'; import { generatePSK } from './pskGenerator'; import { buildProtobuf } from './protobufBuilder'; import { generateQRCode } from './qrCodeGenerator'; -import { getByteLength, toUrlSafeBase64 } from './utils'; +import { getByteLength, toUrlSafeBase64, fromUrlSafeBase64 } from './utils'; +import { Protobuf } from "@meshtastic/js"; /** * Handle the DOMContentLoaded event. * Add event listeners to the buttons and form fields. */ document.addEventListener('DOMContentLoaded', () => { + // Check if there is a configuration in the URL hash and load it + const urlHash = window.location.hash.substring(1); // Remove the "#" character + if (urlHash) { + loadConfigurationFromHash(urlHash); + } + + // Generate QR code for the default form values + generateConfig(); + // Listen for changes on all form inputs and selects document.querySelectorAll('#meshtasticForm input, #meshtasticForm select').forEach(element => { element.addEventListener('input', generateConfig); @@ -22,11 +32,40 @@ document.addEventListener('DOMContentLoaded', () => { // Add click listener for copying the URL document.getElementById('copyUrlButton')?.addEventListener('click', copyUrlToClipboard); - - // Generate QR Code on first page load - generateConfig(); }); +/** + * Load the configuration from the hash and populate the form. + * @param {string} hash - The URL-safe Base64 configuration string. + */ +function loadConfigurationFromHash(hash: string): void { + try { + const binaryData = fromUrlSafeBase64(hash); + const channelSet = Protobuf.AppOnly.ChannelSet.fromBinary(binaryData); + + // Extract the channel settings from the Protobuf message + const channelSettings = channelSet.settings[0]; + const formValues = { + channelName: channelSettings.name, + psk: new TextDecoder().decode(channelSettings.psk), + uplinkEnabled: channelSettings.uplinkEnabled, + downlinkEnabled: channelSettings.downlinkEnabled, + positionPrecision: channelSettings.moduleSettings?.positionPrecision || 0, + isClientMuted: channelSettings.moduleSettings?.isClientMuted || false, + region: channelSet.loraConfig.region, + modemPreset: channelSet.loraConfig.modemPreset, + hopLimit: channelSet.loraConfig.hopLimit, + ignoreMqtt: channelSet.loraConfig.ignoreMqtt, + configOkToMqtt: channelSet.loraConfig.configOkToMqtt, + }; + + // Populate the form with these values using formHandler + populateForm(formValues); + } catch (error) { + console.error("Error loading configuration from URL hash:", error); + } +} + /** * Handle the change event on the PSK type select element. * Enable or disable the PSK input field based on the selected PSK type. @@ -80,6 +119,9 @@ async function generateConfig(): Promise { // Convert to URL-safe Base64 string const base64 = toUrlSafeBase64(binaryData); + // Update the URL hash with the generated configuration + window.location.hash = `#${base64}`; + // Create the Meshtastic URL const meshtasticUrl = `https://meshtastic.org/e/#${base64}`; console.log("Generated Meshtastic URL:", meshtasticUrl); diff --git a/src/utils.ts b/src/utils.ts index d2f6cfe..6b39bbd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { fromByteArray } from "base64-js"; +import { fromByteArray, toByteArray } from "base64-js"; /** * Utility function to calculate the byte length of a string. @@ -21,3 +21,19 @@ export function toUrlSafeBase64(binaryData: Uint8Array): string { .replace(/\+/g, "-") .replace(/\//g, "_"); } + +/** + * Convert a URL-safe Base64 string back to binary data. + * @param base64String - The URL-safe Base64 string to decode. + * @returns {Uint8Array} - The decoded binary data. + */ +export function fromUrlSafeBase64(base64String: string): Uint8Array { + // Replace URL-safe characters with standard Base64 characters + const paddedBase64 = base64String + .replace(/-/g, "+") + .replace(/_/g, "/") + // Re-add padding if necessary (Base64 strings must be divisible by 4) + .padEnd(base64String.length + (4 - (base64String.length % 4)) % 4, "="); + + return toByteArray(paddedBase64); +}