2021-06-25 12:18:25 +02:00
|
|
|
const PDFJS = require('pdfjs-dist')
|
|
|
|
PDFJS.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS.version}/pdf.worker.js`
|
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
import jsQR from "jsqr"
|
|
|
|
import { saveAs } from 'file-saver'
|
2021-06-25 12:18:25 +02:00
|
|
|
|
|
|
|
import { decodeData } from "../src/decode"
|
2021-06-28 02:29:29 +02:00
|
|
|
import { createPass } from "../src/pass"
|
2021-06-25 12:18:25 +02:00
|
|
|
import Card from "../components/Card"
|
2021-06-26 00:28:35 +02:00
|
|
|
import Alert from "../components/Alert"
|
2021-06-25 12:18:25 +02:00
|
|
|
|
|
|
|
export default Form
|
|
|
|
|
|
|
|
function Form() {
|
2021-06-26 00:28:35 +02:00
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
function readFileAsync(file) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let reader = new FileReader();
|
|
|
|
|
|
|
|
reader.onload = () => {
|
|
|
|
resolve(reader.result);
|
|
|
|
};
|
|
|
|
|
|
|
|
reader.onerror = reject;
|
|
|
|
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-06-26 00:28:35 +02:00
|
|
|
const error = function(heading, message) {
|
|
|
|
const alert = document.getElementById('alert')
|
|
|
|
alert.setAttribute('style', null)
|
|
|
|
|
|
|
|
document.getElementById('heading').innerHTML = heading
|
|
|
|
document.getElementById('message').innerHTML = message
|
2021-06-26 21:53:08 +02:00
|
|
|
|
|
|
|
document.getElementById('spin').style.display = 'none'
|
2021-06-26 00:28:35 +02:00
|
|
|
}
|
|
|
|
|
2021-06-26 14:22:53 +02:00
|
|
|
const processPdf = async function() {
|
|
|
|
document.getElementById('spin').style.display = 'block'
|
|
|
|
|
|
|
|
const file = document.getElementById('pdf').files[0]
|
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
const result = await readFileAsync(file)
|
|
|
|
var typedarray = new Uint8Array(result)
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
const canvas = document.getElementById('canvas')
|
|
|
|
const ctx = canvas.getContext('2d')
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
var loadingTask = PDFJS.getDocument(typedarray)
|
|
|
|
await loadingTask.promise.then(async function (pdfDocument) {
|
|
|
|
const pdfPage = await pdfDocument.getPage(1)
|
|
|
|
const viewport = pdfPage.getViewport({ scale: 1 })
|
|
|
|
canvas.width = viewport.width
|
|
|
|
canvas.height = viewport.height
|
|
|
|
|
|
|
|
const renderTask = pdfPage.render({
|
|
|
|
canvasContext: ctx,
|
|
|
|
viewport,
|
2021-06-25 12:18:25 +02:00
|
|
|
})
|
2021-06-26 21:53:08 +02:00
|
|
|
|
|
|
|
return await renderTask.promise
|
|
|
|
})
|
|
|
|
|
|
|
|
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
|
|
var code = jsQR(imageData.data, imageData.width, imageData.height, {
|
|
|
|
inversionAttempts: 'dontInvert',
|
|
|
|
})
|
|
|
|
|
|
|
|
if (code) {
|
|
|
|
const rawData = code.data
|
|
|
|
let decoded
|
|
|
|
|
|
|
|
try {
|
|
|
|
decoded = decodeData(rawData)
|
|
|
|
} catch (error) {
|
|
|
|
error('Invalid QR code found', 'Make sure that you picked the correct PDF')
|
|
|
|
}
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
const result = { decoded: decoded, raw: rawData }
|
|
|
|
|
|
|
|
return result
|
|
|
|
} else {
|
|
|
|
error('No QR code found', 'Try scanning the PDF again')
|
|
|
|
}
|
|
|
|
}
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
const addToWallet = async function(event) {
|
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
let result
|
|
|
|
|
|
|
|
try {
|
|
|
|
result = await processPdf()
|
|
|
|
} catch {
|
|
|
|
error('Error:', 'Could not extract QR code data from PDF')
|
|
|
|
}
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
if (typeof result === 'undefined') {
|
|
|
|
return
|
|
|
|
}
|
2021-06-25 12:18:25 +02:00
|
|
|
|
2021-06-26 21:53:08 +02:00
|
|
|
const color = document.getElementById('color').value
|
2021-06-28 02:29:29 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
const pass = await createPass(
|
|
|
|
{
|
2021-06-26 21:53:08 +02:00
|
|
|
decoded: result.decoded,
|
|
|
|
raw: result.raw,
|
|
|
|
color: color
|
2021-06-28 02:29:29 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!pass) {
|
|
|
|
error('Error:', "Something went wrong.")
|
|
|
|
} else {
|
|
|
|
const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
|
|
|
|
saveAs(passBlob, 'covid.pkpass')
|
2021-06-25 12:18:25 +02:00
|
|
|
}
|
2021-06-28 02:29:29 +02:00
|
|
|
} catch (e) {
|
2021-06-26 21:53:08 +02:00
|
|
|
error('Error:', error.message)
|
2021-06-28 02:29:29 +02:00
|
|
|
} finally {
|
2021-06-26 14:22:53 +02:00
|
|
|
document.getElementById('spin').style.display = 'none'
|
2021-06-28 02:29:29 +02:00
|
|
|
}
|
2021-06-25 12:18:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
2021-06-28 18:17:16 +02:00
|
|
|
<form className="space-y-5" id="form" action="https://api.covidpass.marvinsextro.de/covid.pkpass" method="POST" onSubmit={(e) => addToWallet(e)}>
|
2021-06-25 12:18:25 +02:00
|
|
|
<Card step={1} heading="Select Certificate" content={
|
|
|
|
<div className="space-y-5">
|
|
|
|
<p>
|
2021-06-26 00:28:35 +02:00
|
|
|
Please select the (scanned) certificate PDF page, which you received from your doctor, pharmacy, vaccination centre or online.
|
2021-06-25 12:18:25 +02:00
|
|
|
</p>
|
|
|
|
<input
|
2021-06-28 18:17:16 +02:00
|
|
|
className="w-full"
|
2021-06-25 12:18:25 +02:00
|
|
|
type="file"
|
|
|
|
id="pdf"
|
|
|
|
accept="application/pdf"
|
|
|
|
required
|
|
|
|
/>
|
|
|
|
</div>
|
2021-06-28 18:17:16 +02:00
|
|
|
} />
|
2021-06-26 00:28:35 +02:00
|
|
|
<Card step={2} heading="Pick a Color" content={
|
2021-06-28 18:17:16 +02:00
|
|
|
<div className="relative inline-block w-full">
|
|
|
|
<select name="color" id="color" className="bg-gray-200 dark:bg-gray-900 focus:outline-none w-full h-10 pl-3 pr-6 text-base rounded-md appearance-none cursor-pointer">
|
2021-06-26 00:28:35 +02:00
|
|
|
<option value="white">white</option>
|
|
|
|
<option value="black">black</option>
|
|
|
|
<option value="grey">grey</option>
|
|
|
|
<option value="green">green</option>
|
|
|
|
<option value="indigo">indigo</option>
|
|
|
|
<option value="blue">blue</option>
|
|
|
|
<option value="purple">purple</option>
|
|
|
|
<option value="teal">teal</option>
|
|
|
|
</select>
|
2021-06-28 18:17:16 +02:00
|
|
|
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
|
|
|
<svg className="w-5 h-5 fill-current" viewBox="0 0 20 20"><path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" fillRule="evenodd"></path></svg>
|
|
|
|
</div>
|
2021-06-26 00:28:35 +02:00
|
|
|
</div>
|
2021-06-28 18:17:16 +02:00
|
|
|
} />
|
2021-06-26 00:28:35 +02:00
|
|
|
<Card step={3} heading="Add to Wallet" content={
|
2021-06-25 12:18:25 +02:00
|
|
|
<div className="space-y-5">
|
2021-06-26 14:22:53 +02:00
|
|
|
<p>
|
|
|
|
Data privacy is of special importance when processing health-related data.
|
|
|
|
In order for you to make an informed decision, please read the <a href="/privacy">Privacy Policy</a>.
|
|
|
|
</p>
|
2021-06-26 00:28:35 +02:00
|
|
|
<label htmlFor="privacy" className="flex flex-row space-x-4 items-center">
|
2021-06-28 18:17:16 +02:00
|
|
|
<input type="checkbox" id="privacy" value="privacy" required className="h-4 w-4" />
|
2021-06-25 12:18:25 +02:00
|
|
|
<p>
|
2021-06-26 14:22:53 +02:00
|
|
|
I accept the <a href="/privacy" className="underline">Privacy Policy</a>
|
2021-06-25 12:18:25 +02:00
|
|
|
</p>
|
|
|
|
</label>
|
2021-06-26 14:22:53 +02:00
|
|
|
<div className="flex flex-row items-center justify-start">
|
2021-06-28 18:17:16 +02:00
|
|
|
<button id="download" type="download" className="focus:outline-none bg-green-600 py-2 px-3 text-white font-semibold rounded-md disabled:bg-gray-400">
|
2021-06-26 14:22:53 +02:00
|
|
|
Add to Wallet
|
|
|
|
</button>
|
2021-06-28 18:17:16 +02:00
|
|
|
<div id="spin" style={{ "display": "none" }}>
|
2021-06-26 14:22:53 +02:00
|
|
|
<svg className="animate-spin h-5 w-5 ml-2" viewBox="0 0 24 24">
|
|
|
|
<circle className="opacity-0" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
|
|
</svg>
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-06-25 12:18:25 +02:00
|
|
|
</div>
|
2021-06-28 18:17:16 +02:00
|
|
|
} />
|
2021-06-25 12:18:25 +02:00
|
|
|
</form>
|
2021-06-26 14:22:53 +02:00
|
|
|
<Alert />
|
2021-06-28 18:17:16 +02:00
|
|
|
<canvas id="canvas" style={{ display: "none" }} />
|
2021-06-25 12:18:25 +02:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|