diff options
author | 2023-09-06 09:46:40 +0200 | |
---|---|---|
committer | 2023-09-06 07:46:40 +0000 | |
commit | 0eedce69a6d375ea4b75dc66ac97883d8ec0c7ac (patch) | |
tree | 2e0675d6e5829a01300ee54c5ad8d9f958eddec0 /src | |
parent | 8a30b6bdb3c48138f8e015739a0189c87ee4a451 (diff) | |
download | it-tools-0eedce69a6d375ea4b75dc66ac97883d8ec0c7ac.tar.gz it-tools-0eedce69a6d375ea4b75dc66ac97883d8ec0c7ac.tar.zst it-tools-0eedce69a6d375ea4b75dc66ac97883d8ec0c7ac.zip |
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 <corentin.thomasset74@gmail.com>
* Update src/tools/wifi-qr-code-generator/index.ts
Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
* remove naive UI grid
* Update src/tools/wifi-qr-code-generator/index.ts
---------
Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/tools/index.ts | 3 | ||||
-rw-r--r-- | src/tools/wifi-qr-code-generator/index.ts | 13 | ||||
-rw-r--r-- | src/tools/wifi-qr-code-generator/useQRCode.ts | 146 | ||||
-rw-r--r-- | src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue | 153 |
4 files changed, 314 insertions, 1 deletions
diff --git a/src/tools/index.ts b/src/tools/index.ts index 15770b5..aa38074 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 0000000..ad0135c --- /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 0000000..c8a7215 --- /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<string> + password: MaybeRef<string> + eapMethod: MaybeRef<EAPMethod> + isHiddenSSID: MaybeRef<boolean> + eapAnonymous: MaybeRef<boolean> + eapIdentity: MaybeRef<string> + eapPhase2Method: MaybeRef<EAPPhase2Method> + color: { foreground: MaybeRef<string>; background: MaybeRef<string> } + 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<WifiEncryption>('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 0000000..e6320d3 --- /dev/null +++ b/src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue @@ -0,0 +1,153 @@ +<script setup lang="ts"> +import { + EAPMethods, + EAPPhase2Methods, + useWifiQRCode, +} from './useQRCode'; +import { useDownloadFileFromBase64 } from '@/composable/downloadBase64'; + +const foreground = ref('#000000ff'); +const background = ref('#ffffffff'); + +const ssid = ref(); +const password = ref(); +const eapMethod = ref(); +const isHiddenSSID = ref(false); +const eapAnonymous = ref(false); +const eapIdentity = ref(); +const eapPhase2Method = ref(); + +const { qrcode, encryption } = useWifiQRCode({ + ssid, + password, + eapMethod, + isHiddenSSID, + eapAnonymous, + eapIdentity, + eapPhase2Method, + color: { + background, + foreground, + }, + options: { width: 1024 }, +}); + +const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-code.png' }); +</script> + +<template> + <c-card> + <div grid grid-cols-1 gap-12> + <div> + <c-select + v-model:value="encryption" + mb-4 + label="Encryption method" + default-value="WPA" + label-position="left" + label-width="130px" + label-align="right" + :options="[ + { + label: 'No password', + value: 'nopass', + }, + { + label: 'WPA/WPA2', + value: 'WPA', + }, + { + label: 'WEP', + value: 'WEP', + }, + { + label: 'WPA2-EAP', + value: 'WPA2-EAP', + }, + ]" + /> + <div class="mb-6 flex flex-row items-center gap-2"> + <c-input-text + v-model:value="ssid" + label-position="left" + label-width="130px" + label-align="right" + label="SSID:" + rows="1" + autosize + placeholder="Your WiFi SSID..." + mb-6 + /> + <n-checkbox v-model:checked="isHiddenSSID"> + Hidden SSID + </n-checkbox> + </div> + <c-input-text + v-if="encryption !== 'nopass'" + v-model:value="password" + label-position="left" + label-width="130px" + label-align="right" + label="Password:" + rows="1" + autosize + type="password" + placeholder="Your WiFi Password..." + mb-6 + /> + <c-select + v-if="encryption === 'WPA2-EAP'" + v-model:value="eapMethod" + label="EAP method" + label-position="left" + label-width="130px" + label-align="right" + :options="EAPMethods.map((method) => ({ label: method, value: method }))" + searchable mb-4 + /> + <div v-if="encryption === 'WPA2-EAP'" class="mb-6 flex flex-row items-center gap-2"> + <c-input-text + v-model:value="eapIdentity" + label-position="left" + label-width="130px" + label-align="right" + label="Identity:" + rows="1" + autosize + placeholder="Your EAP Identity..." + mb-6 + /> + <n-checkbox v-model:checked="eapAnonymous"> + Anonymous? + </n-checkbox> + </div> + <c-select + v-if="encryption === 'WPA2-EAP'" + v-model:value="eapPhase2Method" + label="EAP Phase 2 method" + label-position="left" + label-width="130px" + label-align="right" + :options="EAPPhase2Methods.map((method) => ({ label: method, value: method }))" + searchable mb-4 + /> + <n-form label-width="130" label-placement="left"> + <n-form-item label="Foreground color:"> + <n-color-picker v-model:value="foreground" :modes="['hex']" /> + </n-form-item> + <n-form-item label="Background color:"> + <n-color-picker v-model:value="background" :modes="['hex']" /> + </n-form-item> + </n-form> + </div> + <div v-if="qrcode"> + <div flex flex-col items-center gap-3> + <img alt="wifi-qrcode" :src="qrcode" width="200"> + <c-button @click="download"> + Download qr-code + </c-button> + </div> + </div> + </div> + </c-card> +</template> |