Coder Social home page Coder Social logo

ciscoheat / sveltekit-flash-message Goto Github PK

View Code? Open in Web Editor NEW
228.0 6.0 5.0 442 KB

Send temporary data after redirect, usually from endpoints. Works with both SSR and client.

Home Page: https://www.npmjs.com/package/sveltekit-flash-message

License: MIT License

TypeScript 71.31% JavaScript 2.38% HTML 0.62% Svelte 21.56% SCSS 4.12%
cookies flash-messages session svelte sveltekit

sveltekit-flash-message's Introduction

sveltekit-flash-message ⚡

This Sveltekit library passes temporary data to the next request, usually in form actions and endpoints. It's useful for displaying a success or failure message after a POST, which should not always be displayed at the form, rather as a message on the page that the request was redirected to.

Since it's a temporary message it's also known as a "flash message", especially known from PHP apps, since it's easy to add this functionality with PHP's built-in session handling. With SvelteKit it's a bit harder, but this library was made to alleviate that, encouraging well-behaved web apps that Redirects after Post.

Installation

pnpm i -D sveltekit-flash-message
npm i -D sveltekit-flash-message

How to use

1. Add the flash message to app.d.ts (Typescript only)

In src/app.d.ts, add the type for the flash message to App.PageData as an optional property called flash. It can be as simple as a string, or something more advanced. It has to be serializable though, so only JSON-friendly data structures. For example:

src/app.d.ts

// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
  namespace App {
    // interface Error {}
    // interface Locals {}
    interface PageData {
      flash?: { type: 'success' | 'error'; message: string };
    }
    // interface PageState {}
    // interface Platform {}
  }
}

export {};

2. Wrap the load function of a top-level +layout or +page route

If you're not using any load functions, this is a simple step. Create a src/routes/+layout.server.ts file with the following content:

src/routes/+layout.server.ts

export { load } from 'sveltekit-flash-message/server';

But most likely you already have a top-level load function, in which case you can import loadFlash and wrap your load function with it:

src/routes/+layout.server.ts

import { loadFlash } from 'sveltekit-flash-message/server';

export const load = loadFlash(async (event) => {
  const data = { someOther: 'data' };
  return data;
});

Note: There cannot be any additional loadFlash calls in routes below, as the flash cookie is deleted when it is found the first time.

3. Display the flash message

Import getFlash in a layout or page component to display the flash message. getFlash will return a store that you'll use to access the message:

src/routes/+layout.svelte

<script lang="ts">
  import { getFlash } from 'sveltekit-flash-message';
  import { page } from '$app/stores';

  const flash = getFlash(page);
</script>

{#if $flash}
  {@const bg = $flash.type == 'success' ? '#3D9970' : '#FF4136'}
  <div style:background-color={bg} class="flash">{$flash.message}</div>
{/if}

4. Send flash messages

Server-side

To send a flash message from the server, import redirect from sveltekit-flash-message/server and use it in load functions and form actions.

Note: With SvelteKit 2, you don't need to throw the redirect, just call redirect. If you're still on SvelteKit 1, throw the function call.

import { redirect } from 'sveltekit-flash-message/server'

// The most specific: Redirect with a specific HTTP status to a specific location.
redirect(
  status: number,
  location: string,
  message: App.PageData['flash'],
  event: RequestEvent | Cookies
)

// Makes a 303 redirect to a specific location.
redirect(
  location: string,
  message: App.PageData['flash'],
  event: RequestEvent | Cookies
)

// Makes a 303 redirect to the current location.
redirect(
  message: App.PageData['flash'],
  event: RequestEvent
)

// For compatibility, the sveltekit signature can also be used, which will send no flash message.
redirect(
  status: number,
  location: string,
)

Form action example

src/routes/todos/+page.server.ts

import { redirect } from 'sveltekit-flash-message/server';

export const actions = {
  default: async ({ request, locals, cookies }) => {
    const form = await request.formData();

    await api('POST', `/todos/${locals.userid}`, {
      text: form.get('text')
    });

    redirect('/', { type: 'success', message: "That's the entrepreneur spirit!" }, cookies);
  }
};

Endpoint example

src/routes/todos/+server.ts

import type { RequestEvent } from '@sveltejs/kit';
import { redirect } from 'sveltekit-flash-message/server';

export const POST = async ({ cookies }) => {
  redirect('/', { type: 'success', message: 'Endpoint POST successful!' }, cookies);
};

Setting without redirecting

If you want to display a flash message without redirecting, as an error message when validation fails for example, you can use the setFlash function:

import { fail } from '@sveltejs/kit';
import { setFlash } from 'sveltekit-flash-message/server';

export const actions = {
  default: async ({ request, cookies }) => {
    const form = await request.formData();

    if (!form.get('text')) {
      setFlash({ type: 'error', message: 'Please enter text.' }, cookies);
      return fail(400);
    }
  }
};

Client-side

If you want to update the flash message on the client, use getFlash in any component:

src/routes/some-route/+page.svelte

<script>
  import { getFlash } from 'sveltekit-flash-message';
  import { page } from '$app/stores';

  const flash = getFlash(page);

  function showMessage() {
    $flash = { type: 'success', message: 'Updated from other component!' };
  }
</script>

<button on:click={showMessage}>Show flash message</button>

This will of course not set a cookie for the next request, it'll only update the flash message on the client.

Client-side fetching and redirecting

The flash message will update automatically on redirect or navigation, but when using fetch, you must call updateFlash afterwards:

<script lang="ts">
  import { updateFlash } from 'sveltekit-flash-message';
  import { page } from '$app/stores';

  async function submitForm(e: Event) {
    const form = e.target as HTMLFormElement;
    const body = new FormData(e.target as HTMLFormElement);

    await fetch(form.action, { method: 'POST', body });
    await updateFlash(page);
  }
</script>

<form method="POST" action="/test" on:submit|preventDefault={submitForm}>
  <input type="text" name="test" value="TEST" />
  <button>Submit with fetch</button>
</form>

updateFlash can take a second parameter, which is used to run a function before updating, so navigation events will pass through before showing the flash message. This is useful when you want to redirect based on the fetch response:

async function submitForm(e: Event) {
  const response = await fetch(new URL('/logout', $page.url), { method: 'POST' });
  if (response.redirected) {
    await updateFlash(page, () => goto(response.url, { invalidateAll: true }));
  }
}

Toast messages, event-style

A common use case for flash messages is to show a toast notification, but a toast is more like an event than data that should be displayed on the page, as we've done previously. But you can use the flash store as an event handler with a reactive statement:

src/routes/+layout.svelte

import { getFlash } from 'sveltekit-flash-message';
import { page } from '$app/stores';
import toast, { Toaster } from 'svelte-french-toast';

const flash = getFlash(page);

$: if ($flash) {
  toast($flash.message, {
    icon: $flash.type == 'success' ? '✅' : '❌'
  });

  // Clear the flash message to avoid double-toasting.
  $flash = undefined;
}

Flash message options

When calling getFlash, you can specify options, which will be inherited for the current route and the ones below.

const flash = getFlash(page, {
  clearOnNavigate: true,
  clearAfterMs: undefined,
  clearArray: false,
  flashCookieOptions: CookieSerializeOptions
});

You can also use initFlash, if you don't display a flash message in a certain layout but still want to set options for the routes below:

import { initFlash } from 'sveltekit-flash-message';
import { page } from '$app/stores';

initFlash(page, {
  clearAfterMs: 10000
});

clearOnNavigate

If true (the default), the flash message will be removed when navigating to a different route.

clearAfterMs

Can be set to a number of milliseconds before the flash message is automatically set to undefined.

clearArray

If you specify App.PageData['flash'] as an array, the library will concatenate messages into the array instead of replacing them. But if you always want to clear the previous messages for arrays, set the clearArray option to true. If your flash message isn't an array, this option will have no effect.

flashCookieOptions

You can change the options for the cookie being sent, like this on the server:

import { loadFlash, flashCookieOptions } from 'sveltekit-flash-message/server';

flashCookieOptions.sameSite = 'lax';

export const load = loadFlash(async (event) => {
  // ...load function...
});

And correspondingly, on the client (in a top-level component):

import { initFlash } from 'sveltekit-flash-message';

initFlash(page, {
  flashCookieOptions: { sameSite: 'lax' }
});

All options can be found in the cookie npm package. Default options for the flash cookie are:

{
  path: '/',
  maxAge: 120,
  sameSite: 'strict',
  httpOnly: false // Setting this to true will most likely break things client-side.
}

The name of the cookie, flash, cannot be changed currently, let me know if that's inconvenient. ⚡

Securing the flash message

Since the flash message is transferred in a cookie, it can be easily tampered with, so don't trust its content. Treat it like you do with any user data - hanging from a ten-foot pole over a fiery pit. 🔥 So never use {@html} to display it, and if you need to persist it for some reason, make sure you validate it.

Together with Superforms

The sister library to sveltekit-flash-message is Superforms, the all-in-one solution for forms in SvelteKit. You can use them together without any extra work, but there are options for closer integration, found here on the Superforms website.

Notes

When setting cookies in a response

If you're using +hooks.server.ts/js, or anywhere you have access to response, calling response.headers.set('set-cookie', ...) will discard the flash message cookie. You must use response.headers.append instead.

Redirecting in the load function

In SvelteKit, links are preloaded on hover for increased responsiveness of the app. This can have the side-effect of accidentally setting a flash cookie, if a flash message redirect is made in a load function, and the user hovers over a link leading to it, so it is preloaded. To prevent this, set the data-sveltekit-preload-data="tap" attribute on links where a redirect could happen in the load function.

Migration guides

From 0.x to 1.x

The only thing you need to do when upgrading to 1.x is to remove all calls to updateFlash in use:enhance.

 <form
    method="POST"
-   use:enhance={() =>
-     ({ update }) =>
-       updateFlash(page, update)}
+   use:enhance
 >

From 1.x to 2.x

  1. Rename functions:
  • initFlash is deprecated, getFlash can now be used directly instead.
  • loadFlashMessage is deprecated and renamed to loadFlash.
  1. If you've added the beforeNavigate snippet that clears the flash message after navigation - it can now be removed since it's automatic (though it can be prevented by setting the clearOnNavigate option to false).

  2. If you're using the snippet for clearing the message after a certain amount of time, you can remove it and use the clearAfterMs option instead.

Feedback and issues

Please open a github issue for suggestions, if you find a bug or have feedback in general!

sveltekit-flash-message's People

Contributors

ciscoheat avatar stlmpp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

sveltekit-flash-message's Issues

Flash not showing when redirecting from +layout.server.ts

I'm having trouble getting flash messages to show after a redirect.

  1. I've set up my flash messages and when I manually provide a flash object in +layout.svelte I can see it show up.
  2. However, when I trigger the redirect in +layout.server.ts, which provides a flash message, the redirect happens fine but the flash message doesn't show up on the page.

Any help much appreciated.

+layout.server.ts

import type { LayoutServerLoad } from './$types';
import { loadFlashMessage } from 'sveltekit-flash-message/server';
import { get } from 'svelte/store';
import { currentUser } from '$lib/stores/currentUser';
import { redirect } from 'sveltekit-flash-message/server';

export const load = loadFlashMessage(async (event) => {
    const user = get(currentUser);

    if (!user && event.request.url.match('/new')) {
        const message = { type: 'error', message: 'You need to log in to create lists' } as const;
        throw redirect(303, '/', message, event);
    }
}) satisfies LayoutServerLoad;

Identifier 'load' has already been declared

I am trying to setup the library in my project. I have added it into my +layout.server.js file like this:

export { loadFlash  } from 'sveltekit-flash-message/server';
export const load = loadFlash(async ({ locals,fetch}) => {
...

However i get the following error:

Internal server error: Parse failure: Identifier 'load' has already been declared (15:13)
At file: /src/routes/+layout.server.js
Contents of line 15: export const load = loadFlash(async (

I also have a +layout.js file with a load function. could it be because of that?

i am using v2.2.1 and "@sveltejs/kit": "^1.26.0", houdini graphql lib, superforms

External callbacks not redirecting correctly

I have a callback from google authentication using Lucia.

the callback calls the route:

/auth/callback/google/server.ts

I have simplified the endpoint to the bare minimum:

import type { RequestHandler } from './$types';
import { redirect } from 'sveltekit-flash-message/server';

export const GET: RequestHandler = async (event) => {
	throw redirect(
		303,
		'/',
		{ type: 'success', message: 'Welcome' },
		event
	);
};

so google will respond with something like this:

http://localhost:5173/auth/callback/google?state=r5w7scpscva31xlqhm62c3c4f8x0urvw4ok&code=4%2F0AZEOvhVPlgoztB1YfZrhbAZrA8Ye-TxU-utPWUSeBymRLQOQXCzVGQS72EQ4gQ&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid&authuser=1&prompt=consent

When Google executes the callback The page gets forwarded to / but I do not get the flash message.

If i just copy the google link and paste it in the browser, then I get redirected to / and the flash message works.

Is there an issue with CORS or am I missing something?

Ben

Problems when subscribing to flash store and hovering over <a> tag where load function uses redirect.

Issue Description

I am currently using sveltekit-flash-message to show a toast after redirecting from a protected page to a public page. To achieve this, I followed the example in the readme by subscribing to the flash store, which triggers my toast each time the redirect happens. However, I encountered an issue where the toast sometimes triggers even when I redirect to a page where the flash-message redirect is not used. I have checked for the undefined case, but the problem persists.

Investigation

After some investigation, I found a way to reproduce the error. The issue occurs when I hover over the protected button, where the flash-message redirect occurs on load. Then, if I click another link, the flash-message gets set again, and the toast re-triggers.

Reproduction Steps

To reproduce this issue, follow these steps:

  1. Go to the public page.
  2. Hover over the protected button (where the flash-message redirect happens on load).
  3. Go to the homepage.

Then the toast is unnecessarily shown.

Try it yourself

Go and run my example, just pnpm i and pnpm dev to run it
https://github.com/hansaskov/sveltekit-flash-messages-toasts-error

Flash message not appearing when using layout groups.

My setup:

// src/routes/(auth)/+layout.server.ts
export { load } from 'sveltekit-flash-message/server'

In my form action I call redirect with a message

// src/routes/(auth)/register/+page.server.ts
export const actions: Actions = {
  default: async (event) => {
    // ...
   const message = {
      type: 'success',
      message:
        'Please check your email and click the verification link to complete registraion. If you do not see an email, please check the spam folder.'
    } as const
    throw redirect('/login', message, event)
  }
}

In the login page $flash is undefined

// src/routes/(auth)/login/+page.svelte
<script lang="ts">
  import { getFlash } from 'sveltekit-flash-message'
  import { page } from '$app/stores'

  const flash = getFlash(page)
  console.log($flash) // undefined
</script>

How to make it work with sveltekit-superforms ?

Hi, thank you for this lib !

I want to make it work with sveltekit-superforms, how should I use it ?
If I follow the docs, I need to use throw a redirect like :
throw redirect(303, '/', message, event)
but in the sveltekit-superforms, I need to return the form like :
return { form }.

Thanks for your help !

Could not show flash message when redirecting.

I am checking if a user is logged in and if true then I am redirecting to the home page but the flash messages wont show
+page.server.ts

export const load: PageServerLoad = async ({ request, locals, cookies }) => {
    if (locals.user && locals.session) {
        // setFlash({ type: 'success', message: `Welcome ${locals.user.name} !` }, cookies) or
        return redirect(303, "/", { type: 'error', message: 'You are already logged in.' }, cookies)
    }
    const form = await superValidate(request, zod(loginSchema))
    return { form }
}

+layout.svelte

import { getFlash } from 'sveltekit-flash-message';
import { page } from '$app/stores';
import { toast } from 'svelte-sonner';

const flash = getFlash(page);

$: if ($flash)  {
    console.log('We have flash');
    if ($flash.type === 'error') {
        toast.error($flash?.message, {
		action: {
			label: 'X',
			onClick: () => toast.dismiss()
		},
		duration: 3000
	});
}
if ($flash.type === 'success') {
	toast.success($flash.message, {
		action: {
			label: 'X',
			onClick: () => toast.dismiss()
		},
		duration: 3000
	});
    }
}

Flash message not appearing when expected

I'm struggling to get the following process to work with flash messages

  1. User is on the /settings/security route and changes their password
  2. On success the user is redirected back to the login screen with the following code
    throw redirect('/login', { type: 'success', message: 'Successfully changed password, please enter new password to login'}, event);
  3. When the user gets to the login screen a flash message appears

The issue is the flash message never appears.
However it does appear once the user has logged in and gone from '/login' to '/'

Any idea why this might be?

Here is my layout code

<script lang="ts">
	import '../theme.postcss';
	import '@skeletonlabs/skeleton/styles/skeleton.css';
	import '../app.postcss';
	import {
		Toast,
		toastStore,
		type ToastSettings,
	} from '@skeletonlabs/skeleton';
	import { page } from '$app/stores';
	import type { LayoutData } from './$types';
	import { initFlash } from 'sveltekit-flash-message/client';
	export let data: LayoutData;

	$: onLoginPage = $page.url.pathname === '/login';

	const flash = initFlash(page);
	function displayFlashToast() {
		if ($flash) {
			if ($flash.type == 'success') {
				const t: ToastSettings = {
					message: `<p>${$flash.message}</p>`,
					autohide: true,
					timeout: 5000,
					background: 'variant-ghost-success'
				};
				toastStore.trigger(t);
			} else {
				const t: ToastSettings = {
					message: `<p>${$flash.message}</p>`,
					autohide: true,
					timeout: 5000,
					background: 'variant-ghost-error'
				};
				toastStore.trigger(t);
			}
		}
	}

	flash.subscribe(($flash) => {
		if ($flash) displayFlashToast();
	});
</script>

{#if onLoginPage}
	<slot />
{:else}
	<!-- My menu layout -->
{/if}

Idea: enhance sveltekit redirect

Was looking around for prior art on doing flash messages in sveltekit and found this project. I was thinking it might be good to stick to the sveltekit style throw redirect(...) and maybe (not sure your exact implementation so maybe not possible) you could wrap the sveltekit redirect and add your functionality there so then i can do something like this.

import { redirect }  "sveltekit-flash-message/server"`

throw redirect(302, "/dashboard", {type: "success", message: "It worked"})

Suggestion: improve `getFlash` options

I am using this library with sveltekit-superforms and without svelte-french-toast.

I'd like to be able to say from the server of my app if the notification is a success, a warning or an error. In case of a success and warning, I'd like the toast to disappear after 6 and 8 seconds while I don't want the errors to disappear by themselves.

In other words, I'd like to configure options based on the flash message.

This could me achieved with options being a configuration object or a function with the following signature (flash: App.PageData['flash']): Partial<FlashOptions>.

Persistant error after HTTP 500 status: `getFlash options can only be set once, at a top-level component`

Situation: I use getFlash options and my website contains a page with an error which results in a HTTP 500 status. After I visit this page resulting in my own 500 I'll also see this error getFlash options can only be set once, at a top-level component. After that, when I try to visit any normal page on my website, I will still get this error and a 500.

It seems this issue is also described in #30 (especially this comment). I have been able to reproduce it using a default SvelteKit project on StackBlitz:

  • Visit: https://stackblitz.com/edit/sveltejs-kit-template-default-f28ajv (wait for the homepage on the right to load)
  • In the address bar above the websitepanel on the right add /testpage en press enter to navigate to this page
    • You'll see '500 | Internal Error' (this is correct, because I'm throwing a 500 on that page)
    • Also notice this error in the console below: `[Error: getFlash options can only be set once, at a top-level component.]
  • In the address bar, remove /testpage again and press enter to navigate back to the homepage
    • You'll still see '500 | Internal Error', instead of the homepage
    • And again this error in the console below: `[Error: getFlash options can only be set once, at a top-level component.]

Changes I've made to this default SvelteKit project:

  • Added to package.json:
  "dependencies": {
    "sveltekit-flash-message": "2.3.0"
  },
  • Added to src/routes/+layout.svelte:
  import {page} from '$app/stores';
  import {getFlash} from 'sveltekit-flash-message';
  const flash = getFlash(page, {clearOnNavigate: false});
  • Created src/routes/testpage/+page.svelte containing:
<script>
  import {error} from '@sveltejs/kit';
  throw error(500);
</script>

Feature Request: More properties to work better with svelte-sonner

I love this library especially with forms. Thanks
I have recently started using svelte-sonner and it would be really cool if we could send down the properties of description and action. and add the enums for type ie: warning, info, promise, etc..
I mean it already works, but I get type errors, so i guess you just need to add more types

if (!session) {
throw redirect(
        302,
        '/auth/login',
        {
            // @ts-ignore
            type: 'warning',
            message: 'Not Authorized',
            description: 'You need to be logged in to Create event',
            action: {
                label: 'Back',
                onClick: () => goto(url.searchParams.get('from') ?? '')
            }
    },
     cookies
    )
}
const flash = getFlash(page) as any

$: if ($flash) {
    toast[$flash.type]($flash.message, {
        description: $flash.description,
        action: $flash.action
    })
}

EDIT: Actually i think this would make it work better with svelte french toast as well

compatibility with sveltekit 1.0.0-next.505

Getting the following error since I upgraded sveltekit again

Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies
Error: Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies

setFlash not working

Quick explainer:
a have a +page.svelte @ '/' from there I POST to '/other' where form actions handle the request and call setFlash. But +page.svelte @ '/other' does not get the flash message.

/ --POST--> /other

here's a repord repo:
https://github.com/philippviereck/flash-message-reprod

I didn't dig deep, but if here event.cookies would be used instead of the header, it works. 🤷‍♂️

  const header = event.request.headers.get('cookie') || '';
  if (!header.includes(cookieName + '=')) {
    //d('No flash cookie found.');
    return { [cookieName]: undefined };
  }

  const cookies = parse(header) as Record<string, string>;
  const dataString = cookies[cookieName];

endless re-rendering for beforeUpdate

If I add the beforeUpdate hook in the layout file

  beforeUpdate(() => updateFlashMessage(session))

I get constant re-rendering once the server sets the cookie.

I am using

		"@sveltejs/kit": "1.0.0-next.369",

Cannot read properties of undefined (reading 'SSR')

Cannot read properties of undefined (reading 'SSR')
TypeError: Cannot read properties of undefined (reading 'SSR')
    at file:///Users/user/git/app/node_modules/sveltekit-flash-message/client.js:7:34
    at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async nodeImport (file:///Users/user/git/app/node_modules/vite/dist/node/chunks/dep-3ffe6dd0.js:53401:21)
    at async eval (/src/routes/pages/[uid]/+page.svelte:23:31)
    at async instantiateModule (file:///Users/user/git/app/node_modules/vite/dist/node/chunks/dep-3ffe6dd0.js:53330:9)

Have the following versions:

		"@sveltejs/adapter-node": "1.0.0-next.88",
		"sveltekit-flash-message": "^0.6.2",
		"vite": "^3.1.0-beta.1",
		"@sveltejs/kit": "1.0.0-next.457",

Timeout and clear message

Hey thanks for this library. It's working great. Is there a recommended way to handle automatically clearing a message after a certain amount of time?

Error when using vitest 0.34.1 - Failed to resolve entry for package "sveltekit-flash-message".

When upgrading vitest from 0.33.0 to 0.34.1 I get the following error when running vitest:

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Error: Failed to resolve entry for package "sveltekit-flash-message". The package may have incorrect main/module/exports specified in its package.json: Missing "." specifier in "sveltekit-flash-message" package

If I remove sveltekit-flash-message from the project, vitest runs just fine.

Also if I leave the sveltekit-flash-message and go back to 0.33.0, vitest runs fine.

You could argue its a vitest problem, but the message is specific for sveltekit-flash-message and removing it solves the problem.

flash does not trigger.

The sample code in the documentation does not work. Toast it not trigger

       const flash = getFlash(page);
       $: if ($flash) {
		toastStore.trigger({
			message: $flash.message,
			background: $flash.type == 'success' ? 'variant-filled-success' : 'variant-filled-error'
		});

		$flash = undefined;
	}

This code works but have a double-toasting

      	const flash = getFlash(page);
	flash.subscribe((flash) => {
		if (flash) {
			toastStore.trigger({
				message: flash.message,
				background: flash.type == 'success' ? 'variant-filled-success' : 'variant-filled-error'
			});
		}
	});

Any idea how to resolve this issue?

Persistent Flash store data after server redirect

After getting the message data from a server redirect, that data persist in the store after subsequent page navigation.

I’ve encountered an issue where data from a server redirect message persists in the store even after navigating to subsequent pages.

To illustrate the problem, I’ve replicated my project structure and the issue on this SvelteLab page: https://www.sveltelab.dev/a0qnffsh6ntioi9?files=.%2Fsrc%2Froutes%2Fdashboard%2FtestPage%2F%2Bpage.svelte

In my /auth route, I’ve set up a flash redirect to simulate a scenario where a user is redirected to the dashboard if they are already logged in. However, after this redirect occurs, the alert message continues to display when navigating to the home page and then back to the dashboard.

Usage with Skeleton `Toast` component

In the readme you explain why using the {@html} syntax when displaying messages from this library is a bad idea but skeleton seems to make heavy use of it, including in the view layer for its global toast system.

Is there a recommended way to make sveltekit-flash-message and skeleton's Toast work together nicely in a superforms app or should I just pick one and stick with it?
If I do have to ditch one or the other here are the ideas I was thinking of:

  • Wrap skeleton's toastStore in an interface that conforms to superforms' flashMessage.module interface, assuming superforms itself doesn't do anything that would be risky with skeleton's use of {@html} when calling the flash message implementation.
  • Ditch skeleton's toast system completely, use this library and just conditionally display an element like in the readme but style it like a skeleton toast.

In both approaches I have to learn how something works (a flashMessage.module conformant implementation vs the styling in skeleton's Toast) so neither one is ideal.

Flash message conflicts with hooks.server.js

I am building a webapp using pocketbase as a login database but i am having problems passing the flash message after an authentication request. After some tests the problem looks related to the hooks.server.js file: if i remove it the message is displayed correctly otherwise the flash message does not appear. These are my current files:

hooks.server.js

import { pool } from '$lib/server/db/config.js';
import PocketBase from 'pocketbase';

export const handle = async ({event, resolve}) => {
    event.locals.pool = pool;
    event.locals.pb = new PocketBase("http://127.0.0.1:8090");
    event.locals.pb.authStore.loadFromCookie(event.request.headers.get('cookie') || '');
    
    if (event.locals.pb.authStore.isValid) {
        event.locals.user = structuredClone(event.locals.pb.authStore.model)
    } else {
        event.locals.user = undefined;
    }

    const response = await resolve(event);

    response.headers.set('set-cookie', event.locals.pb.authStore.exportToCookie({secure: false}));
    return response;
}

+layout.server.js

import { loadFlashMessage } from 'sveltekit-flash-message/server';

export const load = loadFlashMessage(async ({locals}) => {

    if (locals.user) {
        return {
            user: locals.user
        }
    } else {
        return {
            user: undefined
        }
    }
})

/auth/+page.server.js (the authentication endpoint)

import { redirect } from 'sveltekit-flash-message/server'

export const actions = {
    login: async (event) => {

        const origin_url = event.request.headers.get('referer');

        const login_body = Object.fromEntries(await event.request.formData());

        try {

            await event.locals.pb.collection('users').authWithPassword(login_body.email, login_body.password);

        } catch(e) {
            console.log(e.data);
        }

        const message = { type: 'success', message: 'Endpoint POST successful!' };

        throw redirect(303, origin_url, message, event)
    }}

+layout.svelte

<script>
    import { initFlash } from 'sveltekit-flash-message/client';
    import { page } from '$app/stores';
  
    const flash = initFlash(page);
</script>

{#if $flash}
    <div>{$flash.message}</div>
{/if}

What can i do to have both the hooks file (required for the authentication process) and show a flash message afterwards?

Issues when redirecting to root route

Hey :)

So I've recently decided to make my SvelteKit projects just a bit less dependent on JavaScript (I do understand the irony, yet isn't that just a beautiful thing to be able to say? 😎 )
sveltekit-flash-message quickly became a crucial addition to my toolbox as I'm trying to create equivalently great interfaces for both JS-enabled/disabled environments, and I believe this is a great opportunity to thank @ciscoheat once again for his amazing work on both sveltekit-flash-message and sveltekit-superforms. these are absolutely fantastic solutions for meaningful problems!

I did find a possible issue, though:
When redirecting to the root route using the custom redirect helper, and client-side isn't directly involved (Implementing a logout form action, for example) - flash messages aren't displayed. refreshing the page makes the message appear (I suspect the load function gets retriggered, hence the update). redirecting to any other route works as expected.
The same behavior was noticed when trying to redirect to root after form submissions with actual data validation (login, for example), yet that was fixed by setting flashModule as the module on the client side (in superForm options).
i also tried to hack a workaround by redirecting to the same URL and triggering the load function which redirects again to the root route with the flash message using invalidateAll. still, nothing.

Any ideas? am I missing something here?

`redirect` with two parameters does not work with type `Cookies`

The definition of the redirect with two parameters is as follows :

export function redirect(
message: App.PageData['flash'],
event: RequestEvent | Cookies
): ReturnType<typeof redir>;

But the implementation only takes in a RequestEvent (as RequestEvent, line 148) :

case 2:
if (typeof status === 'number') {
return realRedirect(status as RedirectStatus, `${location}`);
} else {
const message = status as App.PageData['flash'];
const event = location as RequestEvent;
// Remove the named action, if it exists
const redirectUrl = new URL(event.url);

While I understand that if you don't have the url, you can't properly redirect, this means that using the Cookies type (from event.cookies of course) does not work in the form redirect({hello: 'world'}, event.cookies).


The potential solution would simply to remove the | Cookies from the definition of the 2 args redirect function's arguments type, otherwise I'm not too sure where you'll take the current location from x)

Error: getFlash options can only be set at the first call to getFlash.

I'm using the getFlash component in clientside(root +layout.svelte) as such:

        import { getFlash } from 'sveltekit-flash-message/client';

	const flash = getFlash(page, {
		clearOnNavigate: true
	});

	$: if ($flash) {
		switch ($flash.type) {
			case 'success':
				toast.success($flash.message);
				break;
			case 'error':
				toast.error($flash.message);
				break;
			default:
				toast.info($flash.message);
		}
	}

The code generally works for the most part, but when I make changes to seemingly unrelated +page.svelte or deeper components during dev, this error pops up. I'm not sure if this is a user error, but the error itself is frequent enough to be disruptive(cannot proceed without dev server restart).

Flash Messages Not Working

i tried to implement the flash message code exactly as it is on the documentation it's not working and when i opened the dev console this message showed up:

Screenshot from 2023-07-30 21-02-18

No redirect option to another page within form action?

This example doesn't allow for a redirect to another location:

https://github.com/ciscoheat/sveltekit-flash-message?tab=readme-ov-file#form-action-example

Form action example

src/routes/todos/+page.server.ts

import { redirect } from 'sveltekit-flash-message/server';

export const actions = {
  default: async ({ request, locals, cookies }) => {
    const form = await request.formData();

    await api('POST', `/todos/${locals.userid}`, {
      text: form.get('text')
    });

    const message = { type: 'success', message: "That's the entrepreneur spirit!" } as const;
    throw redirect(message, cookies);
  }
};

How do I redirect to another page using this library, within a formaction?

No messages without refresh

I am not getting the flash messages without having to refresh the page.

My setup was simple: npm init svelte@latest web then npm i sveltekit-flash-message.
Then I followed the guide and made a "proected" route where it emmidiately redirects back on load (from the server)
After it redirects it only shows the message after you refresh the page. If you navigate to /protected manually by typing it into the browser and pressing enter, then it shows the message.

Made a minimal project to show the problem
https://github.com/pjarnfelt/sveltekit-flash-message-reload

[SOLVED] Overwriting 'set-cookie' header via server hook prevents flash message

TL;DR:
Caution to users using the +hooks.server.ts Handle function!

Notice that response.headers.set('set-cookie', ... discards the flash message cookie.
It is imperative that instead response.headers.append('set-cookie', ... must be used.
Multiple set-cookie headers are supported.

I propose adding a documentation specifically warning adventurous users from overwriting the cookie.


I have had a bit of trouble getting flash messages to work.
In the +layout.server.ts ServerLoad I print the cookies before the flash wrapper is executed:

import type { ServerLoad } from '@sveltejs/kit';

import { loadFlash } from 'sveltekit-flash-message/server';
import type { LayoutServerData } from './$types';

export const load: ServerLoad = (event) => {
	console.log('ServerLoad cookies', event.request.headers.get('cookie'));
	return loadFlash(async ({ locals }) => {
		return {
			user: locals.user,
			categories: locals.categories,
		} satisfies LayoutServerData;
	})(event);
};

In the +layout.svelte I render the flash

<script lang="ts">
import { showFlash } from '$lib/Toast/toast.client';

showFlash(page);
</script>

Show flash reactively prints client cookies

import type { Page } from '@sveltejs/kit';
import type { Readable } from 'svelte/motion';
import { getFlash } from 'sveltekit-flash-message/client';
import { toast } from './toast';

export function showFlash(page: Readable<Page<Record<string, string>, string | null>>) {
	page.subscribe((page) => {
		console.log('page: ', page.data.flash);
	});
	const flash = getFlash(page);
	flash.subscribe((msg) => {
		console.log('getFlash: ', msg);
		if (msg) {
			toast.show(msg);
		}
		flash.set(undefined);
	});
}

In my login form `+page.server.ts I show a toast upon failed login

import type { Actions } from './$types';
import { loginSchema, sanitizeLoginForm } from './schema';
import { ZodError } from 'zod';
import { createRedirectTo, ensureRootPath } from '$lib/url';
import type { ToastMessage } from '$lib/Toast/toast';
import { redirect, setFlash } from 'sveltekit-flash-message/server';

export const actions: Actions = {
	default: async (event) => {
		const { locals, request, url } = event;
		const formData = Object.fromEntries(await request.formData());
	// redacted for brevety. Below the login failed
	
	setFlash(
		{
			type: 'surface',
			message: ['Lost your password?', { name: 'Reset it here', url: `/auth/reset-password?redirectTo=${createRedirectTo(url)}` }],
		} satisfies ToastMessage,
		event
	);
	console.log('setFlash: ', event.cookies.get('flash'));
	return {
		data: sanitizeLoginForm(formData),
		errors: {
			nameOrEmail: ['Invalid username or password'],
			password: ['Invalid username or password'],
		},
	};

Interacting with the form should yield the following debug output:

server: setFlash:  {"type":"surface","message":["Lost your password?",{"name":"Reset it here","url":"/auth/reset-password?redirectTo=/"}]}
server: ServerLoad cookies: flash=%7B%22type%22:%22surface%22,%22message%22:%5B%22Lost%20your%20password?%22,%7B%22name%22:%22Reset%20it%20here%22,%22url%22:%22/auth/reset-password?redirectTo=/%22%7D%5D%7D
client: page: {"type":"surface","message":["Lost your password?",{"name":"Reset it here","url":"/auth/reset-password?redirectTo=/"}]}
client: getFlash: {"type":"surface","message":["Lost your password?",{"name":"Reset it here","url":"/auth/reset-password?redirectTo=/"}]}

...but the server sided cookie is lost during the redirect, so the store is never updated, and the toast does not arrive at the client side.

server: setFlash:  {"type":"surface","message":["Lost your password?",{"name":"Reset it here","url":"/auth/reset-password?redirectTo=/"}]}
server: ServerLoad cookies: null
client: page: undefined

I traced this issue back to my bogus authentication implementation hook:

import { getCategories } from '$lib/server/cat';
import type { Handle, RequestEvent } from '@sveltejs/kit';
import PocketBase from 'pocketbase';

type HandleEvent = RequestEvent<Partial<Record<string, string>>, string | null>;

export const handle: Handle = async ({ event, resolve }) => {
	await Promise.all([addPocketBase(event), addProduct(event)]);
	// handle event
	const response = await resolve(event);

	// set authentication cookie
	response.headers.set('set-cookie', event.locals.pb.authStore.exportToCookie({ secure: false }));

	return response;
};

Notice that response.headers.set('set-cookie', ... discards the flash message cookie.
It is imperative that instead response.headers.append('set-cookie', ... must be used.
Multiple set-cookie headers are supported.

I propose adding a documentation specifically warning adventurous users from overwriting the cookie.

flash store undefined after redirect

Sorry if I'm missing something obvious, I'm having issues trying to retrieve flash messages after a redirect in a form action. I'm also using layout groups and found #23. Seems similar but might be unrelated.

Top level layout with a conditional redirect if no user exist:

// src/routes/(app)/+layout.server.ts

import type { LayoutServerLoad } from './$types';
import { loadFlash, redirect } from 'sveltekit-flash-message/server';

export const load: LayoutServerLoad = loadFlash(({ locals: { user } }) => {
	if (!user) {
		throw redirect(302, '/login');
	}
});

Route where I want to redirect with a flash message on success:

// src/routes/(app)/shop/categories/[slug]/+page.server.ts

import { redirect } from 'sveltekit-flash-message/server';

export const actions = {
	default: async (event) => {
                ... 

                // Using new slug after successful edit
                // I also tried including explicit status code in signature
		throw redirect(
			`/shop/categories/${newSlug}`,
			{ type: 'success', message: 'Edit success' },
			event
		);
	}
};

// src/routes/(app)/shop/categories/[slug]/+page.svelte

<script lang="ts">
	import { getFlash } from 'sveltekit-flash-message';
	import { page } from '$app/stores';

	export let data;

	const flash = getFlash(page);
	$: console.log($flash);
</script>

The redirect works fine, but $flash is always undefined. If the edit results in an error, I'm also trying setFlash and returning fail() as the docs suggests, with same results ($flash is undefined).

I'm using version 2.2.1, and I should also mention I'm using superforms, if that might be relevant.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.