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
					
				
							
								
								
									
										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 New Issue
	
	Block a user