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
# 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/server.js ./
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
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
The API can be found [here](https://github.com/marvinsxtr/covidpass-api).
## Setup
```sh

View File

@ -1,22 +1,24 @@
export default Card
function Card({ heading, content, step }) {
return (
<div className="rounded-md p-6 bg-white dark:bg-gray-800 space-y-4">
{step &&
<div className="flex flex-row items-center">
<div className="rounded-full p-4 bg-green-600 h-5 w-5 flex items-center justify-center">
<p className="text-white text-lg font-bold">
{step}
</p>
</div>
<div className="ml-3 font-bold text-xl">
{heading}
</div>
</div>}
<div className="text-lg">
{content}
</div>
</div>
)
function Card({heading, content, step}) {
return (
<div className="rounded-md p-6 bg-white dark:bg-gray-800 space-y-4">
{
step &&
<div className="flex flex-row items-center">
<div className="rounded-full p-4 bg-green-600 h-5 w-5 flex items-center justify-center">
<p className="text-white text-lg font-bold">
{step}
</p>
</div>
<div className="ml-3 font-bold text-xl">
{heading}
</div>
</div>
}
<div className="text-lg">
{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 {BrowserQRCodeReader} from '@zxing/browser'
import React, {useEffect, useRef, useState} from "react"
import {decodeData} from "../src/decode"
import {processPdf, processPng} from "../src/process"
import {createPass} from "../src/pass"
import Card from "../components/Card"
import Alert from "../components/Alert"
import jsQR from "jsqr";
export default Form
function Form() {
function readFileAsync(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsArrayBuffer(file);
})
}
function readFileAsync(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
const error = function(heading, message) {
const alert = document.getElementById('alert')
alert.setAttribute('style', null)
document.getElementById('heading').innerHTML = heading
document.getElementById('message').innerHTML = message
reader.onload = () => {
resolve(reader.result);
};
document.getElementById('spin').style.display = 'none'
}
reader.onerror = reject;
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')
reader.readAsArrayBuffer(file);
})
}
if (typeof result === 'undefined') {
return
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 color = document.getElementById('color').value
try {
const pass = await createPass(
{
decoded: result.decoded,
raw: result.raw,
color: color
const processFile = async function () {
console.log(qrCode)
console.log(file)
if (!qrCode && !file) {
error("Error", "Please capture a QR Code or select a file to scan");
return;
}
)
if (!pass) {
error('Error:', "Something went wrong.")
} else {
const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
saveAs(passBlob, 'covid.pkpass')
}
} catch (e) {
error('Error:', e.message)
} finally {
document.getElementById('spin').style.display = 'none'
}
}
return (
<div>
<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={
<div className="space-y-5">
<p>
Please select the (scanned) certificate PDF page, which you received from your doctor, pharmacy, vaccination centre or online.
</p>
<input
className="w-full"
type="file"
id="pdf"
accept="application/pdf"
required
/>
</div>
} />
<Card step={2} heading="Pick a Color" content={
<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>
} />
<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>
)
document.getElementById('spin').style.display = 'block'
let rawData;
if (file) {
let imageData
const fileBuffer = await readFileAsync(file)
switch (file.type) {
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')
}
}
const addToWallet = async function (event) {
event.preventDefault()
let result
try {
result = await processFile()
} catch (e) {
error('Error:', 'Could not extract QR code data from certificate')
}
if (typeof result === 'undefined') {
return
}
const color = document.getElementById('color').value
try {
const pass = await createPass(
{
decoded: result.decoded,
raw: result.raw,
color: color
}
)
if (!pass) {
error('Error:', "Something went wrong.")
} else {
const passBlob = new Blob([pass], {type: "application/vnd.apple.pkpass"});
saveAs(passBlob, 'covid.pkpass')
}
} catch (e) {
error('Error:', e.message)
} finally {
document.getElementById('spin').style.display = 'none'
}
}
const [isCameraOpen, setIsCameraOpen] = useState(false);
const [globalControls, setGlobalControls] = useState(undefined);
const [qrCode, setQrCode] = useState(undefined);
const [file, setFile] = useState(undefined);
const inputFile = useRef(undefined)
useEffect(() => {
if (inputFile && inputFile.current) {
inputFile.current.addEventListener('input', () => {
let selectedFile = inputFile.current.files[0];
if (selectedFile !== undefined) {
setQrCode(undefined);
setFile(selectedFile);
}
});
}
}, [inputFile])
async function showFileDialog() {
inputFile.current.click();
}
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'
export default Logo
function Logo() {
return (
<Link href="/">
<a className="flex flex-row items-center p-3 justify-center space-x-1" >
<Icon className="fill-current" />
<h1 className="text-3xl font-bold">
CovidPass
</h1>
</a>
</Link>
)
return (
<Link href="/">
<a className="flex flex-row items-center p-3 justify-center space-x-1">
<svg className="fill-current" xmlns="http://www.w3.org/2000/svg" enableBackground="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>
<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">
<Head>
<title>CovidPass</title>
<link rel="icon" href="/favicon.png" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<main className="flex flex-col space-y-5">
@ -18,11 +18,11 @@ function Page({ content }) {
{content}
<footer>
<nav className="nav flex space-x-4 m-6 flex-row-reverse space-x-reverse 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>
<nav className="nav flex pt-4 flex-row space-x-4 justify-center text-md font-bold">
<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>
<Link href="/privacy"><a className="hover:underline" >Privacy Policy</a></Link>
<Link href="/imprint"><a className="hover:underline" >Imprint</a></Link>
</nav>
</footer>
</main>

View File

@ -15,12 +15,12 @@ export default {
additionalLinkTags: [
{
rel: 'icon',
href: 'https://covidpass.marvinsextro.de/favicon.png',
href: 'https://covidpass.marvinsextro.de/favicon.ico',
},
{
rel: 'apple-touch-icon',
href: 'https://covidpass.marvinsextro.de/favicon.png',
sizes: '96x96'
href: 'https://covidpass.marvinsextro.de/apple-touch-icon.png',
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>",
"private": true,
"scripts": {
"dev": "node server.js",
"dev": "next dev",
"build": "next build",
"start": "NODE_ENV=production node server.js"
"start": "next start"
},
"dependencies": {
"@zxing/browser": "^0.0.9",
"@zxing/library": "^0.18.6",
"base45-js": "^1.0.1",
"cbor-js": "^0.1.0",
"do-not-zip": "^1.0.0",
"file-saver": "^2.0.5",
"jpeg-js": "^0.4.3",
"jsqr": "^1.4.0",
"next": "latest",
"next-seo": "^4.26.0",
"node-fetch": "^2.6.1",
"pdfjs-dist": "^2.5.207",
"pngjs": "^6.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"webpack": "^5.0.0",
"worker-loader": "^3.0.7"
},
"devDependencies": {
"@svgr/webpack": "^5.5.0",
"autoprefixer": "^10.0.4",
"postcss": "^8.1.10",
"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 {
render() {
return (
<Html lang="en">
<Head />
<Head/>
<body className="bg-gray-200 dark:bg-gray-900 text-gray-800 dark:text-white">
<Main />
<NextScript />
<Main/>
<NextScript/>
</body>
</Html>
)

View File

@ -17,10 +17,16 @@ export default function Home() {
images: [
{
url: 'https://covidpass.marvinsextro.de/thumbnail.png',
width: 611,
height: 318,
width: 1000,
height: 500,
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',
}}

View File

@ -13,7 +13,7 @@ export default function Privacy() {
<div className="px-4">
<ul className="list-disc">
<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>
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>.
</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>
</ul>
</div>
@ -78,7 +78,7 @@ export default function Privacy() {
</p>
<div className="px-4">
<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>
</ul>
</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)
})
})
}