+
Android
-
Ensure you have the original Dragalia Lost app installed. The Dragalipatch app works by
@@ -112,6 +99,21 @@
on the screen that follows.
+
+
+
iOS
For information on how to play on iOS, please see{' '}
@@ -214,17 +216,17 @@
diff --git a/Website/src/routes/(main)/(home)/acknowledgement.svelte b/Website/src/routes/(main)/(home)/acknowledgement.svelte
index fc9128af6..672a246f8 100644
--- a/Website/src/routes/(main)/(home)/acknowledgement.svelte
+++ b/Website/src/routes/(main)/(home)/acknowledgement.svelte
@@ -1,13 +1,13 @@
-
-
-
- {name} ,
+
+
+
+ {name} ,
diff --git a/Website/src/routes/(main)/(home)/icons/buyMeACoffee.svelte b/Website/src/routes/(main)/(home)/icons/buyMeACoffee.svelte
index 82c15c645..70edf70a9 100644
--- a/Website/src/routes/(main)/(home)/icons/buyMeACoffee.svelte
+++ b/Website/src/routes/(main)/(home)/icons/buyMeACoffee.svelte
@@ -1,5 +1,5 @@
Buy Me A Coffee Buy Me A Coffee
diff --git a/Website/src/routes/(main)/(home)/icons/discord.svelte b/Website/src/routes/(main)/(home)/icons/discord.svelte
index 3e032833a..d627c3ace 100644
--- a/Website/src/routes/(main)/(home)/icons/discord.svelte
+++ b/Website/src/routes/(main)/(home)/icons/discord.svelte
@@ -1,5 +1,5 @@
diff --git a/Website/src/routes/(main)/(home)/icons/github.svelte b/Website/src/routes/(main)/(home)/icons/github.svelte
index 825e5815a..6434fac4d 100644
--- a/Website/src/routes/(main)/(home)/icons/github.svelte
+++ b/Website/src/routes/(main)/(home)/icons/github.svelte
@@ -1,7 +1,7 @@
diff --git a/Website/src/routes/(main)/(home)/icons/patreon.svelte b/Website/src/routes/(main)/(home)/icons/patreon.svelte
index 6431c9fae..343672f5b 100644
--- a/Website/src/routes/(main)/(home)/icons/patreon.svelte
+++ b/Website/src/routes/(main)/(home)/icons/patreon.svelte
@@ -1,19 +1,19 @@
-
+ />
diff --git a/Website/src/routes/(main)/(home)/linkButton.svelte b/Website/src/routes/(main)/(home)/linkButton.svelte
index 982b30fa3..03a876953 100644
--- a/Website/src/routes/(main)/(home)/linkButton.svelte
+++ b/Website/src/routes/(main)/(home)/linkButton.svelte
@@ -1,14 +1,14 @@
-
-
-
-
+
+
+
+
diff --git a/Website/src/routes/(main)/+layout.server.ts b/Website/src/routes/(main)/+layout.server.ts
new file mode 100644
index 000000000..e75355a5a
--- /dev/null
+++ b/Website/src/routes/(main)/+layout.server.ts
@@ -0,0 +1,25 @@
+import type { LayoutServerLoad } from './$types';
+import getJwtMetadata from '$lib/jwt';
+import Cookies from '$lib/cookies';
+
+export const load: LayoutServerLoad = ({ cookies, depends }) => {
+ depends(`cookie:${Cookies.IdToken}`);
+
+ const idToken = cookies.get(Cookies.IdToken);
+ if (!idToken) {
+ return {
+ hasValidJwt: false
+ };
+ }
+
+ const jwtMetadata = getJwtMetadata(idToken);
+ if (!jwtMetadata.valid || Date.now() > jwtMetadata.expiryTimestampMs) {
+ return {
+ hasValidJwt: false
+ };
+ }
+
+ return {
+ hasValidJwt: true
+ };
+};
diff --git a/Website/src/routes/(main)/+layout.svelte b/Website/src/routes/(main)/+layout.svelte
index 0f1158f98..a745dfd41 100644
--- a/Website/src/routes/(main)/+layout.svelte
+++ b/Website/src/routes/(main)/+layout.svelte
@@ -1,14 +1,17 @@
-
+
-
+
diff --git a/Website/src/routes/(main)/header.svelte b/Website/src/routes/(main)/header.svelte
index 9584255e1..b33ab9ea0 100644
--- a/Website/src/routes/(main)/header.svelte
+++ b/Website/src/routes/(main)/header.svelte
@@ -1,84 +1,71 @@
{#if enhance}
-
-
+
{:else}
-
+
{/if}
diff --git a/Website/src/routes/(main)/headerContents.svelte b/Website/src/routes/(main)/headerContents.svelte
new file mode 100644
index 000000000..a6334b3ff
--- /dev/null
+++ b/Website/src/routes/(main)/headerContents.svelte
@@ -0,0 +1,34 @@
+
+
+
Dawnshard
+
+
+
+
+ Toggle theme
+
+
+{#if hasValidJwt}
+
invalidate(`cookie:${Cookies.IdToken}`)}>Log out
+{:else}
+
Login
+{/if}
diff --git a/Website/src/routes/(main)/login/+page.server.ts b/Website/src/routes/(main)/login/+page.server.ts
new file mode 100644
index 000000000..fee8b27c6
--- /dev/null
+++ b/Website/src/routes/(main)/login/+page.server.ts
@@ -0,0 +1,40 @@
+import { redirect } from '@sveltejs/kit';
+import { Buffer } from 'buffer';
+import type { PageServerLoad } from './$types';
+import { PUBLIC_BAAS_URL, PUBLIC_BAAS_CLIENT_ID, PUBLIC_DAWNSHARD_URL } from '$env/static/public';
+
+const redirectUri = new URL('oauth', PUBLIC_DAWNSHARD_URL);
+
+const getChallengeString = () => {
+ const buffer = new Uint8Array(8);
+ crypto.getRandomValues(buffer);
+ return Array.from(buffer, (dec) => dec.toString(16).padStart(2, '0')).join('');
+};
+
+const getUrlSafeBase64Hash = async (input: string) => {
+ const buffer = new TextEncoder().encode(input);
+ const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
+ const base64 = Buffer.from(new Uint8Array(hashBuffer)).toString('base64');
+ return base64.replace('+', '-').replace('/', '_').replace('=', '');
+};
+
+export const load: PageServerLoad = async ({ cookies, url }) => {
+ const originalPage = url.searchParams.get('originalPage') ?? '/';
+
+ const challengeStringValue = getChallengeString();
+ cookies.set('challengeString', challengeStringValue, { path: '/' });
+
+ const queryParams = new URLSearchParams({
+ client_id: PUBLIC_BAAS_CLIENT_ID,
+ redirect_uri: redirectUri.toString(),
+ response_type: 'session_token_code',
+ scope: 'user user.birthday openid',
+ language: 'en-US',
+ session_token_code_challenge: await getUrlSafeBase64Hash(challengeStringValue),
+ session_token_code_challenge_method: 'S256',
+ state: JSON.stringify({ originalPage })
+ });
+
+ const baasUrl = new URL(`/custom/thirdparty/auth?${queryParams}`, PUBLIC_BAAS_URL);
+ redirect(302, baasUrl);
+};
diff --git a/Website/src/routes/(main)/logout/+page.server.ts b/Website/src/routes/(main)/logout/+page.server.ts
new file mode 100644
index 000000000..5dec19bc0
--- /dev/null
+++ b/Website/src/routes/(main)/logout/+page.server.ts
@@ -0,0 +1,9 @@
+import type { PageServerLoad } from './$types';
+import Cookies from '$lib/cookies';
+import { redirect } from '@sveltejs/kit';
+
+export const load: PageServerLoad = async ({ cookies }) => {
+ cookies.delete(Cookies.IdToken, { path: '/' });
+
+ redirect(302, '/');
+};
diff --git a/Website/src/routes/(main)/navigation/+page@.svelte b/Website/src/routes/(main)/navigation/+page@.svelte
index 7039a19d0..e12d57af1 100644
--- a/Website/src/routes/(main)/navigation/+page@.svelte
+++ b/Website/src/routes/(main)/navigation/+page@.svelte
@@ -1,8 +1,8 @@
- Navigation
-
+ Navigation
+
diff --git a/Website/src/routes/(main)/news/+page.svelte b/Website/src/routes/(main)/news/+page.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/Website/src/routes/(main)/oauth/+page.server.ts b/Website/src/routes/(main)/oauth/+page.server.ts
new file mode 100644
index 000000000..97fd74f89
--- /dev/null
+++ b/Website/src/routes/(main)/oauth/+page.server.ts
@@ -0,0 +1,115 @@
+import type { PageServerLoad } from './$types';
+import { PUBLIC_BAAS_URL, PUBLIC_BAAS_CLIENT_ID } from '$env/static/public';
+import Cookies from '$lib/cookies';
+import { redirect } from '@sveltejs/kit';
+import getJwtMetadata from '$lib/jwt';
+
+const sessionTokenUrl = new URL('/connect/1.0.0/api/session_token', PUBLIC_BAAS_URL);
+const sdkTokenUrl = new URL('/1.0.0/gateway/sdk/token', PUBLIC_BAAS_URL);
+
+const getOriginalPage = (url: URL) => {
+ const stateJson = url.searchParams.get('state');
+ if (!stateJson) {
+ return null;
+ }
+
+ let stateObject;
+ try {
+ stateObject = JSON.parse(stateJson);
+ } catch {
+ return null;
+ }
+
+ if (!stateObject.originalPage) {
+ return null;
+ }
+
+ return stateObject.originalPage;
+};
+
+export const load: PageServerLoad = async ({ cookies, url, fetch }) => {
+ const challengeString = cookies.get(Cookies.ChallengeString);
+
+ if (!challengeString) {
+ throw new Error('Failed to get challenge string');
+ }
+
+ const sessionTokenCode = url.searchParams.get('session_token_code');
+
+ if (!sessionTokenCode) {
+ throw new Error('Failed to get session token code');
+ }
+
+ const sessionTokenCodeParams = new URLSearchParams({
+ client_id: PUBLIC_BAAS_CLIENT_ID,
+ session_token_code: sessionTokenCode,
+ session_token_code_verifier: challengeString
+ });
+
+ const sessionTokenResponse = await fetch(sessionTokenUrl, {
+ method: 'POST',
+ body: sessionTokenCodeParams
+ });
+
+ if (!sessionTokenResponse.ok) {
+ throw new Error('Session token request failed');
+ }
+
+ const sessionTokenResponseBody = await sessionTokenResponse.json();
+ const sessionToken = sessionTokenResponseBody.session_token;
+
+ if (!sessionToken) {
+ throw new Error('Failed to parse session token response');
+ }
+
+ const sdkTokenRequest = {
+ client_id: PUBLIC_BAAS_CLIENT_ID,
+ session_token: sessionToken
+ };
+
+ const sdkTokenResponse = await fetch(sdkTokenUrl, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(sdkTokenRequest)
+ });
+
+ if (!sdkTokenResponse.ok) {
+ throw new Error('SDK token request failed');
+ }
+
+ const sdkTokenResponseBody = await sdkTokenResponse.json();
+ const idToken = sdkTokenResponseBody.idToken;
+
+ if (!idToken) {
+ throw new Error('Failed to parse SDK token response');
+ }
+
+ const jwtMetadata = getJwtMetadata(idToken);
+ if (!jwtMetadata.valid) {
+ throw Error('Invalid JWT returned');
+ }
+
+ console.log(jwtMetadata);
+
+ const maxAge = (jwtMetadata.expiryTimestampMs - Date.now()) / 1000;
+
+ cookies.set(Cookies.IdToken, idToken, {
+ path: '/',
+ sameSite: 'lax',
+ httpOnly: true,
+ maxAge,
+ ...(import.meta.env.MODE !== 'development' && {
+ secure: true
+ })
+ });
+
+ cookies.delete('challengeString', {
+ path: '/'
+ });
+
+ const destination = getOriginalPage(url) ?? '/';
+ redirect(302, destination);
+};
diff --git a/Website/src/routes/(main)/sideNav.svelte b/Website/src/routes/(main)/sideNav.svelte
index 7f4417979..9bdd8ae28 100644
--- a/Website/src/routes/(main)/sideNav.svelte
+++ b/Website/src/routes/(main)/sideNav.svelte
@@ -1,21 +1,21 @@
-
-
-
+
+
+
diff --git a/Website/src/routes/(main)/user.ts b/Website/src/routes/(main)/user.ts
new file mode 100644
index 000000000..17911602a
--- /dev/null
+++ b/Website/src/routes/(main)/user.ts
@@ -0,0 +1,8 @@
+import { z } from 'zod';
+
+export const userSchema = z.object({
+ viewerId: z.number().int(),
+ name: z.string()
+});
+
+export type User = z.infer
;
diff --git a/Website/src/routes/+layout.svelte b/Website/src/routes/+layout.svelte
index 17abf18a5..e163a965b 100644
--- a/Website/src/routes/+layout.svelte
+++ b/Website/src/routes/+layout.svelte
@@ -1,5 +1,5 @@
diff --git a/Website/svelte.config.js b/Website/svelte.config.js
index c9d691feb..4d0172766 100644
--- a/Website/svelte.config.js
+++ b/Website/svelte.config.js
@@ -3,20 +3,20 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
- // Consult https://kit.svelte.dev/docs/integrations#preprocessors
- // for more information about preprocessors
- preprocess: [vitePreprocess({})],
+ // Consult https://kit.svelte.dev/docs/integrations#preprocessors
+ // for more information about preprocessors
+ preprocess: [vitePreprocess({})],
- kit: {
- // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
- // If your environment is not supported or you settled on a specific environment, switch out the adapter.
- // See https://kit.svelte.dev/docs/adapters for more information about adapters.
- adapter: adapter(),
- alias: {
- $shadcn: './src/lib/shadcn',
- $static: './static'
- }
- }
+ kit: {
+ // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+ // If your environment is not supported or you settled on a specific environment, switch out the adapter.
+ // See https://kit.svelte.dev/docs/adapters for more information about adapters.
+ adapter: adapter(),
+ alias: {
+ $shadcn: './src/lib/shadcn',
+ $static: './static'
+ }
+ }
};
export default config;
diff --git a/Website/tailwind.config.js b/Website/tailwind.config.js
index 3ff3115e9..ea97c4816 100644
--- a/Website/tailwind.config.js
+++ b/Website/tailwind.config.js
@@ -2,68 +2,68 @@ import { fontFamily } from 'tailwindcss/defaultTheme';
/** @type {import('tailwindcss').Config} */
const config = {
- darkMode: 'selector',
- content: ['./src/**/*.{html,js,svelte,ts}'],
- safelist: ['dark'],
- theme: {
- container: {
- center: true,
- padding: '2rem',
- screens: {
- '2xl': '1400px'
- }
- },
- extend: {
- colors: {
- border: 'hsl(var(--border) / )',
- input: 'hsl(var(--input) / )',
- ring: 'hsl(var(--ring) / )',
- background: 'hsl(var(--background) / )',
- foreground: 'hsl(var(--foreground) / )',
- primary: {
- DEFAULT: 'hsl(var(--primary) / )',
- foreground: 'hsl(var(--primary-foreground) / )'
- },
- secondary: {
- DEFAULT: 'hsl(var(--secondary) / )',
- foreground: 'hsl(var(--secondary-foreground) / )'
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive) / )',
- foreground: 'hsl(var(--destructive-foreground) / )'
- },
- muted: {
- DEFAULT: 'hsl(var(--muted) / )',
- foreground: 'hsl(var(--muted-foreground) / )'
- },
- accent: {
- DEFAULT: 'hsl(var(--accent) / )',
- foreground: 'hsl(var(--accent-foreground) / )'
- },
- popover: {
- DEFAULT: 'hsl(var(--popover) / )',
- foreground: 'hsl(var(--popover-foreground) / )'
- },
- card: {
- DEFAULT: 'hsl(var(--card) / )',
- foreground: 'hsl(var(--card-foreground) / )'
- }
- },
- borderRadius: {
- lg: 'var(--radius)',
- md: 'calc(var(--radius) - 2px)',
- sm: 'calc(var(--radius) - 4px)'
- },
- fontFamily: {
- sans: [...fontFamily.sans]
- }
- }
- },
- variants: {
- extend: {
- backgroundImage: ['dark']
- }
- }
+ darkMode: 'selector',
+ content: ['./src/**/*.{html,js,svelte,ts}'],
+ safelist: ['dark'],
+ theme: {
+ container: {
+ center: true,
+ padding: '2rem',
+ screens: {
+ '2xl': '1400px'
+ }
+ },
+ extend: {
+ colors: {
+ border: 'hsl(var(--border) / )',
+ input: 'hsl(var(--input) / )',
+ ring: 'hsl(var(--ring) / )',
+ background: 'hsl(var(--background) / )',
+ foreground: 'hsl(var(--foreground) / )',
+ primary: {
+ DEFAULT: 'hsl(var(--primary) / )',
+ foreground: 'hsl(var(--primary-foreground) / )'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary) / )',
+ foreground: 'hsl(var(--secondary-foreground) / )'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive) / )',
+ foreground: 'hsl(var(--destructive-foreground) / )'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted) / )',
+ foreground: 'hsl(var(--muted-foreground) / )'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent) / )',
+ foreground: 'hsl(var(--accent-foreground) / )'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover) / )',
+ foreground: 'hsl(var(--popover-foreground) / )'
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card) / )',
+ foreground: 'hsl(var(--card-foreground) / )'
+ }
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ fontFamily: {
+ sans: [...fontFamily.sans]
+ }
+ }
+ },
+ variants: {
+ extend: {
+ backgroundImage: ['dark']
+ }
+ }
};
export default config;
diff --git a/Website/tests/test.ts b/Website/tests/test.ts
index 5816be413..589fab7b0 100644
--- a/Website/tests/test.ts
+++ b/Website/tests/test.ts
@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
test('index page has expected h1', async ({ page }) => {
- await page.goto('/');
- await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
+ await page.goto('/');
+ await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
});
diff --git a/Website/tsconfig.json b/Website/tsconfig.json
index 82081abc3..34aadc028 100644
--- a/Website/tsconfig.json
+++ b/Website/tsconfig.json
@@ -1,18 +1,18 @@
{
- "extends": "./.svelte-kit/tsconfig.json",
- "compilerOptions": {
- "allowJs": true,
- "checkJs": true,
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "resolveJsonModule": true,
- "skipLibCheck": true,
- "sourceMap": true,
- "strict": true,
- "moduleResolution": "bundler"
- }
- // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
- //
- // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
- // from the referenced tsconfig.json - TypeScript does not merge them in
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "moduleResolution": "bundler"
+ }
+ // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
+ //
+ // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
+ // from the referenced tsconfig.json - TypeScript does not merge them in
}
diff --git a/Website/vite.config.ts b/Website/vite.config.ts
index c098bb5b0..64b9db012 100644
--- a/Website/vite.config.ts
+++ b/Website/vite.config.ts
@@ -3,8 +3,14 @@ import { enhancedImages } from '@sveltejs/enhanced-img';
import { defineConfig } from 'vite';
export default defineConfig({
- plugins: [sveltekit(), enhancedImages()],
- server: {
- port: 3001
- }
+ plugins: [sveltekit(), enhancedImages()],
+ server: {
+ port: 3001,
+ proxy: {
+ '/api': {
+ target: 'http://localhost:5000',
+ changeOrigin: true
+ }
+ }
+ }
});