UNPKG

sveltekit-flash-message

Version:

Send temporary data to the next request after redirect. Works with both SSR and client.

195 lines (194 loc) 7.16 kB
import { get as svelteGet, writable } from 'svelte/store'; import { tick } from 'svelte'; import { BROWSER as browser } from 'esm-env'; import { serialize } from './cookie-es-main/index.js'; import { navigating } from '$app/stores'; import { FlashMessage } from './flashMessage.js'; import { FlashRouter } from './router.js'; const cookieName = 'flash'; const routers = new WeakMap(); function get(page) { return 'subscribe' in page ? svelteGet(page) : page; } function getRouter(page, initialData) { let router = routers.get(page); if (!router) { router = new FlashRouter(); routers.set(page, router); router.getFlashMessage(get(page).route.id).message.set(initialData); subscribeToNavigation(page); } return router; } function subscribeToNavigation(page) { if (!browser) return; navigating.subscribe((nav) => { //console.log('Navigating:', nav); if (nav === null) { const cookieData = parseFlashCookie(); if (cookieData !== undefined) { //console.log('🚀 ~ afterNavigate:', cookieData, get(page).route.id); const flash = getRouter(page).getFlashMessage(get(page).route.id); flash.message.set(cookieData, { concatenateArray: !flash.options.clearArray }); clearFlashCookie(flash.options.flashCookieOptions); } } else { const navTo = nav?.to?.route.id; if (navTo) { const flash = getRouter(page).getFlashMessage(navTo); if (flash.options.clearOnNavigate && nav.from?.route.id != navTo) { //console.log('🚀 ~ beforeNavigate ~ clear message on nav to:', navTo); flash.message.set(undefined); } } } }); try { if ('subscribe' in page) throw '$app/stores used'; // Svelte 5 $effect(() => { // Track the whole object, like page.subscribe in Svelte 4 page.data; page.error; page.form; page.params; page.route; page.state; page.status; page.url; const cookieData = parseFlashCookie(); if (cookieData !== undefined) { //console.log('🚀 ~ page.subscribe:', cookieData, page.route.id); const flash = getRouter(page).getFlashMessage(page.route.id); flash.message.set(cookieData, { concatenateArray: !flash.options.clearArray }); clearFlashCookie(flash.options.flashCookieOptions); } }); } catch (e) { if (!('subscribe' in page)) { throw new Error('sveltekit-flash-message cannot use Page from $app/state in Svelte 4. Use $app/stores instead.'); } // Svelte 4 const p = page; p.subscribe(($page) => { const cookieData = parseFlashCookie(); if (cookieData !== undefined) { //console.log('🚀 ~ page.subscribe:', cookieData, $page.route.id); const flash = getRouter(page).getFlashMessage($page.route.id); flash.message.set(cookieData, { concatenateArray: !flash.options.clearArray }); clearFlashCookie(flash.options.flashCookieOptions); } }); } } export function initFlash(page, options) { return _initFlash(page, options).message; } // @DCI-context function _initFlash(page, options) { if (!browser) { // The SSR version uses a simple store with no options, // since they are used only on the client. return new FlashMessage(writable(get(page).data.flash)); } const _page = get(page); ///// Roles ////////////////////////////////////////////////////////////////// //#region Router ///// // eslint-disable-next-line dci-lint/literal-role-contracts const Router = getRouter(page, _page.data.flash); function Router_getFlashMessage() { const route = Router.routes.get(Page_route()); if (route) return route; return options ? Router_createRoute() : Router.getClosestRoute(Page_route()); } function Router_createRoute() { return Router.createRoute(Page_route(), Page_initialData(), options); } //#endregion //#region Page const Page = { store: page, route: _page.route.id, initialdata: _page.data.flash, navigating }; function Page_initialData() { return Page.initialdata; } function Page_route() { return Page.route ?? ''; } //#endregion return Router_getFlashMessage(); } /** * Retrieves the flash message store for display or modification. * @param page Page store, imported from `$app/state`. * @param {FlashOptions} options for the flash message. Can only be set once, usually at the highest level component where getFlash is called for the first time. * @returns The flash message store. */ export function getFlash(page, options) { return _initFlash(page, options).message; } /** * Update the flash message manually, usually after a fetch request. * @param page Page store, imported from `$app/state`. * @param {Promise<void>} update A callback which is executed *before* the message is updated, to delay the message until navigation events are completed, for example when using `goto`. * @returns {Promise<boolean>} `true` if a flash message existed, `false` if not. */ export async function updateFlash(page, update) { // Update before setting the new message, so navigation events can pass through first. if (update) await update(); const cookieData = parseFlashCookie(); if (cookieData !== undefined) { if (browser) await tick(); const flash = getRouter(page).getFlashMessage(get(page).route.id); flash.message.set(cookieData, { concatenateArray: !flash.options.clearArray }); clearFlashCookie(flash.options.flashCookieOptions); } return !!cookieData; } /////////////////////////////////////////////////////////// function clearFlashCookie(options) { // Clear parsed cookie if (browser) { document.cookie = serialize(cookieName, '', { ...options, maxAge: 0 }); } } function parseFlashCookie() { const cookieString = document.cookie; if (!cookieString || !cookieString.includes(cookieName + '=')) return undefined; function parseCookieString(str) { const output = {}; if (!str) return output; return str .split(';') .map((v) => v.split('=')) .reduce((acc, v) => { acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim()); return acc; }, output); } const cookies = parseCookieString(cookieString); if (cookies[cookieName]) { try { return JSON.parse(cookies[cookieName]); } catch (e) { // Ignore value if parsing failed } } return undefined; }