diff --git a/.changeset/gorgeous-papayas-teach.md b/.changeset/gorgeous-papayas-teach.md new file mode 100644 index 0000000..b2ee5a1 --- /dev/null +++ b/.changeset/gorgeous-papayas-teach.md @@ -0,0 +1,5 @@ +--- +"@svelte-dev/i18n": patch +--- + +fix type diff --git a/.changeset/smooth-moose-sneeze.md b/.changeset/smooth-moose-sneeze.md new file mode 100644 index 0000000..bb036a2 --- /dev/null +++ b/.changeset/smooth-moose-sneeze.md @@ -0,0 +1,5 @@ +--- +"@svelte-dev/auth": patch +--- + +auth handler with more checks diff --git a/.changeset/tricky-vans-grow.md b/.changeset/tricky-vans-grow.md new file mode 100644 index 0000000..e23be2d --- /dev/null +++ b/.changeset/tricky-vans-grow.md @@ -0,0 +1,8 @@ +--- +"@svelte-dev/auth-oauth2": patch +"@svelte-dev/session": patch +"@svelte-dev/auth": patch +"@svelte-dev/i18n": patch +--- + +chore: update readme diff --git a/.vscode/settings.json b/.vscode/settings.json index 144296c..b0321d6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "conventionalCommits.scopes": ["auth", "i18n", "session", "strategies"], - "cSpell.words": ["willin"] + "cSpell.words": ["willin"], + "svelte.plugin.svelte.compilerWarnings": { + "a11y-no-noninteractive-tabindex": "ignore" + } } diff --git a/apps/web/mdsvex.config.js b/apps/web/mdsvex.config.js new file mode 100644 index 0000000..20f352f --- /dev/null +++ b/apps/web/mdsvex.config.js @@ -0,0 +1,74 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineMDSveXConfig as defineConfig } from 'mdsvex'; +import remarkGfm from 'remark-gfm'; +import remarkGithub from 'remark-github'; +import rehypeSlug from 'rehype-slug'; +import rehypeAutolinkHeadings from 'rehype-autolink-headings'; +import { codeToHtml } from 'shikiji'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * @param {string} code + * @param {string | undefined} lang + */ +async function highlighter(code, lang = '') { + /** + * escape curlies, backtick, \t, \r, \n to avoid breaking output of {@html `here`} in .svelte + * @param {string} str the string to escape + * @returns the escaped string + */ + const escape_svelty = (str) => + str + .replace(/[{}`]/g, (c) => ({ '`': '`', '{': '{', '}': '}' })[c]) + .replace(/\\([trn])/g, '\$1'); + + const html = await codeToHtml(code, { + lang, + + theme: 'nord' + }); + + return escape_svelty(html); +} + +const config = defineConfig({ + extensions: ['.svelte.md', '.md', '.svx'], + layout: { + // blog: './path/to/blog/layout.svelte', + // article: './path/to/article/layout.svelte', + // _: './path/to/fallback/layout.svelte' + _: path.resolve(__dirname, './src/components/mdsvex.svelte') + }, + highlight: { + highlighter + }, + rehypePlugins: [ + rehypeSlug, + [ + rehypeAutolinkHeadings, + { + behavior: 'wrap', + properties: { + className: ['anchor'] + } + } + ] + ], + remarkPlugins: [ + remarkGfm, + [ + remarkGithub, + { + repository: 'willin/svelte-turbo' + } + ] + ], + smartypants: { + dashes: 'oldschool' + } +}); + +export default config; diff --git a/apps/web/package.json b/apps/web/package.json index db92366..3a10300 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,20 +13,34 @@ "format": "prettier --write ." }, "dependencies": { - "@svelte-dev/i18n": "*", - "@svelte-dev/session": "*", "@svelte-dev/auth": "*", + "@svelte-dev/auth-alipay": "*", "@svelte-dev/auth-github": "*", "@svelte-dev/auth-sso": "*", - "@svelte-dev/auth-alipay": "*" + "@svelte-dev/i18n": "*", + "@svelte-dev/session": "*", + "theme-change": "^2.5.0" }, "devDependencies": { + "@melt-ui/pp": "^0.1.4", + "@melt-ui/svelte": "^0.65.1", + "@svelte-dev/eslint-config": "*", "@sveltejs/adapter-cloudflare": "^2.3.3", "@sveltejs/kit": "^1.27.4", - "@svelte-dev/eslint-config": "*", + "@tailwindcss/typography": "^0.5.10", + "autoprefixer": "^10.4.16", + "daisyui": "^4.4.19", "eslint": "^8.28.0", + "mdsvex": "^0.11.0", + "postcss": "^8.4.32", + "rehype-autolink-headings": "^7.1.0", + "rehype-slug": "^6.0.0", + "remark-gfm": "^4.0.0", + "remark-github": "^12.0.0", + "shikiji": "^0.8.1", "svelte": "^5.0.0-next.22", "svelte-check": "^3.6.0", + "tailwindcss": "^3.3.6", "tslib": "^2.4.1", "typescript": "^5.0.0", "vite": "^4.4.2", diff --git a/apps/web/postcss.config.cjs b/apps/web/postcss.config.cjs new file mode 100644 index 0000000..52a8bf0 --- /dev/null +++ b/apps/web/postcss.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + plugins: [require('tailwindcss'), require('autoprefixer')] +}; diff --git a/apps/web/src/app.css b/apps/web/src/app.css new file mode 100644 index 0000000..44b0c2b --- /dev/null +++ b/apps/web/src/app.css @@ -0,0 +1,96 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.prose h1 a, +.prose h2 a, +.prose h3 a, +.prose h4 a, +.prose h5 a, +.prose h6 a { + @apply no-underline block; +} + +* { + cursor: url('https://willin.wang/images/default.cur'), default; +} + +a, +a *, +button, +button *, +.btn, +.btn *, +.prose .post-image, +.cursor-pointer * { + cursor: url('https://willin.wang/images/pointer.cur'), pointer !important; +} + +html { + /* transition: background-color 0.5s linear, color 0.25s linear; */ + min-height: 100vh; +} + +div, +p { + @apply break-all; +} + +pre { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +/* Remove Safari input shadow on mobile */ +input[type='text'], +input[type='email'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +table { + display: block; + max-width: fit-content; + overflow-x: auto; + white-space: nowrap; +} + +.break-words, +.break-words p { + word-wrap: break-word; + word-break: break-word; +} + +.prose img { + /* Don't apply styles to next/image */ + @apply m-0 max-h-[75vh]; +} +.prose .mermaid > svg { + @apply mx-auto my-2; +} + +.prose { + @apply min-w-full; +} +.prose > * { + @apply min-w-[95%] w-full lg:w-[95%] mx-auto; +} +.prose h1 a, +.prose h2 a { + @apply relative text-primary-content bg-primary border-primary-content mx-auto rounded shadow-lg text-center w-full py-1; +} +.prose h3, +.prose h4, +.prose h5, +.prose h6 { + @apply text-secondary relative; +} + +.prose table { + @apply table min-w-[95%] w-full lg:w-[95%] table-zebra shadow mx-auto; +} + +.prose tr { + @apply hover; +} diff --git a/apps/web/src/app.d.ts b/apps/web/src/app.d.ts index e764109..f2f1ef8 100644 --- a/apps/web/src/app.d.ts +++ b/apps/web/src/app.d.ts @@ -2,6 +2,7 @@ /// import type { Auth } from '$lib/auth/auth.ts'; +import type { SessionStorage } from '@svelte-dev/session'; // See https://kit.svelte.dev/docs/types#app // for information about these interfaces diff --git a/apps/web/src/components/ChangeLanguage.svelte b/apps/web/src/components/ChangeLanguage.svelte new file mode 100644 index 0000000..7cbdd2d --- /dev/null +++ b/apps/web/src/components/ChangeLanguage.svelte @@ -0,0 +1,92 @@ + + + diff --git a/apps/web/src/components/ChangeRepo.svelte b/apps/web/src/components/ChangeRepo.svelte new file mode 100644 index 0000000..abf6e44 --- /dev/null +++ b/apps/web/src/components/ChangeRepo.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/apps/web/src/components/ChangeTheme.svelte b/apps/web/src/components/ChangeTheme.svelte new file mode 100644 index 0000000..1b8c055 --- /dev/null +++ b/apps/web/src/components/ChangeTheme.svelte @@ -0,0 +1,71 @@ + + + diff --git a/apps/web/src/components/Navbar.svelte b/apps/web/src/components/Navbar.svelte new file mode 100644 index 0000000..eb57148 --- /dev/null +++ b/apps/web/src/components/Navbar.svelte @@ -0,0 +1,127 @@ + + +
+ +
diff --git a/apps/web/src/components/mdsvex.svelte b/apps/web/src/components/mdsvex.svelte new file mode 100644 index 0000000..4df13de --- /dev/null +++ b/apps/web/src/components/mdsvex.svelte @@ -0,0 +1,50 @@ + + +
+
+
+
+ {#if title} +

+ {@html title} +

+ {/if} + {#if desc} +

{@html desc}

+ {/if} +
+
+
+ +
+
+ + +
+ + +
diff --git a/apps/web/src/components/toc.svelte b/apps/web/src/components/toc.svelte new file mode 100644 index 0000000..93d8cee --- /dev/null +++ b/apps/web/src/components/toc.svelte @@ -0,0 +1,31 @@ + + + diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts index 1997cf2..d6f12e5 100644 --- a/apps/web/src/hooks.server.ts +++ b/apps/web/src/hooks.server.ts @@ -2,8 +2,7 @@ import { env } from '$env/dynamic/private'; import { fallbackLng } from '$lib/i8n'; import { handleAuth } from '@svelte-dev/auth'; import { GitHubStrategy } from '@svelte-dev/auth-github'; -import { locale } from '@svelte-dev/i18n'; - +import { getLocales, locale } from '@svelte-dev/i18n'; // import { SSOStrategy } from '@svelte-dev/auth-sso'; // import { AlipayStrategy } from '@svelte-dev/auth-alipay'; @@ -68,12 +67,13 @@ export const handle = handleAuth( // ssoStrategy, githubStrategy ], - successRedirect: '/demo', - failureRedirect: '/error' + successRedirect: '/auth', + failureRedirect: '/auth' }, ({ event, resolve }) => { - const lang = event.params.lang ?? fallbackLng; - event.locals.lang = lang; + const url = new URL(event.request.url); + const [, matched = ''] = url.pathname.split('/'); + const lang = getLocales().includes(matched) ? matched : fallbackLng; locale.set(lang); return resolve(event, { diff --git a/apps/web/src/i18n/en.ts b/apps/web/src/i18n/en.ts index dbac815..a950fc5 100644 --- a/apps/web/src/i18n/en.ts +++ b/apps/web/src/i18n/en.ts @@ -1,7 +1,17 @@ import type { I18nDict } from '@svelte-dev/i18n'; export const dict: I18nDict = { + __name: 'English', + __flag: '🇺🇸', + __unicode: '1f1fa-1f1f8', + __code: 'EN', + __direction: 'ltr', + __status: 'beta', site: { title: 'Hello World' + }, + common: { + get_started: 'Get Started', + npm_downloads: 'NPM Downloads' } }; diff --git a/apps/web/src/i18n/zh.ts b/apps/web/src/i18n/zh.ts index e6756e5..c64c147 100644 --- a/apps/web/src/i18n/zh.ts +++ b/apps/web/src/i18n/zh.ts @@ -1,7 +1,17 @@ import type { I18nDict } from '@svelte-dev/i18n'; export const dict: I18nDict = { + __name: '中文', + __flag: '🇨🇳', + __unicode: '1f1e8-1f1f3', + __code: 'ZH', + __direction: 'ltr', + __status: '', site: { title: '你好,世界' + }, + common: { + get_started: '开始使用', + npm_downloads: 'NPM 下载量' } }; diff --git a/apps/web/src/lib/i8n.ts b/apps/web/src/lib/i8n.ts index a0dbd7a..3ec20f9 100644 --- a/apps/web/src/lib/i8n.ts +++ b/apps/web/src/lib/i8n.ts @@ -1,4 +1,5 @@ import { browser } from '$app/environment'; +import { navigating } from '$app/stores'; import { addMessages, locale } from '@svelte-dev/i18n'; const translations = import.meta.glob(`../i18n/*.ts`, { eager: true }); @@ -16,4 +17,10 @@ if (browser) { const path = new URL(location.href).pathname.split('/'); const lang = supportedLanguages.includes(path?.[1]) ? path?.[1] : fallbackLng; locale.set(lang); + + navigating.subscribe((params) => { + if (params?.to?.params?.lang) { + locale.set(params.to?.params?.lang); + } + }); } diff --git a/apps/web/src/lib/stores/npm.ts b/apps/web/src/lib/stores/npm.ts new file mode 100644 index 0000000..f886e75 --- /dev/null +++ b/apps/web/src/lib/stores/npm.ts @@ -0,0 +1,26 @@ +import { browser } from '$app/environment'; +import { writable } from 'svelte/store'; + +export const downloadsAuth = writable(0); +export const downloadsSession = writable(0); +export const downloadsI18n = writable(0); + +if (browser) { + fetch('https://raw.githubusercontent.com/wshow/github-readme-npm-downloads/main/npm.json') + .then((res) => res.json()) + .then((json: { stats: [key: string, count: number][] }) => { + json.stats.forEach(([key, count]) => { + switch (key) { + case '@svelte-dev/auth': + downloadsAuth.set(count); + break; + case '@svelte-dev/session': + downloadsSession.set(count); + break; + case '@svelte-dev/i18n': + downloadsI18n.set(count); + break; + } + }); + }); +} diff --git a/apps/web/src/lib/stores/prefix.ts b/apps/web/src/lib/stores/prefix.ts new file mode 100644 index 0000000..258cc59 --- /dev/null +++ b/apps/web/src/lib/stores/prefix.ts @@ -0,0 +1,10 @@ +import { page } from '$app/stores'; +import { fallbackLng } from '$lib/i8n'; +import { locales } from '@svelte-dev/i18n'; +import { derived } from 'svelte/store'; + +export const linkPrefix = derived([page, locales], ([$page, $locales]) => { + const path = $page.url.pathname.split('/'); + const lang = $locales.includes(path?.[1]) ? path?.[1] : fallbackLng; + return lang === fallbackLng ? '' : `/${lang}`; +}); diff --git a/apps/web/src/lib/stores/repo.ts b/apps/web/src/lib/stores/repo.ts new file mode 100644 index 0000000..4b7dedf --- /dev/null +++ b/apps/web/src/lib/stores/repo.ts @@ -0,0 +1,9 @@ +import { page } from '$app/stores'; +import { derived } from 'svelte/store'; + +export const currentRepo = derived(page, ($page) => { + const path = $page.url.pathname.split('/'); + const repos = ['auth', 'session', 'i18n']; + const matched = path.find((p) => repos.includes(p)); + return matched; +}); diff --git a/apps/web/src/lib/themes.ts b/apps/web/src/lib/themes.ts new file mode 100644 index 0000000..968282a --- /dev/null +++ b/apps/web/src/lib/themes.ts @@ -0,0 +1,148 @@ +export const defaultDarkTheme = 'sunset'; +export const defaultLightTheme = 'retro'; + +export const themes = [ + { + name: '🌝  light', + id: 'light' + }, + { + name: '🌚  dark', + id: 'dark' + }, + { + name: '🧁  cupcake', + id: 'cupcake' + }, + { + name: '🐝  bumblebee', + id: 'bumblebee' + }, + { + name: '✳️  Emerald', + id: 'emerald' + }, + { + name: '🏢  Corporate', + id: 'corporate' + }, + { + name: '🌃  synthwave', + id: 'synthwave' + }, + { + name: '👴  retro', + id: 'retro' + }, + { + name: '🤖  cyberpunk', + id: 'cyberpunk' + }, + { + name: '🌸  valentine', + id: 'valentine' + }, + { + name: '🎃  halloween', + id: 'halloween' + }, + { + name: '🌷  garden', + id: 'garden' + }, + { + name: '🌲  forest', + id: 'forest' + }, + { + name: '🐟  aqua', + id: 'aqua' + }, + { + name: '👓  lofi', + id: 'lofi' + }, + { + name: '🖍  pastel', + id: 'pastel' + }, + { + name: '🧚‍♀️  fantasy', + id: 'fantasy' + }, + { + name: '📝  Wireframe', + id: 'wireframe' + }, + { + name: '🏴  black', + id: 'black' + }, + { + name: '💎  luxury', + id: 'luxury' + }, + { + name: '🧛‍♂️  dracula', + id: 'dracula' + }, + { + name: '🖨  CMYK', + id: 'cmyk' + }, + { + name: '🍁  Autumn', + id: 'autumn' + }, + { + name: '💼  Business', + id: 'business' + }, + { + name: '💊  Acid', + id: 'acid' + }, + { + name: '🍋  Lemonade', + id: 'lemonade' + }, + { + name: '🌙  Night', + id: 'night' + }, + { + name: '☕️  Coffee', + id: 'coffee' + }, + { + name: '❄️  Winter', + id: 'winter' + }, + { + name: '🕶️ Dim', + id: 'dim' + }, + { + name: '🤓 Nord', + id: 'nord' + }, + { + name: '🌇 Sunset', + id: 'sunset' + } +] as const; + +export const darkThemes = [ + 'dark', + 'synthwave', + 'halloween', + 'forest', + 'black', + 'luxury', + 'dracula', + 'business', + 'night', + 'coffee', + 'dim', + 'sunset' +] as const; diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts new file mode 100644 index 0000000..3e8446f --- /dev/null +++ b/apps/web/src/lib/utils.ts @@ -0,0 +1,4 @@ +export function getRealPath(path: string, locales: string[]): string { + const reg = new RegExp(locales.map((x) => `/${x}`).join('|')); + return path.replace(reg, ''); +} diff --git a/apps/web/src/routes/(zh)/auth/+page.md b/apps/web/src/routes/(zh)/auth/+page.md new file mode 100644 index 0000000..9061e65 --- /dev/null +++ b/apps/web/src/routes/(zh)/auth/+page.md @@ -0,0 +1,209 @@ +--- +title: '@svelte-dev/auth' +desc: 一个简单好用的 Svelte 身份管理库 +--- + + + +![Logo](https://repository-images.githubusercontent.com/726691357/f09bf6fc-3844-4584-8eee-6bfb425d8a38) + +## 特性 + +- 完全的**服务器端**身份验证 +- 完整的**TypeScript**支持 +- **策略**-基础身份验证 +- 轻松处理**成功和失败** +- 实现**自定义**策略 +- 支持持久**会话** + +## 概述 + +Svelte Auth是一个完整的开源身份验证解决方案,适用于Svelte应用程序。 + +深受[Passport.js](https://passportjs.org)和[Remix-Auth](https://github.com/sergiodxa/remix-auth)的启发,但完全从头开始重写,以便在[Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)之上工作。 Svelte Auth可以在最小的设置下添加到任何基于Svelte的应用程序中。 + +与Passport.js一样,它使用策略模式来支持不同的身份验证流程。 每个策略都作为单独的npm包单独发布。 + +## 演示 + +{#if $page && $page.data.user} + +
+
{JSON.stringify($page.data.user, null, 2)}
+
+{:else} + + + Github 登录 +{/if} + +## 安装 + +要使用它,从npm(yarn或bun)安装: + +```bash +npm install @svelte-dev/auth @svelte-dev/session +``` + +## 使用 + +API规范:[文档](https://svelte-auth.js.cool/docs/) + +这是一个简单的例子: + +```ts +// hooks.server.ts +import { env } from '$env/dynamic/private'; +import { sequence } from '@sveltejs/kit/hooks'; +import { handleAuth } from '@svelte-dev/auth'; +import { OAuth2Strategy } from '@svelte-dev/auth-oauth2'; + +const oauthStrategy = new OAuth2Strategy( + { + clientID: env.SSO_ID, + clientSecret: env.SSO_SECRET, + callbackURL: env.SSO_CALLBACK_URL || 'http://localhost:8788/auth/oauth2/callback' + }, + async ({ profile }) => { + // Get the user data from your DB or API using the tokens and profile + return profile; + } +); + +export const handle = handleAuth({ + // Auth Options + autoRouting: true, + strategies: [oauthStrategy], + sessionKey: 'user', + sessionErrorKey: 'auth:error', + sessionStrategyKey: 'strategy', + successRedirect: '/', + failureRedirect: '/', + // Session Storage Options + adapter: { + name: 'cookie', + options: { + chunk: true + } + }, + session: { + secrets: ['s3cr3t'] + }, + cookie: { + secure: !!env.SSO_CALLBACK_URL, + sameSite: 'lax', + path: '/', + httpOnly: !!env.SSO_CALLBACK_URL + } +}); +``` + +就是这样。 + +## 进阶使用 + +### 自定义路由 + +如果您没有设置`authRouting`,您首先需要添加一个 `src/routes/auth/[provider]/+server.ts`: + +```ts +import { redirect, type RequestEvent } from '@sveltejs/kit'; + +export const GET = async (event: RequestEvent) => { + const { request } = event; + const provider = event.params.provider ?? 'github'; + return await event.locals.auth.authenticate(event, provider, { + successRedirect: '/dashboard', + failureRedirect: '/error' + }); +}; +``` + +然后添加回调 `src/routes/auth/[provider]/callback/+server.ts.ts:`: + +```ts +// 根据实际需要修改 +import type { RequestEvent } from '@sveltejs/kit'; + +export const GET = async (event: RequestEvent) => { + const provider = event.params.provider ?? 'github'; + + return await event.locals.auth.authenticate(event, provider, { + successRedirect: '/dashboard', + failureRedirect: '/error' + }); +}; +``` + +### Typescript + +修改`app.d.ts`,这是一个例子: + +```ts +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + interface Locals { + auth: Auth; + session: SessionStorage<{ user: any }>; + user: + | { + invalid?: boolean; + [key: string]: unknown; + } + | unknown; + } + // interface PageData {} + interface Platform { + env: { + SSO_ID: string; + SSO_SECRET: string; + }; + context: { + waitUntil(promise: Promise): void; + }; + caches: CacheStorage & { default: Cache }; + } + } +} + +export {}; +``` + +### 策略 + +目前已经支持的策略有: + +| Package | Meta | Changelog | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| [@svelte-dev/auth-oauth2](packages/auth-oauth2/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-oauth2?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-oauth2) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-oauth2?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-oauth2) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-oauth2?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-oauth2) | [Changelog](packages/auth-oauth2/CHANGELOG.md) | +| [@svelte-dev/auth-github](packages/auth-github/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-github?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-github) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-github?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-github) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-github?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-github) | [Changelog](packages/auth-github/CHANGELOG.md) | +| [@svelte-dev/auth-alipay](packages/auth-alipay/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-alipay?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-alipay) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-alipay?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-alipay) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-alipay?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-alipay) | [Changelog](packages/auth-alipay/CHANGELOG.md) | +| [@svelte-dev/auth-afdian](packages/auth-afdian/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-afdian?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-afdian) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-afdian?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-afdian) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-afdian?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-afdian) | [Changelog](packages/auth-afdian/CHANGELOG.md) | +| [@svelte-dev/auth-sso](packages/auth-sso/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-sso?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-sso) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-sso?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-sso) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-sso?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-sso) | [Changelog](packages/auth-sso/CHANGELOG.md) | + +注意: 不同的策略配置参数可能不尽相同。 + +> 欢迎共享您的策略。 + +## TypeDocs + +[自动化生成的接口文档](/docs/auth/) + +## 赞助 + +维护者: [Willin Wang](https://willin.wang) + +如果您对本项目感兴趣,可以通过以下方式支持我: + +- 关注我的 Github 账号:[@willin](https://github.com/willin) [![github](https://img.shields.io/github/followers/willin.svg?style=social&label=Followers)](https://github.com/willin) +- 参与 [爱发电](https://afdian.net/@willin) 计划 +- 支付宝或微信[扫码打赏](https://user-images.githubusercontent.com/1890238/89126156-0f3eeb80-d516-11ea-9046-5a3a5d59b86b.png) + +## License + +Apache-2.0 diff --git a/apps/web/src/routes/(zh)/auth/+page.server.ts b/apps/web/src/routes/(zh)/auth/+page.server.ts new file mode 100644 index 0000000..2aa1af6 --- /dev/null +++ b/apps/web/src/routes/(zh)/auth/+page.server.ts @@ -0,0 +1,10 @@ +import { redirect, type ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + const returnUrl = await locals.session.get('returnTo'); + await locals.session.unset('returnTo'); + + if (returnUrl) throw redirect(307, returnUrl); + + return { user: locals.user }; +}; diff --git a/apps/web/src/routes/(zh)/i18n/+page.md b/apps/web/src/routes/(zh)/i18n/+page.md new file mode 100644 index 0000000..44dde12 --- /dev/null +++ b/apps/web/src/routes/(zh)/i18n/+page.md @@ -0,0 +1,6 @@ +--- +title: '@svelte-dev/i18n' +desc: 一个简单好用的 Svelte 国际化脚手架工具 +--- + +待完善。 diff --git a/apps/web/src/routes/(zh)/session/+page.md b/apps/web/src/routes/(zh)/session/+page.md new file mode 100644 index 0000000..86de443 --- /dev/null +++ b/apps/web/src/routes/(zh)/session/+page.md @@ -0,0 +1,312 @@ +--- +title: '@svelte-dev/session' +desc: 一个简单好用的 Svelte Session 会话管理库 +--- + + + +## 概述 + +Session 是网站的重要组成部分,它允许服务器识别来自同一个人的请求,尤其是在涉及服务器端表单验证或页面上没有 JavaScript 时。会话是许多允许用户“登录”的网站的基本构建块,包括社交、电子商务、商业和教育网站。 + +@svelte-dev/session 附带了几个用于常见场景的预构建 Session 存储桶的方式,以及一个用于创建自定义 SessionStorage 的策略: + +- 创建自定义的 Session 存储策略 +- `CookieSessionStrategy` +- `MemoryStrategy` +- `CloudflareKVStrategy` (可用于 Cloudflare Workers、Pages) + +## 演示 + +
+
+
+
您已经访问本页面
+
{$page.data.views} 次
+
刷新页面查看更新
+
+
+
+ +## 安装 + +可以通过 `npm`, `yarn`, `pnpm` 或者 `bun` 进行安装: + +```bash +npm add @svelte-dev/session +``` + +## 基础使用 + +### 使用 `handleSession` 提供的傻瓜化注入 + +在项目根目录下创建 `hooks.server.ts`: + +```ts +import { handleSession } from '@svelte-dev/session'; + +export const handle = handleSession({ + // 选择您所需要的适配器 + adapter: { + name: 'cookie', + options: { + chunk: true + } + }, + // Session 会话配置 + session: { + key: '__sid', + secrets: ['s3cr3t'] + }, + // Cookie 配置 + cookie: { + path: '/', + sameSite: 'lax', + secure: true, + httpOnly: true + } +}); +``` + +### Svelte 5 中使用 + +在 `+page.server.ts` 中读取、修改 Session 数据: + +```ts +import type { ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + const views = locals.session.get('views') ?? 0; + await locals.session.set('views', views + 1); + return {}; +}; +``` + +在 `svelte5 runes` 组件中读取: + +```svelte + + +您访问了本页面 {data.session.views} 次 +``` + +### Svelte 4 中使用 + +在 `+page.server.ts` 中读取、修改 Session 数据: + +```ts +import type { ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + const views = locals.session.get('views') ?? 0; + await locals.session.set('views', views + 1); + return { views }; +}; +``` + +在 `svelte5 runes` 组件中读取: + +```svelte + + +您访问了本页面 {$page.data.views} 次 +``` + +## 进阶使用 + +### Cloudflare KV + +从 `hooks.server.ts` 中初始化: + +```ts +import { handleSession } from '@svelte-dev/session'; + +export const handle = handleSession({ + adapter: { + name: 'cloudflare-kv', + options: { + namespace: 'SESSION' + } + }, + session: { + secrets: ['s3cr3t'] + }, + cookie: { + path: '/' + } +}); +``` + +详细 Cloudflare adapter 文档: + +### 自定义 Handle 方法 + +```ts +export const handle = handleSession({ + adapter: { + name: 'cookie', + options: { + chunk: true + } + }, + session: { + key: '__sid', + secrets: ['s3cr3t'] + }, + cookie: { + path: '/', + sameSite: 'lax', + secure: true, + httpOnly: true + }, + ({ event, resolve }) => { + // 可以通过 `event.locals.session` 访问 Session 数据 + // 自定义 handle 方法从这里开始写 + return resolve(event); + } +); +``` + +同时,你还可以用 [sequence()](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks-sequence) 来组合多个 handle 方法: + +```ts +const sessionHandler = handleSession({ + // ... 配置 +}); + +export const handle = sequence(sessionHandler, ({ resolve, event }) => { + // 可以通过 `event.locals.session` 访问 Session 数据 + // 自定义 handle 方法从这里开始写 + return resolve(event); +}); +``` + +### Typescript 类型 + +这里有一个 `app.d.ts` 的示例,根据实际需要修改: + +```ts +import type { FlashSessionData, SessionData, SessionStorage } from '@svelte-dev/session'; + +declare global { + namespace App { + // interface Error {} + interface Locals { + session: SessionStorage<{ views: number }>; + } + interface PageData { + session: FlashSessionData; + } + interface Session extends SessionStorage {} + // interface Platform {} + } +} +``` + +### 创建自定义 Session 策略 + +接口定义如下: + +```ts +export interface SessionStorageStrategy { + /** + * Creates a new record with the given data and returns the session id. + */ + createData: (data: FlashSessionData, expires?: Date) => Promise; + + /** + * Returns data for a given session id, or `null` if there isn't any. + */ + readData: (id: string) => Promise | null>; + + /** + * Updates data for the given session id. + */ + updateData: ( + id: string, + data: FlashSessionData, + expires?: Date + ) => Promise; + + /** + * Deletes data for a given session id from the data store. + */ + deleteData: (id: string) => Promise; +} +``` + +一个简单的实现示例: + +```ts +import type { RequestEvent } from '@sveltejs/kit'; +import type { + CookieOptions, + FlashSessionData, + SessionData, + SessionOptions, + SessionStorageStrategy +} from '@svelte-dev/session'; + +export type YourStrageOptions = { + /** + * Example + */ + key?: string; +}; + +export class YourStrategy + implements SessionStorageStrategy +{ + constructor( + event: RequestEvent, + options: YourStrageOptions & { cookie: CookieOptions; session: SessionOptions } + ) {} + /** + * Creates a new record with the given data and returns the session id. + */ + async createData(data, expires?: Date): Promise {} + + /** + * Returns data for a given session id, or `null` if there isn't any. + */ + async readData(id: string): Promise | null> {} + + /** + * Updates data for the given session id. + */ + async updateData( + id: string, + data: FlashSessionData, + expires?: Date + ): Promise {} + + /** + * Deletes data for a given session id from the data store. + */ + async deleteData(id: string): Promise {} +} +``` + +## TypeDocs + +[自动化生成的接口文档](/docs/session/) + +## 赞助 + +维护者: [Willin Wang](https://willin.wang) + +如果您对本项目感兴趣,可以通过以下方式支持我: + +- 关注我的 Github 账号:[@willin](https://github.com/willin) [![github](https://img.shields.io/github/followers/willin.svg?style=social&label=Followers)](https://github.com/willin) +- 参与 [爱发电](https://afdian.net/@willin) 计划 +- 支付宝或微信[扫码打赏](https://user-images.githubusercontent.com/1890238/89126156-0f3eeb80-d516-11ea-9046-5a3a5d59b86b.png) + +## License + +Apache-2.0 diff --git a/apps/web/src/routes/[[lang=locale]]/demo/+page.server.ts b/apps/web/src/routes/(zh)/session/+page.server.ts similarity index 91% rename from apps/web/src/routes/[[lang=locale]]/demo/+page.server.ts rename to apps/web/src/routes/(zh)/session/+page.server.ts index 0c40548..82ebc53 100644 --- a/apps/web/src/routes/[[lang=locale]]/demo/+page.server.ts +++ b/apps/web/src/routes/(zh)/session/+page.server.ts @@ -3,5 +3,5 @@ import type { ServerLoad } from '@sveltejs/kit'; export const load: ServerLoad = async ({ locals }) => { const views = locals.session.get('views') ?? 0; await locals.session.set('views', views + 1); - return {}; + return { views }; }; diff --git a/apps/web/src/routes/[[lang=locale]]/+layout.server.ts b/apps/web/src/routes/+layout.server.ts similarity index 82% rename from apps/web/src/routes/[[lang=locale]]/+layout.server.ts rename to apps/web/src/routes/+layout.server.ts index 5915cd8..20acfba 100644 --- a/apps/web/src/routes/[[lang=locale]]/+layout.server.ts +++ b/apps/web/src/routes/+layout.server.ts @@ -1,4 +1,4 @@ -import type { LayoutServerLoad } from '../$types.js'; +import type { LayoutServerLoad } from './$types.js'; export const load: LayoutServerLoad = ({ locals }) => { const { user } = locals; // locals.user set by hooks.server.ts/handle(), undefined if not logged in diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte new file mode 100644 index 0000000..8a7ec11 --- /dev/null +++ b/apps/web/src/routes/+layout.svelte @@ -0,0 +1,31 @@ + + + + +
+ +
+ +
+ +
diff --git a/apps/web/src/routes/[[lang=locale]]/+layout.ts b/apps/web/src/routes/+layout.ts similarity index 100% rename from apps/web/src/routes/[[lang=locale]]/+layout.ts rename to apps/web/src/routes/+layout.ts diff --git a/apps/web/src/routes/[[lang=locale]]/+layout.svelte b/apps/web/src/routes/[[lang=locale]]/+layout.svelte deleted file mode 100644 index db2d786..0000000 --- a/apps/web/src/routes/[[lang=locale]]/+layout.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -
-

@svelte-dev/auth & @svelte-dev/session

- -
-

Demo

-

{$t('site.title')}

- -
-
- - diff --git a/apps/web/src/routes/[[lang=locale]]/+page.svelte b/apps/web/src/routes/[[lang=locale]]/+page.svelte index 96829cc..6efcf4d 100644 --- a/apps/web/src/routes/[[lang=locale]]/+page.svelte +++ b/apps/web/src/routes/[[lang=locale]]/+page.svelte @@ -1,3 +1,60 @@ - - -Github Login + + +
+
+
+
+

Svelte Turbo

+

Make rockets for rookies.

+
+
+
+

@svelte-dev/session

+

{$t('common.npm_downloads')}: {$downloadsSession}

+ +
+
+
+
+

@svelte-dev/auth

+

{$t('common.npm_downloads')}: {$downloadsAuth}

+ +
+
+
+
+

@svelte-dev/i18n

+

{$t('common.npm_downloads')}: {$downloadsI18n}

+ +
+
+
+
+
+
diff --git a/apps/web/src/routes/[[lang=locale]]/demo/+page.svelte b/apps/web/src/routes/[[lang=locale]]/demo/+page.svelte deleted file mode 100644 index 8acbe28..0000000 --- a/apps/web/src/routes/[[lang=locale]]/demo/+page.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -{#if data && data.session.views} -
- You have visited this page {data.session.views} times. -
-{/if} - -{#if data && data.user} -
-
{JSON.stringify(data.user, null, 2)}
-
-{/if} diff --git a/apps/web/src/routes/en/auth/+page.md b/apps/web/src/routes/en/auth/+page.md new file mode 100644 index 0000000..4436e38 --- /dev/null +++ b/apps/web/src/routes/en/auth/+page.md @@ -0,0 +1,204 @@ +--- +title: '@svelte-dev/auth' +desc: A simple and easy-to-use Svelte identity management library +--- + + + +![Logo](https://repository-images.githubusercontent.com/726691357/f09bf6fc-3844-4584-8eee-6bfb425d8a38) + +## Features + +- Full **Server-Side** Authentication +- Complete **TypeScript** Support +- **Strategy**-based Authentication +- Easily handle **success and failure** +- Implement **custom** strategies +- Supports persistent **sessions** + +## Overview + +Svelte Auth is a complete open-source authentication solution for Svelte applications. + +Heavily inspired by [Passport.js](https://passportjs.org) and [Remix-Auth](https://github.com/sergiodxa/remix-auth), but completely rewrote it from scratch to work on top of the [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). Svelte Auth can be dropped in to any Svelte-based application with minimal setup. + +As with Passport.js, it uses the strategy pattern to support the different authentication flows. Each strategy is published individually as a separate npm package. + +## Demo + +{#if $page && $page.data.user} + +
+
{JSON.stringify($page.data.user, null, 2)}
+
+{:else} + + + Github Login + +{/if} + +## Installation + +To use it, install it from npm (yarn or bun): + +```bash +npm install @svelte-dev/auth @svelte-dev/session +``` + +## Usage + +Here's an simple Example: + +```ts +// hooks.server.ts +import { env } from '$env/dynamic/private'; +import { sequence } from '@sveltejs/kit/hooks'; +import { handleAuth } from '@svelte-dev/auth'; +import { OAuth2Strategy } from '@svelte-dev/auth-oauth2'; + +const oauthStrategy = new OAuth2Strategy( + { + clientID: env.SSO_ID, + clientSecret: env.SSO_SECRET, + callbackURL: env.SSO_CALLBACK_URL || 'http://localhost:8788/auth/oauth2/callback' + }, + async ({ profile }) => { + // Get the user data from your DB or API using the tokens and profile + return profile; + } +); + +export const handle = handleAuth({ + // Auth Options + autoRouting: true, + strategies: [oauthStrategy], + sessionKey: 'user', + sessionErrorKey: 'auth:error', + sessionStrategyKey: 'strategy', + successRedirect: '/', + failureRedirect: '/', + // Session Storage Options + adapter: { + name: 'cookie', + options: { + chunk: true + } + }, + session: { + secrets: ['s3cr3t'] + }, + cookie: { + secure: !!env.SSO_CALLBACK_URL, + sameSite: 'lax', + path: '/', + httpOnly: !!env.SSO_CALLBACK_URL + } +}); +``` + +That's it. + +## Advanced Usage + +### Custom Handle + +If you did not set `authRouting`. You need to add a login handler `src/routes/auth/[provider]/+server.ts`: + +```ts +import { redirect, type RequestEvent } from '@sveltejs/kit'; + +export const GET = async (event: RequestEvent) => { + const { request } = event; + const provider = event.params.provider ?? 'github'; + return await event.locals.auth.authenticate(event, provider, { + successRedirect: '/dashboard', + failureRedirect: '/error' + }); +}; +``` + +Then, add a callback handler `src/routes/auth/[provider]/callback/+server.ts.ts`: + +```ts +// same as before... +import type { RequestEvent } from '@sveltejs/kit'; + +export const GET = async (event: RequestEvent) => { + const provider = event.params.provider ?? 'github'; + + return await event.locals.auth.authenticate(event, provider, { + successRedirect: '/dashboard', + failureRedirect: '/error' + }); +}; +``` + +### Typescript + +Modify `app.d.ts`, here is an example: + +```ts +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + interface Locals { + auth: Auth; + session: SessionStorage<{ user: any }>; + user: + | { + invalid?: boolean; + [key: string]: unknown; + } + | unknown; + } + // interface PageData {} + interface Platform { + env: { + SSO_ID: string; + SSO_SECRET: string; + }; + context: { + waitUntil(promise: Promise): void; + }; + caches: CacheStorage & { default: Cache }; + } + } +} + +export {}; +``` + +### Strategies + +| Package | Meta | Changelog | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| [@svelte-dev/auth-oauth2](packages/auth-oauth2/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-oauth2?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-oauth2) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-oauth2?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-oauth2) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-oauth2?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-oauth2) | [Changelog](packages/auth-oauth2/CHANGELOG.md) | +| [@svelte-dev/auth-github](packages/auth-github/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-github?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-github) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-github?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-github) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-github?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-github) | [Changelog](packages/auth-github/CHANGELOG.md) | +| [@svelte-dev/auth-alipay](packages/auth-alipay/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-alipay?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-alipay) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-alipay?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-alipay) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-alipay?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-alipay) | [Changelog](packages/auth-alipay/CHANGELOG.md) | +| [@svelte-dev/auth-afdian](packages/auth-afdian/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-afdian?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-afdian) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-afdian?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-afdian) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-afdian?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-afdian) | [Changelog](packages/auth-afdian/CHANGELOG.md) | +| [@svelte-dev/auth-sso](packages/auth-sso/) | [![npm](https://img.shields.io/npm/v/@svelte-dev/auth-sso?style=flat-square&logo=npm)](https://npmjs.org/package/@svelte-dev/auth-sso) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth-sso?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-sso) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth-sso?style=flat-square&label=down)](https://npmjs.org/package/@svelte-dev/auth-sso) | [Changelog](packages/auth-sso/CHANGELOG.md) | + +> Welcome to share your strategies here. + +## TypeDocs + +[API Spec](/docs/auth/) + +## Sponsor + +Owner: [Willin Wang](https://willin.wang) + +Donation ways: + +- Follow me: [@willin](https://github.com/willin) [![github](https://img.shields.io/- Github: +- Paypal: +- Alipay or Wechat Pay: [QRCode](https://user-images.githubusercontent.com/1890238/89126156-0f3eeb80-d516-11ea-9046-5a3a5d59b86b.png) + +## License + +Apache-2.0 diff --git a/apps/web/src/routes/en/auth/+page.server.ts b/apps/web/src/routes/en/auth/+page.server.ts new file mode 100644 index 0000000..4d0d5cf --- /dev/null +++ b/apps/web/src/routes/en/auth/+page.server.ts @@ -0,0 +1,5 @@ +import type { ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + return { user: locals.user }; +}; diff --git a/apps/web/src/routes/en/i18n/+page.md b/apps/web/src/routes/en/i18n/+page.md new file mode 100644 index 0000000..08240a3 --- /dev/null +++ b/apps/web/src/routes/en/i18n/+page.md @@ -0,0 +1,6 @@ +--- +title: '@svelte-dev/i18n' +desc: A simple and easy-to-use Svelte I18n management library +--- + +TBD. diff --git a/apps/web/src/routes/en/session/+page.md b/apps/web/src/routes/en/session/+page.md new file mode 100644 index 0000000..5405c31 --- /dev/null +++ b/apps/web/src/routes/en/session/+page.md @@ -0,0 +1,316 @@ +--- +title: '@svelte-dev/session' +desc: A simple and easy-to-use Svelte Session storage management library +--- + + + +## Overview + +Sessions are an important part of websites that allow the server to identify requests coming from the same person, especially when it comes to server-side form validation or when JavaScript is not on the page. Sessions are a fundamental building block of many sites that let users "log in", including social, e-commerce, business, and educational websites. + +Svelte-Session comes with several pre-built session storage options for common scenarios, and one to create your own: + +- custom storage with `createCustomStrategy` +- `CookieSessionStrategy` +- `MemoryStrategy` +- `CloudflareKVStrategy` (Cloudflare Workers) + +## Demo + +## Installation + +To use it, install it from `npm` (`yarn` or `bun`): + +```bash +npm add @svelte-dev/session +``` + +## Demo + +
+
+
+
You have visited
+
{$page.data.views}
+
Refresh to check
+
+
+
+ +## Usage + +Init from `hooks.server.ts`: + +```ts +import { handleSession } from '@svelte-dev/session'; + +export const handle = handleSession({ + adapter: { + name: 'cookie', + options: { + chunk: true + } + }, + session: { + key: '__sid', + secrets: ['s3cr3t'] + }, + cookie: { + path: '/', + sameSite: 'lax', + secure: true, + httpOnly: true + } +}); +``` + +### Svelte 5 + +Load Data from `+page.server.ts`: + +```ts +import type { ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + const views = locals.session.get('views') ?? 0; + await locals.session.set('views', views + 1); + return {}; +}; +``` + +Use in `svelte5` runes component: + +```svelte + + +{data.session.views} +``` + +### Svelte 4 + +Load Data from `+page.server.ts`: + +```ts +import type { ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + const views = locals.session.get('views') ?? 0; + await locals.session.set('views', views + 1); + return { views }; +}; +``` + +Use in `svelte4` component: + +```svelte + + +{$page.data.views} +``` + +## Advanced Usage + +### Cloudflare KV + +Init from `hooks.server.ts`: + +```ts +import { handleSession } from '@svelte-dev/session'; + +export const handle = handleSession({ + adapter: { + name: 'cloudflare-kv', + options: { + namespace: 'SESSION' + } + }, + session: { + secrets: ['s3cr3t'] + }, + cookie: { + path: '/' + } +}); +``` + +Checkout the docs for more details: + +### Custom Handler + +```ts +export const handle = handleSession({ + adapter: { + name: 'cookie', + options: { + chunk: true + } + }, + session: { + key: '__sid', + secrets: ['s3cr3t'] + }, + cookie: { + path: '/', + sameSite: 'lax', + secure: true, + httpOnly: true + }, + ({ event, resolve }) => { + // event.locals is populated with the session `event.locals.session` + // Do anything you want here + return resolve(event); + } +); +``` + +In case you're using [sequence()](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks-sequence), do this + +```ts +const sessionHandler = handleSession({ + adapter: { + name: 'cookie', + options: { + chunk: true + } + } +}); + +export const handle = sequence(sessionHandler, ({ resolve, event }) => { + // event.locals is populated with the session `event.locals.session` + // event.locals is also populated with all parsed cookies by handleSession, it would cause overhead to parse them again - `event.locals.cookies`. + // Do anything you want here + return resolve(event); +}); +``` + +### Typescript + +Here's a simple example, modify `app.d.ts`: + +```ts +import type { FlashSessionData, SessionData, SessionStorage } from '@svelte-dev/session'; + +declare global { + namespace App { + // interface Error {} + interface Locals { + session: SessionStorage<{ views: number }>; + } + interface PageData { + session: FlashSessionData; + } + interface Session extends SessionStorage {} + // interface Platform {} + } +} +``` + +### Create your own stragety + +Interface: + +```ts +export interface SessionStorageStrategy { + /** + * Creates a new record with the given data and returns the session id. + */ + createData: (data: FlashSessionData, expires?: Date) => Promise; + + /** + * Returns data for a given session id, or `null` if there isn't any. + */ + readData: (id: string) => Promise | null>; + + /** + * Updates data for the given session id. + */ + updateData: ( + id: string, + data: FlashSessionData, + expires?: Date + ) => Promise; + + /** + * Deletes data for a given session id from the data store. + */ + deleteData: (id: string) => Promise; +} +``` + +A simple example: + +```ts +import type { RequestEvent } from '@sveltejs/kit'; +import type { + CookieOptions, + FlashSessionData, + SessionData, + SessionOptions, + SessionStorageStrategy +} from '@svelte-dev/session'; + +export type YourStrageOptions = { + /** + * Example + */ + key?: string; +}; + +export class YourStrategy + implements SessionStorageStrategy +{ + constructor( + event: RequestEvent, + options: YourStrageOptions & { cookie: CookieOptions; session: SessionOptions } + ) {} + /** + * Creates a new record with the given data and returns the session id. + */ + async createData(data, expires?: Date): Promise {} + + /** + * Returns data for a given session id, or `null` if there isn't any. + */ + async readData(id: string): Promise | null> {} + + /** + * Updates data for the given session id. + */ + async updateData( + id: string, + data: FlashSessionData, + expires?: Date + ): Promise {} + + /** + * Deletes data for a given session id from the data store. + */ + async deleteData(id: string): Promise {} +} +``` + +## TypeDocs + +[API Spec](/docs/session/) + +## Sponsor + +Owner: [Willin Wang](https://willin.wang) + +Donation ways: + +- Follow me:[@willin](https://github.com/willin) [![github](https://img.shields.io/github/followers/willin.svg?style=social&label=Followers)](https://github.com/willin) +- Github: +- Paypal: +- Alipay or Wechat Pay: [QRCode](https://user-images.githubusercontent.com/1890238/89126156-0f3eeb80-d516-11ea-9046-5a3a5d59b86b.png) + +## License + +Apache-2.0 diff --git a/apps/web/src/routes/en/session/+page.server.ts b/apps/web/src/routes/en/session/+page.server.ts new file mode 100644 index 0000000..82ebc53 --- /dev/null +++ b/apps/web/src/routes/en/session/+page.server.ts @@ -0,0 +1,7 @@ +import type { ServerLoad } from '@sveltejs/kit'; + +export const load: ServerLoad = async ({ locals }) => { + const views = locals.session.get('views') ?? 0; + await locals.session.set('views', views + 1); + return { views }; +}; diff --git a/apps/web/static/ads.txt b/apps/web/static/ads.txt new file mode 100644 index 0000000..e2a8718 --- /dev/null +++ b/apps/web/static/ads.txt @@ -0,0 +1 @@ +google.com, pub-5059418763237956, DIRECT, f08c47fec0942fa0 diff --git a/apps/web/svelte.config.js b/apps/web/svelte.config.js index 2fd1699..abcc45f 100644 --- a/apps/web/svelte.config.js +++ b/apps/web/svelte.config.js @@ -1,24 +1,41 @@ import adapter from '@sveltejs/adapter-cloudflare'; import { vitePreprocess } from '@sveltejs/kit/vite'; +import { mdsvex } from 'mdsvex'; +import { preprocessMeltUI } from '@melt-ui/pp'; +import mdsvexConfig from './mdsvex.config.js'; /** @type {import('@sveltejs/kit').Config} */ const config = { // Consult https://kit.svelte.dev/docs/integrations#preprocessors // for more information about preprocessors - preprocess: vitePreprocess(), - compilerOptions: { - runes: true - }, + extensions: ['.svelte', ...mdsvexConfig.extensions], + preprocess: [mdsvex(mdsvexConfig), vitePreprocess(), preprocessMeltUI()], + // compilerOptions: { + // runes: true + // }, kit: { + alias: { + $components: './src/components' + }, // 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({ + pages: 'build', + assets: 'build', + fallback: null, + precompress: true, routes: { include: ['/*'], - exclude: ['', '', '/favicon.png', '/docs/*'] + exclude: ['', '', '/favicon.png', '/ads.txt', '/images', '/docs/*'] } }) + }, + onwarn: (warning, handler) => { + if (warning.code.startsWith('a11y-no-noninteractive-tabindex')) { + return; + } + handler(warning); } }; diff --git a/apps/web/tailwind.config.cjs b/apps/web/tailwind.config.cjs new file mode 100644 index 0000000..358e5d6 --- /dev/null +++ b/apps/web/tailwind.config.cjs @@ -0,0 +1,25 @@ +/** @type {import('tailwindcss').Config} */ + +export default { + content: [ + // + './src/**/*.{html,svelte,js,ts,md,svx,svelte.md}' + ], + theme: { + extend: { + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))' + } + } + }, + daisyui: { + themes: true, // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"] + darkTheme: 'sunset', // name of one of the included themes for dark mode + base: true, // applies background color and foreground color for root element by default + styled: true, // include daisyUI colors and design decisions for all components + utils: true, // adds responsive and modifier utility classes + themeRoot: ':root' // The element that receives theme color CSS variables + }, + plugins: [require('@tailwindcss/typography'), require('daisyui')] +}; diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 0131ff9..fc2afe1 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,8 +1,14 @@ +import path from 'node:path'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [sveltekit()], + resolve: { + alias: { + $components: path.resolve('/src/components') + } + }, test: { include: ['src/**/*.{test,spec}.{js,ts}'] } diff --git a/bun.lockb b/bun.lockb index 3f3bc8d..c848cd7 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/auth-oauth2/vite.config.ts.timestamp-1702359796228-e895b48aa94cc.mjs b/packages/auth-oauth2/vite.config.ts.timestamp-1702359796228-e895b48aa94cc.mjs new file mode 100644 index 0000000..fcca68a --- /dev/null +++ b/packages/auth-oauth2/vite.config.ts.timestamp-1702359796228-e895b48aa94cc.mjs @@ -0,0 +1,10 @@ +// vite.config.ts +import { sveltekit } from "file:///Users/v0/Projects/svelte-dev/svelte-turbo/node_modules/@sveltejs/kit/src/exports/vite/index.js"; +import { defineConfig } from "file:///Users/v0/Projects/svelte-dev/svelte-turbo/node_modules/vite/dist/node/index.js"; +var vite_config_default = defineConfig({ + plugins: [sveltekit()] +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvdjAvUHJvamVjdHMvc3ZlbHRlLWRldi9zdmVsdGUtdHVyYm8vcGFja2FnZXMvYXV0aC1vYXV0aDJcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9Vc2Vycy92MC9Qcm9qZWN0cy9zdmVsdGUtZGV2L3N2ZWx0ZS10dXJiby9wYWNrYWdlcy9hdXRoLW9hdXRoMi92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vVXNlcnMvdjAvUHJvamVjdHMvc3ZlbHRlLWRldi9zdmVsdGUtdHVyYm8vcGFja2FnZXMvYXV0aC1vYXV0aDIvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBzdmVsdGVraXQgfSBmcm9tICdAc3ZlbHRlanMva2l0L3ZpdGUnO1xuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtzdmVsdGVraXQoKV1cbn0pO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUErVyxTQUFTLGlCQUFpQjtBQUN6WSxTQUFTLG9CQUFvQjtBQUU3QixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTLENBQUMsVUFBVSxDQUFDO0FBQ3ZCLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/packages/auth/README.md b/packages/auth/README.md index acf9606..74b12ed 100644 --- a/packages/auth/README.md +++ b/packages/auth/README.md @@ -4,157 +4,7 @@ [![github](https://img.shields.io/github/followers/willin.svg?style=social&label=Followers)](https://github.com/willin) [![npm](https://img.shields.io/npm/v/@svelte-dev/auth.svg)](https://npmjs.org/package/@svelte-dev/auth) [![npm](https://img.shields.io/npm/dm/@svelte-dev/auth.svg)](https://npmjs.org/package/@svelte-dev/auth) [![npm](https://img.shields.io/npm/dt/@svelte-dev/auth.svg)](https://npmjs.org/package/@svelte-dev/auth) -Simple Authentication for [Svlelte](https://svelte.dev/). - -## Features - -- Full **Server-Side** Authentication -- Complete **TypeScript** Support -- **Strategy**-based Authentication -- Easily handle **success and failure** -- Implement **custom** strategies -- Supports persistent **sessions** - -## Overview - -Svelte Auth is a complete open-source authentication solution for Svelte applications. - -Heavily inspired by [Passport.js](https://passportjs.org) and [Remix-Auth](https://github.com/sergiodxa/remix-auth), but completely rewrote it from scratch to work on top of the [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). Svelte Auth can be dropped in to any Svelte-based application with minimal setup. - -As with Passport.js, it uses the strategy pattern to support the different authentication flows. Each strategy is published individually as a separate npm package. - -## Installation - -To use it, install it from npm (yarn or bun): - -```bash -npm install @svelte-dev/auth @svelte-dev/session -``` - -## Usage - -API Specification: [Documentation](https://svelte-auth.js.cool/docs/) - -Here's an simple Example: - -```ts -// hooks.server.ts -import { env } from '$env/dynamic/private'; -import { sequence } from '@sveltejs/kit/hooks'; -import { handleAuth } from '@svelte-dev/auth'; -import { OAuth2Strategy } from '@svelte-dev/auth-oauth2'; - -const oauthStrategy = new OAuth2Strategy( - { - clientID: env.SSO_ID, - clientSecret: env.SSO_SECRET, - callbackURL: env.SSO_CALLBACK_URL || 'http://localhost:8788/auth/oauth2/callback' - }, - async ({ profile }) => { - // Get the user data from your DB or API using the tokens and profile - return profile; - } -); - -export const handle = handleAuth({ - // Auth Options - autoRouting: true, - strategies: [oauthStrategy], - sessionKey: 'user', - sessionErrorKey: 'auth:error', - sessionStrategyKey: 'strategy', - successRedirect: '/', - failureRedirect: '/', - // Session Storage Options - adapter: { - name: 'cookie', - options: { - chunk: true - } - }, - session: { - secrets: ['s3cr3t'] - }, - cookie: { - secure: !!env.SSO_CALLBACK_URL, - sameSite: 'lax', - path: '/', - httpOnly: !!env.SSO_CALLBACK_URL - } -}); -``` - -That's it. - -## Advanced Usage - -If you did not set `authRouting`. You need to add a login handler `src/routes/auth/[provider]/+server.ts`: - -```ts -import { redirect, type RequestEvent } from '@sveltejs/kit'; - -export const GET = async (event: RequestEvent) => { - const { request } = event; - const provider = event.params.provider ?? 'github'; - return await event.locals.auth.authenticate(event, provider, { - successRedirect: '/dashboard', - failureRedirect: '/error' - }); -}; -``` - -Then, add a callback handler `src/routes/auth/[provider]/callback/+server.ts.ts`: - -```ts -// same as before... -import type { RequestEvent } from '@sveltejs/kit'; - -export const GET = async (event: RequestEvent) => { - const provider = event.params.provider ?? 'github'; - - return await event.locals.auth.authenticate(event, provider, { - successRedirect: '/dashboard', - failureRedirect: '/error' - }); -}; -``` - -### Typescript - -Modify `app.d.ts`, here is an example: - -```ts -// See https://kit.svelte.dev/docs/types#app -// for information about these interfaces -declare global { - namespace App { - // interface Error {} - interface Locals { - auth: Auth; - session: SessionStorage<{ user: any }>; - user: - | { - invalid?: boolean; - [key: string]: unknown; - } - | unknown; - } - // interface PageData {} - interface Platform { - env: { - SSO_ID: string; - SSO_SECRET: string; - }; - context: { - waitUntil(promise: Promise): void; - }; - caches: CacheStorage & { default: Cache }; - } - } -} - -export {}; -``` +[中文文档](https://svelte.js.cool/auth) | [English Docs](https://svelte.js.cool/en/auth) ## 赞助 Sponsor diff --git a/packages/auth/src/lib/auth/auth.ts b/packages/auth/src/lib/auth/auth.ts index 6bd489c..9bcead7 100644 --- a/packages/auth/src/lib/auth/auth.ts +++ b/packages/auth/src/lib/auth/auth.ts @@ -90,19 +90,35 @@ export class Auth { if (!inject || !event.url.pathname.startsWith('/auth')) { return; } + const params = event.url.pathname.split('/'); + if (params.length !== 3 && params.length !== 4) { + // /auth + return; + } + if (![...this.#strategies.keys()].includes(params[2])) { + // /auth/invalid-provider + return; + } + if (params.length === 4 && params[3] !== 'callback') { + // /auth/provider/invalid-path + return; + } + const returnUrl = event.url?.searchParams?.get('redirect_uri'); + if (returnUrl) { + await this.#session.flash('returnTo', returnUrl); + } if ((event.locals as any).user) { - throw redirect(307, this.#options.successRedirect ?? '/'); + throw redirect(307, returnUrl || this.#options.successRedirect || '/'); } - const params = event.url.pathname.split('/'); const idx = params.findIndex((x) => x === 'auth') + 1; const provider = idx > 0 && idx < params.length ? params[idx] : ''; if (!provider) { - throw redirect(307, this.#options.failureRedirect ?? '/'); + throw redirect(307, this.#options.failureRedirect || '/'); } await this.authenticate(event, provider, { - successRedirect: this.#options.successRedirect ?? '/', - failureRedirect: this.#options.failureRedirect ?? '/' + successRedirect: this.#options.successRedirect || '/', + failureRedirect: this.#options.failureRedirect || '/' }); } } diff --git a/packages/i18n/README.md b/packages/i18n/README.md index 2fd3d95..68a2308 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -1,5 +1,9 @@ # @svelte-dev/i18n +[![github](https://img.shields.io/github/followers/willin.svg?style=social&label=Followers)](https://github.com/willin) [![npm](https://img.shields.io/npm/v/@svelte-dev/i18n.svg)](https://npmjs.org/package/@svelte-dev/i18n) [![npm](https://img.shields.io/npm/dm/@svelte-dev/i18n.svg)](https://npmjs.org/package/@svelte-dev/i18n) [![npm](https://img.shields.io/npm/dt/@svelte-dev/i18n.svg)](https://npmjs.org/package/@svelte-dev/i18n) + +[中文文档](https://svelte.js.cool/i18n) | [English Docs](https://svelte.js.cool/en/i18n) + ## 赞助 Sponsor 维护者 Owner: [Willin Wang](https://willin.wang) diff --git a/packages/i18n/src/lib/i18n/dictionary.ts b/packages/i18n/src/lib/i18n/dictionary.ts index 0a2b1f2..f01a6cb 100644 --- a/packages/i18n/src/lib/i18n/dictionary.ts +++ b/packages/i18n/src/lib/i18n/dictionary.ts @@ -1,5 +1,5 @@ import deepmerge from 'deepmerge'; -import { derived, writable } from 'svelte/store'; +import { derived, writable, type Readable } from 'svelte/store'; import type { I18nDict } from './types.js'; import { getPossibleLocales } from './locale.js'; @@ -72,7 +72,9 @@ export function addMessages(locale: string, ...partials: I18nDict[]) { }); } -const $locales = derived([$dictionary], ([dictionary]) => Object.keys(dictionary)); +const $locales: Readable = derived([$dictionary], ([dictionary]) => + Object.keys(dictionary) +); $dictionary.subscribe((newDictionary) => (dictionary = newDictionary)); diff --git a/packages/session/README.md b/packages/session/README.md index f87fe88..b403fab 100644 --- a/packages/session/README.md +++ b/packages/session/README.md @@ -2,248 +2,7 @@ [![github](https://img.shields.io/github/followers/willin.svg?style=social&label=Followers)](https://github.com/willin) [![npm](https://img.shields.io/npm/v/@svelte-dev/session.svg)](https://npmjs.org/package/@svelte-dev/session) [![npm](https://img.shields.io/npm/dm/@svelte-dev/session.svg)](https://npmjs.org/package/@svelte-dev/session) [![npm](https://img.shields.io/npm/dt/@svelte-dev/session.svg)](https://npmjs.org/package/@svelte-dev/session) -Simple Session Storage Management for [Svlelte](https://svelte.dev/). - - - - -- [@svelte-dev/session](#svelte-devsession) - - [Overview](#overview) - - [Installation](#installation) - - [Usage](#usage) - - [Advanced Usage](#advanced-usage) - - [Session API](#session-api) - - [Cloudflare KV](#cloudflare-kv) - - [Custom Handler](#custom-handler) - - [Typescript](#typescript) - - [Create your own stragety](#create-your-own-stragety) - - [赞助 Sponsor](#赞助-sponsor) - - [许可证 License](#许可证-license) - - - -## Overview - -Sessions are an important part of websites that allow the server to identify requests coming from the same person, especially when it comes to server-side form validation or when JavaScript is not on the page. Sessions are a fundamental building block of many sites that let users "log in", including social, e-commerce, business, and educational websites. - -Svelte-Session comes with several pre-built session storage options for common scenarios, and one to create your own: - -- custom storage with `createCustomStrategy` -- `CookieSessionStrategy` -- `MemoryStrategy` -- `CloudflareKVStrategy` (Cloudflare Workers) - -## Installation - -To use it, install it from npm (yarn or bun): - -```bash -npm install @svelte-dev/session -``` - -## Usage - -Init from `hooks.server.ts`: - -```ts -import { handleSession } from '@svelte-dev/session'; - -export const handle = handleSession({ - adapter: { - name: 'cookie', - options: { - chunk: true - } - }, - session: { - key: '__sid', - secrets: ['s3cr3t'] - }, - cookie: { - path: '/', - sameSite: 'lax', - secure: true, - httpOnly: true - } -}); -``` - -Load Data from `+page.server.ts`: - -```ts -import type { ServerLoad } from '@sveltejs/kit'; - -export const load: ServerLoad = async ({ locals }) => { - const views = locals.session.get('views') ?? 0; - await locals.session.set('views', views + 1); - return {}; -}; -``` - -Use in `svelte5` runes component: - -```svelte - - -
-  {JSON.stringify(data, null, 2)}
-
-``` - -## Advanced Usage - -### Session API - -See: For more details - -### Cloudflare KV - -Init from `hooks.server.ts`: - -```ts -import { handleSession } from '@svelte-dev/session'; - -export const handle = handleSession({ - adapter: { - name: 'cloudflare-kv', - options: { - namespace: 'SESSION' - } - }, - session: { - secrets: ['s3cr3t'] - }, - cookie: { - path: '/' - } -}); -``` - -Checkout the docs for more details: - -### Custom Handler - -```ts -export const handle = handleSession({ - adapter: { - name: 'cookie', - options: { - chunk: true - } - }, - session: { - key: '__sid', - secrets: ['s3cr3t'] - }, - cookie: { - path: '/', - sameSite: 'lax', - secure: true, - httpOnly: true - }, - ({ event, resolve }) => { - // event.locals is populated with the session `event.locals.session` - // Do anything you want here - return resolve(event); - } -); -``` - -In case you're using [sequence()](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks-sequence), do this - -```ts -const sessionHandler = handleSession({ - adapter: { - name: 'cookie', - options: { - chunk: true - } - } -}); - -export const handle = sequence(sessionHandler, ({ resolve, event }) => { - // event.locals is populated with the session `event.locals.session` - // event.locals is also populated with all parsed cookies by handleSession, it would cause overhead to parse them again - `event.locals.cookies`. - // Do anything you want here - return resolve(event); -}); -``` - -### Typescript - -Here's a simple example, modify `app.d.ts`: - -```ts -import type { FlashSessionData, SessionData, SessionStorage } from '@svelte-dev/session'; - -declare global { - namespace App { - // interface Error {} - interface Locals { - session: SessionStorage<{ views: number }>; - } - interface PageData { - session: FlashSessionData; - } - interface Session extends SessionStorage {} - // interface Platform {} - } -} -``` - -### Create your own stragety - -```ts -import type { RequestEvent } from '@sveltejs/kit'; -import type { - CookieOptions, - FlashSessionData, - SessionData, - SessionOptions, - SessionStorageStrategy -} from '@svelte-dev/session'; - -export type YourStrageOptions = { - /** - * Example - */ - key?: string; -}; - -export class YourStrategy - implements SessionStorageStrategy -{ - constructor( - event: RequestEvent, - options: YourStrageOptions & { cookie: CookieOptions; session: SessionOptions } - ) {} - /** - * Creates a new record with the given data and returns the session id. - */ - async createData(data, expires?: Date): Promise {} - - /** - * Returns data for a given session id, or `null` if there isn't any. - */ - async readData(id: string): Promise | null> {} - - /** - * Updates data for the given session id. - */ - async updateData( - id: string, - data: FlashSessionData, - expires?: Date - ): Promise {} - - /** - * Deletes data for a given session id from the data store. - */ - async deleteData(id: string): Promise {} -} -``` +[中文文档](https://svelte.js.cool/i18n) | [English Docs](https://svelte.js.cool/en/i18n) ## 赞助 Sponsor