diff --git a/src/const.ts b/src/const.ts index 559f067..452b799 100644 --- a/src/const.ts +++ b/src/const.ts @@ -12,6 +12,8 @@ export const apiServerURL = dev ? 'http://localhost:4000/api/json' : 'https://api.hololivefanbooth.com/api/json'; +export const minStampCountRequired = 10; + export const holomemGachaPool = [ { id: 'UC060r4zABV18vcahAWR1n7w', diff --git a/src/crypto.ts b/src/crypto.ts index 3720541..fd7fca0 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -32,3 +32,7 @@ export function calculateTokenChecksum(tokens: string[]): string { shaObj.update(tokens.join('')); return shaObj.getHash('HEX'); } + +export function tokenHash(token: string): string { + return sha256(token); +} diff --git a/src/lib/components/Stamp.svelte b/src/lib/components/Stamp.svelte index 1a1e4b4..1737d14 100644 --- a/src/lib/components/Stamp.svelte +++ b/src/lib/components/Stamp.svelte @@ -1,13 +1,13 @@ +
- {#each stamps as stamp} + {#each stamps as stamp, i} {/each} diff --git a/src/lib/stores/stamps.ts b/src/lib/stores/stamps.ts index 3644b1b..f8bf68f 100644 --- a/src/lib/stores/stamps.ts +++ b/src/lib/stores/stamps.ts @@ -1,4 +1,17 @@ import { persisted } from 'svelte-persisted-store'; +import { get } from 'svelte/store'; +import { tokenHash } from '../../crypto'; export const collectedStamps = persisted('collected-stamps', {}); export const expectedStamps = persisted('expected-stamps', {}); + +export function getExpectedStampHashes() { + return Object.keys(get(expectedStamps)); +} + +export function saveStamp(token: string) { + const newCollectedStamps = { ...get(collectedStamps) }; + const hash = tokenHash(token); + newCollectedStamps[hash] = token; + collectedStamps.set(newCollectedStamps); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 527a530..078d487 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,19 +7,20 @@ import Navigation from '$lib/components/Navigation.svelte'; import Toast from '$lib/components/Toast.svelte'; import { onMount } from 'svelte'; - import { generateNickname } from 'hololive-nick-gen'; import { dev } from '$app/environment'; + import { saveStamp, collectedStamps } from '$lib/stores/stamps'; onMount(async () => { // Initialize the stores with the nickname and user token found locally - // If first time using the app, generate a new nickname - $userToken = localStorage.getItem('userToken'); + $userToken = localStorage.getItem('userToken'); // FIXME: Replace with persisted store // Bind stuff to window for debugging if (dev || localStorage.getItem('debug') === 'true') { Object.assign(window, { - setToast: setToast + setToast: setToast, + saveStamp: saveStamp, + collectedStamps: collectedStamps, }); } }); diff --git a/src/routes/scanner/+page.svelte b/src/routes/scanner/+page.svelte index 5f646eb..f7b7123 100644 --- a/src/routes/scanner/+page.svelte +++ b/src/routes/scanner/+page.svelte @@ -3,22 +3,21 @@ import QrScanner from 'qr-scanner'; - import { sha256 } from '../../crypto'; + import { tokenHash } from '../../crypto'; import { TOAST_TYPE, SCANNER_STATE, type ScannerState } from '../../custom'; import { Icon } from '@steeze-ui/svelte-icon'; import { Ticket, QrCode, StopCircle } from '@steeze-ui/heroicons'; import { fade } from 'svelte/transition'; - import { get } from 'svelte/store'; - import { expectedStamps } from '$lib/stores/stamps'; + import { getExpectedStampHashes, saveStamp } from '$lib/stores/stamps'; import { setToast } from '$lib/stores/toasts'; + import { minStampCountRequired } from '../../const'; let state: ScannerState = 'STOPPED'; let videoElem: HTMLVideoElement; let qrScanner: QrScanner; let token = ''; - const expectedHashes = Object.values(get(expectedStamps)).map((stamp) => stamp.hash); let collectedStampCount = function () { return 0; @@ -40,14 +39,25 @@ } } + // Handle scan result function onResult(result: QrScanner.ScanResult) { - token = result.data; + try { + token = parseTokenFromScan(result.data); + console.log('Scanned token:', token) + } catch (e) { + setToast({ + type: TOAST_TYPE.ERROR, + message: 'Invalid QR code!' + }); + return; + } + transitionState(); - let hash = sha256(token); + let hash = tokenHash(token); - if (expectedHashes.includes(hash)) { + if (getExpectedStampHashes().includes(hash)) { // Scan success - localStorage.setItem(hash, token); + saveStamp(token); let collectedStamps = collectedStampCount(); @@ -56,7 +66,7 @@ message: 'Stamp Saved!' }); - if (collectedStamps == 10) { + if (collectedStamps == minStampCountRequired) { setTimeout(() => { setToast({ type: TOAST_TYPE.SUCCESS, @@ -72,6 +82,16 @@ } } + function parseTokenFromScan(data: string): string { + const url = new URL(data); + if (url.pathname.startsWith('/sc/')) { + const token = url.pathname.substring(4); + return token; + } else { + throw new Error('Invalid QR code'); + } + } + onMount(() => { videoElem = document.querySelector('video') as HTMLVideoElement; qrScanner = new QrScanner(videoElem, onResult, { @@ -83,9 +103,10 @@ videoElem.setAttribute('autoplay', 'true'); videoElem.setAttribute('muted', 'true'); - collectedStampCount = () => { - return Object.keys(localStorage).filter((key) => expectedHashes.includes(key)).length; - }; + // TODO: Implement collectedStampCount + // collectedStampCount = () => { + // return Object.keys(localStorage).filter((key) => expectedHashes.includes(key)).length; + // }; }); onDestroy(() => {