lukegbcom: add EE QR Code fixer
This removes the trailing $$ from the QR codes generated by EE. Gah, why do they still do this.
This commit is contained in:
parent
58793004a2
commit
3d92896314
8 changed files with 259 additions and 2 deletions
|
@ -10,6 +10,22 @@ const nextConfig = {
|
||||||
loader: 'custom',
|
loader: 'custom',
|
||||||
disableStaticImages: true,
|
disableStaticImages: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
webpack(config) {
|
||||||
|
config.resolve.fallback = {
|
||||||
|
...config.resolve.fallback,
|
||||||
|
fs: false,
|
||||||
|
};
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.wasm(\.bin)?$/i,
|
||||||
|
type: 'asset/resource',
|
||||||
|
generator: {
|
||||||
|
filename: 'static/[hash][ext][query]'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = withPlugins([
|
module.exports = withPlugins([
|
||||||
|
|
|
@ -4414,6 +4414,15 @@ let
|
||||||
sha1 = "7e32f75b41381291d04611f1bf14109ac00651d7";
|
sha1 = "7e32f75b41381291d04611f1bf14109ac00651d7";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
"qrcode.react-3.0.1" = {
|
||||||
|
name = "qrcode.react";
|
||||||
|
packageName = "qrcode.react";
|
||||||
|
version = "3.0.1";
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.0.1.tgz";
|
||||||
|
sha512 = "uCNm16ClMCrdM2R20c/zqmdwHcbMQf3K7ey39EiK/UgEKbqWeM0iH2QxW3iDVFzjQKFzH23ICgOyG4gNsJ0/gw==";
|
||||||
|
};
|
||||||
|
};
|
||||||
"quantize-1.0.2" = {
|
"quantize-1.0.2" = {
|
||||||
name = "quantize";
|
name = "quantize";
|
||||||
packageName = "quantize";
|
packageName = "quantize";
|
||||||
|
@ -5449,6 +5458,15 @@ let
|
||||||
sha512 = "r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==";
|
sha512 = "r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
"zbar.wasm-2.1.1" = {
|
||||||
|
name = "zbar.wasm";
|
||||||
|
packageName = "zbar.wasm";
|
||||||
|
version = "2.1.1";
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/zbar.wasm/-/zbar.wasm-2.1.1.tgz";
|
||||||
|
sha512 = "iXX2tMFRRt2R4+mbKBhNa3TrN0fd5CbzLceasGTBOofA7eF1VvwZeAHW3UkEMDU8w3rSvwx9N3cyosX55UJ+Xw==";
|
||||||
|
};
|
||||||
|
};
|
||||||
"zwitch-2.0.2" = {
|
"zwitch-2.0.2" = {
|
||||||
name = "zwitch";
|
name = "zwitch";
|
||||||
packageName = "zwitch";
|
packageName = "zwitch";
|
||||||
|
@ -6094,6 +6112,7 @@ let
|
||||||
sources."pump-3.0.0"
|
sources."pump-3.0.0"
|
||||||
sources."punycode-2.1.1"
|
sources."punycode-2.1.1"
|
||||||
sources."q-1.5.1"
|
sources."q-1.5.1"
|
||||||
|
sources."qrcode.react-3.0.1"
|
||||||
sources."quantize-1.0.2"
|
sources."quantize-1.0.2"
|
||||||
(sources."rc-1.2.8" // {
|
(sources."rc-1.2.8" // {
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -6235,6 +6254,7 @@ let
|
||||||
sources."wrappy-1.0.2"
|
sources."wrappy-1.0.2"
|
||||||
sources."yallist-4.0.0"
|
sources."yallist-4.0.0"
|
||||||
sources."yaml-1.10.2"
|
sources."yaml-1.10.2"
|
||||||
|
sources."zbar.wasm-2.1.1"
|
||||||
sources."zwitch-2.0.2"
|
sources."zwitch-2.0.2"
|
||||||
];
|
];
|
||||||
buildInputs = globalBuildInputs;
|
buildInputs = globalBuildInputs;
|
||||||
|
|
28
web/lukegbcom/package-lock.json
generated
28
web/lukegbcom/package-lock.json
generated
|
@ -18,6 +18,7 @@
|
||||||
"next-compose-plugins": "^2.2.1",
|
"next-compose-plugins": "^2.2.1",
|
||||||
"next-mdx-remote": "^4.0.2",
|
"next-mdx-remote": "^4.0.2",
|
||||||
"next-optimized-images": "^3.0.0-canary.10",
|
"next-optimized-images": "^3.0.0-canary.10",
|
||||||
|
"qrcode.react": "^3.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"rehype-highlight": "^5.0.2",
|
"rehype-highlight": "^5.0.2",
|
||||||
|
@ -26,7 +27,8 @@
|
||||||
"remark-parse": "^10.0.1",
|
"remark-parse": "^10.0.1",
|
||||||
"remark-rehype": "^10.1.0",
|
"remark-rehype": "^10.1.0",
|
||||||
"sass": "^1.49.11",
|
"sass": "^1.49.11",
|
||||||
"unified": "^10.1.2"
|
"unified": "^10.1.2",
|
||||||
|
"zbar.wasm": "^2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "8.12.0",
|
"eslint": "8.12.0",
|
||||||
|
@ -7519,6 +7521,14 @@
|
||||||
"teleport": ">=0.2.0"
|
"teleport": ">=0.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qrcode.react": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-uCNm16ClMCrdM2R20c/zqmdwHcbMQf3K7ey39EiK/UgEKbqWeM0iH2QxW3iDVFzjQKFzH23ICgOyG4gNsJ0/gw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/quantize": {
|
"node_modules/quantize": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/quantize/-/quantize-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/quantize/-/quantize-1.0.2.tgz",
|
||||||
|
@ -9085,6 +9095,11 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/zbar.wasm": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zbar.wasm/-/zbar.wasm-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-iXX2tMFRRt2R4+mbKBhNa3TrN0fd5CbzLceasGTBOofA7eF1VvwZeAHW3UkEMDU8w3rSvwx9N3cyosX55UJ+Xw=="
|
||||||
|
},
|
||||||
"node_modules/zwitch": {
|
"node_modules/zwitch": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||||
|
@ -14394,6 +14409,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||||
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
|
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
|
||||||
},
|
},
|
||||||
|
"qrcode.react": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-uCNm16ClMCrdM2R20c/zqmdwHcbMQf3K7ey39EiK/UgEKbqWeM0iH2QxW3iDVFzjQKFzH23ICgOyG4gNsJ0/gw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"quantize": {
|
"quantize": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/quantize/-/quantize-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/quantize/-/quantize-1.0.2.tgz",
|
||||||
|
@ -15522,6 +15543,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
||||||
},
|
},
|
||||||
|
"zbar.wasm": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zbar.wasm/-/zbar.wasm-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-iXX2tMFRRt2R4+mbKBhNa3TrN0fd5CbzLceasGTBOofA7eF1VvwZeAHW3UkEMDU8w3rSvwx9N3cyosX55UJ+Xw=="
|
||||||
|
},
|
||||||
"zwitch": {
|
"zwitch": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"next-compose-plugins": "^2.2.1",
|
"next-compose-plugins": "^2.2.1",
|
||||||
"next-mdx-remote": "^4.0.2",
|
"next-mdx-remote": "^4.0.2",
|
||||||
"next-optimized-images": "^3.0.0-canary.10",
|
"next-optimized-images": "^3.0.0-canary.10",
|
||||||
|
"qrcode.react": "^3.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"rehype-highlight": "^5.0.2",
|
"rehype-highlight": "^5.0.2",
|
||||||
|
@ -27,7 +28,8 @@
|
||||||
"remark-parse": "^10.0.1",
|
"remark-parse": "^10.0.1",
|
||||||
"remark-rehype": "^10.1.0",
|
"remark-rehype": "^10.1.0",
|
||||||
"sass": "^1.49.11",
|
"sass": "^1.49.11",
|
||||||
"unified": "^10.1.2"
|
"unified": "^10.1.2",
|
||||||
|
"zbar.wasm": "^2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "8.12.0",
|
"eslint": "8.12.0",
|
||||||
|
|
|
@ -24,6 +24,9 @@ export default function Toolbox() {
|
||||||
<li>
|
<li>
|
||||||
<Link href="/tools/net"><a>Networking Tools</a></Link>
|
<Link href="/tools/net"><a>Networking Tools</a></Link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/tools/ee-qrcode"><a>EE QRCode Mangler</a></Link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
167
web/lukegbcom/pages/tools/ee-qrcode.js
Normal file
167
web/lukegbcom/pages/tools/ee-qrcode.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
|
import styles from '../../styles/Post.module.scss'
|
||||||
|
import localStyles from './ee-qrcode.module.scss'
|
||||||
|
import HeaderNav from '../../lib/HeaderNav'
|
||||||
|
import HeroImage from '../../lib/HeroImage'
|
||||||
|
|
||||||
|
let scannerWorker = null
|
||||||
|
|
||||||
|
async function startCapture(video, canvas, captureType) {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
let captureStream = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (captureType === 'screen')
|
||||||
|
captureStream = await navigator.mediaDevices.getDisplayMedia({ audio: false, video: true })
|
||||||
|
else
|
||||||
|
captureStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true })
|
||||||
|
} catch (e) {
|
||||||
|
alert(`Failed to start screen capture: ${e}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
video.srcObject = captureStream
|
||||||
|
video.play()
|
||||||
|
await new Promise(resolve => {
|
||||||
|
video.addEventListener('loadedmetadata', resolve)
|
||||||
|
})
|
||||||
|
canvas.width = video.videoWidth
|
||||||
|
canvas.height = video.videoHeight
|
||||||
|
|
||||||
|
return (() => {
|
||||||
|
captureStream.getTracks().forEach(track => track.stop())
|
||||||
|
video.srcObject = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scanImage(imageData) {
|
||||||
|
if (scannerWorker === null) {
|
||||||
|
scannerWorker = new Worker(new URL('./ee-qrcode.worker.js', import.meta.url))
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
scannerWorker.onmessage = e => resolve(e.data.response)
|
||||||
|
scannerWorker.postMessage({imageData})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processImage(video, hiddenCanvas, previewCanvas, filterSymbols, onResult) {
|
||||||
|
const hiddenCtx = hiddenCanvas.getContext('2d')
|
||||||
|
hiddenCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)
|
||||||
|
|
||||||
|
const allSymbols = await scanImage(hiddenCtx.getImageData(0, 0, video.videoWidth, video.videoHeight))
|
||||||
|
|
||||||
|
previewCanvas.width = video.videoWidth
|
||||||
|
previewCanvas.height = video.videoHeight
|
||||||
|
const previewCtx = previewCanvas.getContext('2d')
|
||||||
|
previewCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)
|
||||||
|
const symbols = allSymbols.filter(filterSymbols || (() => true))
|
||||||
|
previewCtx.strokeStyle = 'rgba(255, 0, 0, 0.7)'
|
||||||
|
previewCtx.lineWidth = 6
|
||||||
|
symbols.map((sym) => {
|
||||||
|
if (sym.points < 2) return
|
||||||
|
const [firstPoint, ...points] = sym.points
|
||||||
|
|
||||||
|
previewCtx.beginPath()
|
||||||
|
previewCtx.moveTo(firstPoint.x, firstPoint.y)
|
||||||
|
points.forEach(point => previewCtx.lineTo(point.x, point.y))
|
||||||
|
previewCtx.closePath()
|
||||||
|
|
||||||
|
previewCtx.stroke()
|
||||||
|
})
|
||||||
|
if (symbols.length > 0) {
|
||||||
|
onResult(symbols[0].value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runScanner({ video, canvas, previewCanvas, filterSymbols, onResult, running, captureType }) {
|
||||||
|
const stopCapture = await startCapture(video, canvas, captureType)
|
||||||
|
|
||||||
|
while (running()) {
|
||||||
|
await processImage(video, canvas, previewCanvas, filterSymbols, onResult)
|
||||||
|
await sleep(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCapture()
|
||||||
|
}
|
||||||
|
|
||||||
|
function CodeScanner({ filterSymbols, onResult, captureType }) {
|
||||||
|
const videoRef = useRef(null)
|
||||||
|
const canvasRef = useRef(null)
|
||||||
|
const previewCanvasRef = useRef(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let running = true;
|
||||||
|
if (videoRef.current && canvasRef.current && previewCanvasRef.current) {
|
||||||
|
runScanner({ video: videoRef.current, canvas: canvasRef.current, previewCanvas: previewCanvasRef.current, running: (() => running), filterSymbols, onResult, captureType })
|
||||||
|
}
|
||||||
|
return (() => {
|
||||||
|
running = false;
|
||||||
|
});
|
||||||
|
}, [videoRef, canvasRef, previewCanvasRef, captureType])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<video ref={videoRef} className={localStyles.video} />
|
||||||
|
<canvas ref={canvasRef} className={localStyles.video} />
|
||||||
|
<canvas ref={previewCanvasRef} className={localStyles.canvas} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function esimQrcodeFilter(symbol) {
|
||||||
|
if (symbol.typeName !== 'ZBAR_QRCODE') return false
|
||||||
|
if (!symbol.value.match(/^LPA:/)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixData(srcData) {
|
||||||
|
return srcData.replace(/[$][$]$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EsimMangler() {
|
||||||
|
const [running, setRunning] = useState(false)
|
||||||
|
const [scannedData, setScannedData] = useState(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<HeaderNav />
|
||||||
|
<HeroImage image="https://farm9.staticflickr.com/8218/8437956869_05a4e887b0_k_d.jpg" credit={{ url: "https://www.flickr.com/photos/npobre/8437956869/", text: "Norlando Pobre, Flickr" }} withGradient="top-blue">
|
||||||
|
EE QR Code Mangler
|
||||||
|
</HeroImage>
|
||||||
|
<Head>
|
||||||
|
<title>EE QR Code Mangler | Luke Granger-Brown</title>
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.post}>
|
||||||
|
<h1>EE eSIM QR Code Fixer</h1>
|
||||||
|
<p>EE, as of 2022-05-01, still generates not-quite-spec-compliant eSIM QR Codes, which get rejected by some Android devices - including the Pixel series of devices.</p>
|
||||||
|
<p>This page is intended to generate a fixed version of the QR code (which happens entirely on your computer!).</p>
|
||||||
|
<p>{running ? <button onClick={() => setRunning(false)}>Stop Capture</button> : (
|
||||||
|
<span>
|
||||||
|
<button onClick={() => setRunning('screen')}>Start Screen Capture</button>
|
||||||
|
<button onClick={() => setRunning('camera')}>Start Webcam Capture</button>
|
||||||
|
</span>
|
||||||
|
)}</p>
|
||||||
|
{(scannedData !== null) ? <p><QRCodeSVG value={fixData(scannedData)} /></p> : null}
|
||||||
|
{(scannedData !== null) ? <p><tt>{fixData(scannedData)}</tt></p> : null}
|
||||||
|
{(running != false) ? <CodeScanner filterSymbols={esimQrcodeFilter} onResult={(result) => {
|
||||||
|
setScannedData(result)
|
||||||
|
setRunning(false)
|
||||||
|
}} captureType={running} /> : null}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
10
web/lukegbcom/pages/tools/ee-qrcode.module.scss
Normal file
10
web/lukegbcom/pages/tools/ee-qrcode.module.scss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.video {
|
||||||
|
position: absolute;
|
||||||
|
left: -10000px;
|
||||||
|
top: -10000px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
13
web/lukegbcom/pages/tools/ee-qrcode.worker.js
Normal file
13
web/lukegbcom/pages/tools/ee-qrcode.worker.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { scanImageData } from 'zbar.wasm'
|
||||||
|
|
||||||
|
self.addEventListener('message', async e => {
|
||||||
|
const symbols = await scanImageData(e.data.imageData)
|
||||||
|
self.postMessage({
|
||||||
|
response: symbols.map((sym, n) => ({
|
||||||
|
index: n,
|
||||||
|
typeName: sym.typeName,
|
||||||
|
value: sym.decode(),
|
||||||
|
points: sym.points,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue