Merge pull request #19 from covidpass-org/dev

Added a build int QR-Code reader. Support upload QR-Codes as PNG
This commit is contained in:
Sören Busse 2021-06-30 03:25:38 +02:00 committed by GitHub
commit 55339efe6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 894 additions and 4890 deletions

View File

@ -23,8 +23,7 @@ RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001 RUN adduser -S nextjs -u 1001
# You only need to copy next.config.js if you are NOT using the default configuration # You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/next.config.js ./ # COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/server.js ./
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/node_modules ./node_modules

View File

@ -4,8 +4,6 @@
Web app for adding EU COVID-19 Vaccination Certificates to your wallets Web app for adding EU COVID-19 Vaccination Certificates to your wallets
The API can be found [here](https://github.com/marvinsxtr/covidpass-api).
## Setup ## Setup
```sh ```sh

View File

@ -1,22 +1,24 @@
export default Card export default Card
function Card({ heading, content, step }) { function Card({heading, content, step}) {
return ( return (
<div className="rounded-md p-6 bg-white dark:bg-gray-800 space-y-4"> <div className="rounded-md p-6 bg-white dark:bg-gray-800 space-y-4">
{step && {
<div className="flex flex-row items-center"> step &&
<div className="rounded-full p-4 bg-green-600 h-5 w-5 flex items-center justify-center"> <div className="flex flex-row items-center">
<p className="text-white text-lg font-bold"> <div className="rounded-full p-4 bg-green-600 h-5 w-5 flex items-center justify-center">
{step} <p className="text-white text-lg font-bold">
</p> {step}
</div> </p>
<div className="ml-3 font-bold text-xl"> </div>
{heading} <div className="ml-3 font-bold text-xl">
</div> {heading}
</div>} </div>
<div className="text-lg"> </div>
{content} }
</div> <div className="text-lg">
</div> {content}
) </div>
</div>
)
} }

View File

@ -1,190 +1,304 @@
const PDFJS = require('pdfjs-dist')
PDFJS.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS.version}/pdf.worker.js`
import jsQR from "jsqr"
import {saveAs} from 'file-saver' import {saveAs} from 'file-saver'
import {BrowserQRCodeReader} from '@zxing/browser'
import React, {useEffect, useRef, useState} from "react"
import {decodeData} from "../src/decode" import {decodeData} from "../src/decode"
import {processPdf, processPng} from "../src/process"
import {createPass} from "../src/pass" import {createPass} from "../src/pass"
import Card from "../components/Card" import Card from "../components/Card"
import Alert from "../components/Alert" import Alert from "../components/Alert"
import jsQR from "jsqr";
export default Form export default Form
function Form() { function Form() {
function readFileAsync(file) { function readFileAsync(file) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let reader = new FileReader(); let reader = new FileReader();
reader.onload = () => { reader.onload = () => {
resolve(reader.result); resolve(reader.result);
}; };
reader.onerror = reject; reader.onerror = reject;
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}) })
}
const error = function(heading, message) {
const alert = document.getElementById('alert')
alert.setAttribute('style', null)
document.getElementById('heading').innerHTML = heading
document.getElementById('message').innerHTML = message
document.getElementById('spin').style.display = 'none'
}
const processPdf = async function() {
document.getElementById('spin').style.display = 'block'
const file = document.getElementById('pdf').files[0]
const result = await readFileAsync(file)
let typedArray = new Uint8Array(result)
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
let 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,
})
return await renderTask.promise
})
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
let 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')
}
return {decoded: decoded, raw: rawData}
} else {
error('No QR code found', 'Try scanning the PDF again')
}
}
const addToWallet = async function(event) {
event.preventDefault()
let result
try {
result = await processPdf()
} catch {
error('Error:', 'Could not extract QR code data from PDF')
} }
if (typeof result === 'undefined') { const error = function (heading, message) {
return const alert = document.getElementById('alert')
alert.setAttribute('style', null)
document.getElementById('heading').innerHTML = heading
document.getElementById('message').innerHTML = message
document.getElementById('spin').style.display = 'none'
} }
const color = document.getElementById('color').value const processFile = async function () {
console.log(qrCode)
try { console.log(file)
const pass = await createPass( if (!qrCode && !file) {
{ error("Error", "Please capture a QR Code or select a file to scan");
decoded: result.decoded, return;
raw: result.raw,
color: color
} }
)
if (!pass) { document.getElementById('spin').style.display = 'block'
error('Error:', "Something went wrong.")
} else { let rawData;
const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
saveAs(passBlob, 'covid.pkpass') if (file) {
} let imageData
} catch (e) { const fileBuffer = await readFileAsync(file)
error('Error:', e.message)
} finally { switch (file.type) {
document.getElementById('spin').style.display = 'none' case 'application/pdf':
console.log('pdf')
imageData = await processPdf(fileBuffer)
break
case 'image/png':
console.log('png')
imageData = await processPng(fileBuffer)
break
default:
error('Error', 'Invalid file type')
return
}
let code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'dontInvert',
})
rawData = code.data;
} else {
rawData = qrCode.getText()
}
if (rawData) {
let decoded
try {
decoded = decodeData(rawData)
} catch (e) {
error('Invalid QR code found', 'Try another method to select your certificate')
return;
}
return {decoded: decoded, raw: rawData}
} else {
error('No QR code found', 'Try another method to select your certificate')
}
} }
}
return ( const addToWallet = async function (event) {
<div> event.preventDefault()
<form className="space-y-5" id="form" action="https://api.covidpass.marvinsextro.de/covid.pkpass" method="POST" onSubmit={(e) => addToWallet(e)}>
<Card step={1} heading="Select Certificate" content={ let result
<div className="space-y-5">
<p> try {
Please select the (scanned) certificate PDF page, which you received from your doctor, pharmacy, vaccination centre or online. result = await processFile()
</p> } catch (e) {
<input error('Error:', 'Could not extract QR code data from certificate')
className="w-full" }
type="file"
id="pdf" if (typeof result === 'undefined') {
accept="application/pdf" return
required }
/>
</div> const color = document.getElementById('color').value
} />
<Card step={2} heading="Pick a Color" content={ try {
<div className="relative inline-block w-full"> const pass = await createPass(
<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"> {
<option value="white">white</option> decoded: result.decoded,
<option value="black">black</option> raw: result.raw,
<option value="grey">grey</option> color: color
<option value="green">green</option> }
<option value="indigo">indigo</option> )
<option value="blue">blue</option>
<option value="purple">purple</option> if (!pass) {
<option value="teal">teal</option> error('Error:', "Something went wrong.")
</select> } else {
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none"> const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
<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"/></svg> saveAs(passBlob, 'covid.pkpass')
</div> }
</div> } catch (e) {
} /> error('Error:', e.message)
<Card step={3} heading="Add to Wallet" content={ } finally {
<div className="space-y-5"> document.getElementById('spin').style.display = 'none'
<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> const [isCameraOpen, setIsCameraOpen] = useState(false);
<label htmlFor="privacy" className="flex flex-row space-x-4 items-center"> const [globalControls, setGlobalControls] = useState(undefined);
<input type="checkbox" id="privacy" value="privacy" required className="h-4 w-4" /> const [qrCode, setQrCode] = useState(undefined);
<p> const [file, setFile] = useState(undefined);
I accept the <a href="/privacy" className="underline">Privacy Policy</a>
</p> const inputFile = useRef(undefined)
</label>
<div className="flex flex-row items-center justify-start"> useEffect(() => {
<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"> if (inputFile && inputFile.current) {
Add to Wallet inputFile.current.addEventListener('input', () => {
</button> let selectedFile = inputFile.current.files[0];
<div id="spin" style={{ "display": "none" }}> if (selectedFile !== undefined) {
<svg className="animate-spin h-5 w-5 ml-2" viewBox="0 0 24 24"> setQrCode(undefined);
<circle className="opacity-0" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/> setFile(selectedFile);
<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"/> }
</svg> });
</div> }
</div> }, [inputFile])
</div>
} /> async function showFileDialog() {
</form> inputFile.current.click();
<Alert />
<canvas id="canvas" style={{ display: "none" }} /> }
</div>
)
async function hideCameraView() {
if (globalControls !== undefined) {
globalControls.stop();
}
setIsCameraOpen(false);
}
async function showCameraView() {
const codeReader = new BrowserQRCodeReader();
// Needs to be called before any camera can be accessed
await BrowserQRCodeReader.listVideoInputDevices();
// Get preview Element to show camera stream
const previewElem = document.querySelector('#cameraPreview');
setGlobalControls(await codeReader.decodeFromVideoDevice(undefined, previewElem, (result, error, controls) => {
if (result !== undefined) {
setQrCode(result);
setFile(undefined);
controls.stop();
// Reset
setGlobalControls(undefined);
setIsCameraOpen(false);
}
}));
setIsCameraOpen(true);
}
return (
<div>
<form className="space-y-5" id="form" onSubmit={(e) => addToWallet(e)}>
<Card step={1} heading="Select Certificate" content={
<div className="space-y-5">
<p>
Please select the certificate screenshot or (scanned) PDF page, which you received from your
doctor, pharmacy, vaccination centre or online. Note that taking a picture does not work on
most devices yet.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<button
type="button"
onClick={isCameraOpen ? hideCameraView : showCameraView}
className="focus:outline-none h-20 bg-gray-500 hover:bg-gray-700 text-white font-semibold rounded-md">
{isCameraOpen ? "Stop Camera" : "Start Camera"}
</button>
<button
type="button"
onClick={showFileDialog}
className="focus:outline-none h-20 bg-gray-500 hover:bg-gray-700 text-white font-semibold rounded-md">
Open File (PDF, PNG)
</button>
</div>
<video id="cameraPreview" className={`${isCameraOpen ? undefined : "hidden"} rounded-md w-full`}/>
<input type='file'
id='file'
accept="application/pdf,image/png"
ref={inputFile}
style={{display: 'none'}}
/>
{(qrCode || file) &&
<div className="flex items-center space-x-1">
<svg className="h-4 w-4 text-green-600" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7"/>
</svg>
<span className="w-full">
{
qrCode && 'Found QR Code!'
}
{
file && file.name
}
</span>
</div>}
</div>
}/>
<Card step={2} heading="Pick a Color" content={
<div className="space-y-5">
<p>
Pick a background color for your pass.
</p>
<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">
<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>
<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"/>
</svg>
</div>
</div>
</div>
}/>
<Card step={3} heading="Add to Wallet" content={
<div className="space-y-5">
<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>
<label htmlFor="privacy" className="flex flex-row space-x-4 items-center">
<input type="checkbox" id="privacy" value="privacy" required className="h-4 w-4"/>
<p>
I accept the <a href="/privacy" className="underline">Privacy Policy</a>
</p>
</label>
<div className="flex flex-row items-center justify-start">
<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">
Add to Wallet
</button>
<div id="spin" style={{"display": "none"}}>
<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"/>
<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"/>
</svg>
</div>
</div>
</div>
}/>
</form>
<Alert/>
<canvas id="canvas" style={{display: "none"}}/>
</div>
)
} }

View File

@ -1,17 +1,26 @@
import Icon from '../public/favicon.svg'
import Link from 'next/link' import Link from 'next/link'
export default Logo export default Logo
function Logo() { function Logo() {
return ( return (
<Link href="/"> <Link href="/">
<a className="flex flex-row items-center p-3 justify-center space-x-1" > <a className="flex flex-row items-center p-3 justify-center space-x-1">
<Icon className="fill-current" /> <svg className="fill-current" xmlns="http://www.w3.org/2000/svg" enableBackground="new 0 0 24 24"
<h1 className="text-3xl font-bold"> height="48px"
CovidPass viewBox="0 0 24 24" width="48px" fill="#000000">
</h1> <g>
</a> <path d="M0,0h24v24H0V0z" fill="none"/>
</Link> </g>
) <g>
<path
d="M11.3,2.26l-6,2.25C4.52,4.81,4,5.55,4,6.39v4.71c0,5.05,3.41,9.76,8,10.91c4.59-1.15,8-5.86,8-10.91V6.39 c0-0.83-0.52-1.58-1.3-1.87l-6-2.25C12.25,2.09,11.75,2.09,11.3,2.26z M10.23,14.83l-2.12-2.12c-0.39-0.39-0.39-1.02,0-1.41l0,0 c0.39-0.39,1.02-0.39,1.41,0l1.41,1.41l3.54-3.54c0.39-0.39,1.02-0.39,1.41,0l0,0c0.39,0.39,0.39,1.02,0,1.41l-4.24,4.24 C11.26,15.22,10.62,15.22,10.23,14.83z"/>
</g>
</svg>
<h1 className="text-3xl font-bold">
CovidPass
</h1>
</a>
</Link>
)
} }

View File

@ -9,7 +9,7 @@ function Page({ content }) {
<div className="md:w-2/3 xl:w-2/5 md:mx-auto flex flex-col min-h-screen justify-center px-5 py-12"> <div className="md:w-2/3 xl:w-2/5 md:mx-auto flex flex-col min-h-screen justify-center px-5 py-12">
<Head> <Head>
<title>CovidPass</title> <title>CovidPass</title>
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<div> <div>
<main className="flex flex-col space-y-5"> <main className="flex flex-col space-y-5">
@ -18,11 +18,11 @@ function Page({ content }) {
{content} {content}
<footer> <footer>
<nav className="nav flex space-x-4 m-6 flex-row-reverse space-x-reverse text-md font-bold"> <nav className="nav flex pt-4 flex-row space-x-4 justify-center text-md font-bold">
<Link href="/privacy"><a className="hover:underline" >Privacy Policy</a></Link>
<Link href="/imprint"><a className="hover:underline" >Imprint</a></Link>
<a href="https://www.paypal.com/paypalme/msextro" className="hover:underline" >Donate</a> <a href="https://www.paypal.com/paypalme/msextro" className="hover:underline" >Donate</a>
<a href="https://github.com/marvinsxtr/covidpass" className="hover:underline" >GitHub</a> <a href="https://github.com/marvinsxtr/covidpass" className="hover:underline" >GitHub</a>
<Link href="/privacy"><a className="hover:underline" >Privacy Policy</a></Link>
<Link href="/imprint"><a className="hover:underline" >Imprint</a></Link>
</nav> </nav>
</footer> </footer>
</main> </main>

View File

@ -15,12 +15,12 @@ export default {
additionalLinkTags: [ additionalLinkTags: [
{ {
rel: 'icon', rel: 'icon',
href: 'https://covidpass.marvinsextro.de/favicon.png', href: 'https://covidpass.marvinsextro.de/favicon.ico',
}, },
{ {
rel: 'apple-touch-icon', rel: 'apple-touch-icon',
href: 'https://covidpass.marvinsextro.de/favicon.png', href: 'https://covidpass.marvinsextro.de/apple-touch-icon.png',
sizes: '96x96' sizes: '180x180'
}, },
] ]
} }

View File

@ -1,9 +0,0 @@
module.exports = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
},
}

5114
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,27 +4,30 @@
"author": "Marvin Sextro <marvin.sextro@gmail.com>", "author": "Marvin Sextro <marvin.sextro@gmail.com>",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node server.js", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "NODE_ENV=production node server.js" "start": "next start"
}, },
"dependencies": { "dependencies": {
"@zxing/browser": "^0.0.9",
"@zxing/library": "^0.18.6",
"base45-js": "^1.0.1", "base45-js": "^1.0.1",
"cbor-js": "^0.1.0", "cbor-js": "^0.1.0",
"do-not-zip": "^1.0.0", "do-not-zip": "^1.0.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jpeg-js": "^0.4.3",
"jsqr": "^1.4.0", "jsqr": "^1.4.0",
"next": "latest", "next": "latest",
"next-seo": "^4.26.0", "next-seo": "^4.26.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"pdfjs-dist": "^2.5.207", "pdfjs-dist": "^2.5.207",
"pngjs": "^6.0.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"webpack": "^5.0.0", "webpack": "^5.0.0",
"worker-loader": "^3.0.7" "worker-loader": "^3.0.7"
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^5.5.0",
"autoprefixer": "^10.0.4", "autoprefixer": "^10.0.4",
"postcss": "^8.1.10", "postcss": "^8.1.10",
"tailwindcss": "^2.1.1" "tailwindcss": "^2.1.1"

View File

@ -1,14 +1,13 @@
import Document, { Html, Head, Main, NextScript } from 'next/document' import Document, {Html, Head, Main, NextScript} from 'next/document'
class CustomDocument extends Document { class CustomDocument extends Document {
render() { render() {
return ( return (
<Html lang="en"> <Html lang="en">
<Head /> <Head/>
<body className="bg-gray-200 dark:bg-gray-900 text-gray-800 dark:text-white"> <body className="bg-gray-200 dark:bg-gray-900 text-gray-800 dark:text-white">
<Main /> <Main/>
<NextScript /> <NextScript/>
</body> </body>
</Html> </Html>
) )

View File

@ -17,10 +17,16 @@ export default function Home() {
images: [ images: [
{ {
url: 'https://covidpass.marvinsextro.de/thumbnail.png', url: 'https://covidpass.marvinsextro.de/thumbnail.png',
width: 611, width: 1000,
height: 318, height: 500,
alt: 'CovidPass: Add your EU Digital Covid Vaccination Certificates to your favorite wallet app.', alt: 'CovidPass: Add your EU Digital Covid Vaccination Certificates to your favorite wallet app.',
}, },
{
url: 'https://covidpass.marvinsextro.de/favicon.png',
width: 500,
height: 500,
alt: 'CovidPass',
},
], ],
site_name: 'CovidPass', site_name: 'CovidPass',
}} }}

View File

@ -13,7 +13,7 @@ export default function Privacy() {
<div className="px-4"> <div className="px-4">
<ul className="list-disc"> <ul className="list-disc">
<li> <li>
The whole process of generating the pass file happens locally in your browser. For the signing step, ony a hashed representation of your data is sent to the server. The whole process of generating the pass file happens locally in your browser. For the signing step, only a hashed representation of your data is sent to the server.
</li> </li>
<li> <li>
Your data is not stored beyond the active browser session and the site does not use cookies. Your data is not stored beyond the active browser session and the site does not use cookies.
@ -34,7 +34,7 @@ export default function Privacy() {
By default, Apple Wallet passes are accessible from the lock screen. This can be changed in the <a href="https://support.apple.com/de-de/guide/iphone/iph9a2a69136/ios" className="underline">settings</a>. By default, Apple Wallet passes are accessible from the lock screen. This can be changed in the <a href="https://support.apple.com/de-de/guide/iphone/iph9a2a69136/ios" className="underline">settings</a>.
</li> </li>
<li> <li>
The server provider processes data to provide this site. In order to better understand what measures they take to protect your data, please also read their <a href="https://www.hetzner.com/de/rechtliches/datenschutz/" className="underline">privacy policy</a> and the <a href="https://docs.hetzner.com/general/general-terms-and-conditions/data-privacy-faq/" className="underline">data privacy FAQ</a> The server provider processes data to provide this site. In order to better understand what measures they take to protect your data, please also read their <a href="https://www.hetzner.com/de/rechtliches/datenschutz/" className="underline">privacy policy</a> and the <a href="https://docs.hetzner.com/general/general-terms-and-conditions/data-privacy-faq/" className="underline">data privacy FAQ</a>.
</li> </li>
</ul> </ul>
</div> </div>
@ -78,7 +78,7 @@ export default function Privacy() {
</p> </p>
<div className="px-4"> <div className="px-4">
<ul className="list-disc"> <ul className="list-disc">
<li>Assembling the signed pass file out of the inclomplete file generated locally and the signature</li> <li>Assembling the signed pass file out of the incomplete file generated locally and the signature</li>
<li>Saving the file on your device</li> <li>Saving the file on your device</li>
</ul> </ul>
</div> </div>

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="48px" viewBox="0 0 24 24" width="48px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><path d="M11.3,2.26l-6,2.25C4.52,4.81,4,5.55,4,6.39v4.71c0,5.05,3.41,9.76,8,10.91c4.59-1.15,8-5.86,8-10.91V6.39 c0-0.83-0.52-1.58-1.3-1.87l-6-2.25C12.25,2.09,11.75,2.09,11.3,2.26z M10.23,14.83l-2.12-2.12c-0.39-0.39-0.39-1.02,0-1.41l0,0 c0.39-0.39,1.02-0.39,1.41,0l1.41,1.41l3.54-3.54c0.39-0.39,1.02-0.39,1.41,0l0,0c0.39,0.39,0.39,1.02,0,1.41l-4.24,4.24 C11.26,15.22,10.62,15.22,10.23,14.83z"/></g></svg>

Before

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1,25 +0,0 @@
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/a', query)
} else if (pathname === '/b') {
app.render(req, res, '/b', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})

57
src/process.js Normal file
View File

@ -0,0 +1,57 @@
import {PNG} from 'pngjs'
import * as PdfJS from 'pdfjs-dist'
PdfJS.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PdfJS.version}/pdf.worker.js`
// Processes a pdf file and returns it as ImageData
export async function processPdf(file) {
// Array to store ImageData information
const typedArray = new Uint8Array(file)
// PDF scale, increase to read smaller QR Codes
const pdfScale = 2;
// Get the canvas and context to render PDF
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
let loadingTask = PdfJS.getDocument(typedArray)
await loadingTask.promise.then(async function (pdfDocument) {
// Load last PDF page
const pageNumber = pdfDocument.numPages;
const pdfPage = await pdfDocument.getPage(pageNumber)
const viewport = pdfPage.getViewport({scale: pdfScale})
// Set correct canvas width / height
canvas.width = viewport.width
canvas.height = viewport.height
// render PDF
const renderTask = pdfPage.render({
canvasContext: ctx,
viewport,
})
return await renderTask.promise
})
// Return PDF Image Data
return ctx.getImageData(0, 0, canvas.width, canvas.height)
}
// Processes a PNG File and returns it as ImageData
export async function processPng(file) {
return new Promise(async (resolve, reject) => {
let png = new PNG({filterType: 4})
png.parse(file, (error, data) => {
if (error) {
reject()
}
resolve(data)
})
})
}