From 0eedce69a6d375ea4b75dc66ac97883d8ec0c7ac Mon Sep 17 00:00:00 2001 From: Simon Bordeyne <13538749+sbordeyne@users.noreply.github.com> Date: Wed, 6 Sep 2023 09:46:40 +0200 Subject: [PATCH] feat(new tool): add wifi qr code generator (#599) * (feat: new tool): add wifi qr code generator * Update src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue Co-authored-by: Corentin THOMASSET * Update src/tools/wifi-qr-code-generator/index.ts Co-authored-by: Corentin THOMASSET * remove naive UI grid * Update src/tools/wifi-qr-code-generator/index.ts --------- Co-authored-by: Corentin THOMASSET --- components.d.ts | 1 + src/tools/index.ts | 3 +- src/tools/wifi-qr-code-generator/index.ts | 13 ++ src/tools/wifi-qr-code-generator/useQRCode.ts | 146 +++++++++++++++++ .../wifi-qr-code-generator.vue | 153 ++++++++++++++++++ 5 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 src/tools/wifi-qr-code-generator/index.ts create mode 100644 src/tools/wifi-qr-code-generator/useQRCode.ts create mode 100644 src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue diff --git a/components.d.ts b/components.d.ts index af06dfb6..f51e12cd 100644 --- a/components.d.ts +++ b/components.d.ts @@ -188,6 +188,7 @@ declare module '@vue/runtime-core' { UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default'] UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default'] UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default'] + WifiQrCodeGenerator: typeof import('./src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue')['default'] XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default'] YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default'] YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default'] diff --git a/src/tools/index.ts b/src/tools/index.ts index 15770b56..aa380745 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -56,6 +56,7 @@ import { tool as metaTagGenerator } from './meta-tag-generator'; import { tool as mimeTypes } from './mime-types'; import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator'; import { tool as qrCodeGenerator } from './qr-code-generator'; +import { tool as wifiQrCodeGenerator } from './wifi-qr-code-generator'; import { tool as randomPortGenerator } from './random-port-generator'; import { tool as romanNumeralConverter } from './roman-numeral-converter'; import { tool as sqlPrettify } from './sql-prettify'; @@ -117,7 +118,7 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Images and videos', - components: [qrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], + components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], }, { name: 'Development', diff --git a/src/tools/wifi-qr-code-generator/index.ts b/src/tools/wifi-qr-code-generator/index.ts new file mode 100644 index 00000000..ad0135c3 --- /dev/null +++ b/src/tools/wifi-qr-code-generator/index.ts @@ -0,0 +1,13 @@ +import { Qrcode } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'WiFi QR Code generator', + path: '/wifi-qrcode-generator', + description: + 'Generate and download QR-codes for quick connections to WiFi networks.', + keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent', 'wifi'], + component: () => import('./wifi-qr-code-generator.vue'), + icon: Qrcode, + createdAt: new Date('2023-09-06'), +}); diff --git a/src/tools/wifi-qr-code-generator/useQRCode.ts b/src/tools/wifi-qr-code-generator/useQRCode.ts new file mode 100644 index 00000000..c8a7215c --- /dev/null +++ b/src/tools/wifi-qr-code-generator/useQRCode.ts @@ -0,0 +1,146 @@ +import { type MaybeRef, get } from '@vueuse/core'; +import QRCode, { type QRCodeToDataURLOptions } from 'qrcode'; +import { isRef, ref, watch } from 'vue'; + +export const wifiEncryptions = ['WEP', 'WPA', 'nopass', 'WPA2-EAP'] as const; +export type WifiEncryption = typeof wifiEncryptions[number]; + +// @see https://en.wikipedia.org/wiki/Extensible_Authentication_Protocol +// for a list of available EAP methods. There are a lot (40!) of them. +export const EAPMethods = [ + 'MD5', + 'POTP', + 'GTC', + 'TLS', + 'IKEv2', + 'SIM', + 'AKA', + 'AKA\'', + 'TTLS', + 'PWD', + 'LEAP', + 'PSK', + 'FAST', + 'TEAP', + 'EKE', + 'NOOB', + 'PEAP', +] as const; +export type EAPMethod = typeof EAPMethods[number]; + +export const EAPPhase2Methods = [ + 'None', + 'MSCHAPV2', +] as const; +export type EAPPhase2Method = typeof EAPPhase2Methods[number]; + +interface IWifiQRCodeOptions { + ssid: MaybeRef + password: MaybeRef + eapMethod: MaybeRef + isHiddenSSID: MaybeRef + eapAnonymous: MaybeRef + eapIdentity: MaybeRef + eapPhase2Method: MaybeRef + color: { foreground: MaybeRef; background: MaybeRef } + options?: QRCodeToDataURLOptions +} + +interface GetQrCodeTextOptions { + ssid: string + password: string + encryption: WifiEncryption + eapMethod: EAPMethod + isHiddenSSID: boolean + eapAnonymous: boolean + eapIdentity: string + eapPhase2Method: EAPPhase2Method +} + +function escapeString(str: string) { + // replaces \, ;, ,, " and : with the same character preceded by a backslash + return str.replace(/([\\;,:"])/g, '\\$1'); +} + +function getQrCodeText(options: GetQrCodeTextOptions): string | null { + const { ssid, password, encryption, eapMethod, isHiddenSSID, eapAnonymous, eapIdentity, eapPhase2Method } = options; + if (!ssid) { + return null; + } + if (encryption === 'nopass') { + return `WIFI:S:${escapeString(ssid)};;`; // type can be omitted in that case, and password is not needed, makes the QR Code smaller + } + if (encryption !== 'WPA2-EAP' && password) { + // EAP has a lot of options, so we'll handle it separately + // WPA and WEP are pretty simple though. + return `WIFI:S:${escapeString(ssid)};T:${encryption};P:${escapeString(password)};${isHiddenSSID ? 'H:true' : ''};`; + } + if (encryption === 'WPA2-EAP' && password && eapMethod) { + // WPA2-EAP string is a lot more complex, first off, we drop the text if there is no identity, and it's not anonymous. + if (!eapIdentity && !eapAnonymous) { + return null; + } + // From reading, I could only find that a phase 2 is required for the PEAP method, I may be wrong though, I didn't read the whole spec. + if (eapMethod === 'PEAP' && !eapPhase2Method) { + return null; + } + // The string is built in the following order: + // 1. SSID + // 2. Authentication type + // 3. Password + // 4. EAP method + // 5. EAP phase 2 method + // 6. Identity or anonymous if checked + // 7. Hidden SSID if checked + const identity = eapAnonymous ? 'A:anon' : `I:${escapeString(eapIdentity)}`; + const phase2 = eapPhase2Method !== 'None' ? `PH2:${eapPhase2Method};` : ''; + return `WIFI:S:${escapeString(ssid)};T:WPA2-EAP;P:${escapeString(password)};E:${eapMethod};${phase2}${identity};${isHiddenSSID ? 'H:true' : ''};`; + } + return null; +} + +export function useWifiQRCode({ + ssid, + password, + eapMethod, + isHiddenSSID, + eapAnonymous, + eapIdentity, + eapPhase2Method, + color: { background, foreground }, + options, +}: IWifiQRCodeOptions) { + const qrcode = ref(''); + const encryption = ref('WPA'); + + watch( + [ssid, password, encryption, eapMethod, isHiddenSSID, eapAnonymous, eapIdentity, eapPhase2Method, background, foreground].filter(isRef), + async () => { + // @see https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 + // This is the full spec, there's quite a bit of logic to generate the string embeddedin the QR code. + const text = getQrCodeText({ + ssid: get(ssid), + password: get(password), + encryption: get(encryption), + eapMethod: get(eapMethod), + isHiddenSSID: get(isHiddenSSID), + eapAnonymous: get(eapAnonymous), + eapIdentity: get(eapIdentity), + eapPhase2Method: get(eapPhase2Method), + }); + if (text) { + qrcode.value = await QRCode.toDataURL(get(text).trim(), { + color: { + dark: get(foreground), + light: get(background), + ...options?.color, + }, + errorCorrectionLevel: 'M', + ...options, + }); + } + }, + { immediate: true }, + ); + return { qrcode, encryption }; +} diff --git a/src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue b/src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue new file mode 100644 index 00000000..e6320d3e --- /dev/null +++ b/src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue @@ -0,0 +1,153 @@ + + +