mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-01-01 13:12:59 +08:00
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>
This commit is contained in:
parent
8a30b6bdb3
commit
0eedce69a6
5 changed files with 315 additions and 1 deletions
1
components.d.ts
vendored
1
components.d.ts
vendored
|
@ -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']
|
||||
|
|
|
@ -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',
|
||||
|
|
13
src/tools/wifi-qr-code-generator/index.ts
Normal file
13
src/tools/wifi-qr-code-generator/index.ts
Normal file
|
@ -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'),
|
||||
});
|
146
src/tools/wifi-qr-code-generator/useQRCode.ts
Normal file
146
src/tools/wifi-qr-code-generator/useQRCode.ts
Normal file
|
@ -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 };
|
||||
}
|
153
src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue
Normal file
153
src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue
Normal file
|
@ -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>
|
Loading…
Reference in a new issue