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(() => {