Skip to content

Login

🏷️ state data
🏷️ event payload
🏷️ conditional transitions
🏷️ delayed transition
🏷️ state entry side effect
🏷️ send event to self

A login machine example which uses event data for auth credentials, has different (heterogenous) state data for different states, and uses a state-onEnter() side-effect that sends a LOGOUT event to the machine instance, after a delay.

loginMachine.ts
import { defineMachine } from "yay-machine";
interface UnauthenticatedState {
readonly name: "unauthenticated";
}
interface AuthenticatedState {
readonly name: "authenticated";
readonly username: string;
readonly rememberMe: boolean;
}
interface InvalidCredentialsState {
readonly name: "invalidCredentials";
readonly errorMessage: string;
}
interface BannedState {
readonly name: "banned";
}
type LoginState =
| UnauthenticatedState
| AuthenticatedState
| InvalidCredentialsState
| BannedState;
interface LoginEvent {
readonly type: "LOGIN";
readonly username: string;
readonly password: string;
readonly rememberMe?: boolean;
}
interface LogoutEvent {
readonly type: "LOGOUT";
readonly fromSystem?: boolean;
}
/**
* A silly example mainly demonstrating the use of conditional transitions.
*/
export const loginMachine = defineMachine<LoginState, LoginEvent | LogoutEvent>(
{
initialState: { name: "unauthenticated" },
states: {
unauthenticated: {
on: {
LOGIN: [
{
to: "banned",
when: ({ event }) => event.username === "attacker",
},
{
to: "authenticated",
when: ({ event: { username, password } }) =>
username === "trustme" && password === "password123",
data: ({ event: { username, rememberMe } }) => ({
username,
rememberMe: !!rememberMe,
}),
},
{
to: "invalidCredentials",
when: ({ event: { username } }) => username === "trustme",
data: () => ({ errorMessage: "Incorrect password" }),
},
{
to: "invalidCredentials",
data: ({ event: { username } }) => ({
errorMessage: `Unknown username "${username}" or incorrect password`,
}),
},
],
},
},
authenticated: {
onEnter: ({ send }) => {
const timer = setTimeout(
() => send({ type: "LOGOUT", fromSystem: true }),
1000 * 60 * 5,
); // automatically log out after 5 minutes
return () => clearTimeout(timer);
},
on: {
LOGOUT: {
to: "unauthenticated",
when: ({ state: { rememberMe }, event: { fromSystem } }) =>
!fromSystem || !rememberMe,
},
},
},
},
},
);
import assert from "assert";
import { loginMachine } from "./loginMachine";
const login = loginMachine.newInstance().start();
login.send({ type: "LOGIN", username: "foo", password: "bar" });
assert.deepStrictEqual(login.state, {
name: "invalidCredentials",
errorMessage: 'Unknown username "foo" or incorrect password',
});