Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"ts-node": "^10.9.2",
"eslint": "^9.35.0",
"neostandard": "^0.13.0",
"typescript": "~5.9.2"
"typescript": "~6.0.2"
},
"dependencies": {
"fastify-plugin": "^5.0.0"
Expand Down
69 changes: 31 additions & 38 deletions src/flash.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,60 @@
import { format } from 'node:util'
import type { FastifyRequest, FastifyReply } from 'fastify'

type ReplyReturn =
| {
[k: string]: string[] | undefined
declare module '@fastify/secure-session' {
interface SessionData {
flash: { [k: string]: string[] }
}
| string[]
}

type FlashData = { [k: string]: string[] }
export type ReplyReturn = FlashData | string[]

export function flashFactory () {
return {
request (type: string, ...message: string[] | [string[]]): number {
request (this: FastifyRequest, type: string, ...message: string[] | [string[]]): number {
if (!this.session) {
throw new Error('Session not found')
}
let currentSession = this.session.get('flash')
if (!currentSession) {
currentSession = {}
this.session.set('flash', currentSession)
}

let currentSession = this.session.get('flash') || {}

if (message.length === 0) {
throw new Error('Provide a message to flash.')
}

if (Array.isArray(message[0])) {
for (let i = 0; i < message[0].length; i++) {
currentSession = {
...currentSession,
[type]: (currentSession[type] || []).concat(message[0][i]),
}
}
} else {
currentSession = {
...currentSession,
[type]: (currentSession[type] || []).concat(
message.length > 1 ? format.apply(undefined, message) : message[0]
),
}
const messagesToAdd = Array.isArray(message[0])
? message[0]
: [message.length > 1 ? format.apply(undefined, message) : (message[0] as string)]

currentSession = {
...currentSession,
[type]: (currentSession[type] || []).concat(messagesToAdd)
}

this.session.set('flash', currentSession)
return this.session.get('flash')[type].length
const updatedFlash = this.session.get('flash') || {}
return updatedFlash[type]?.length ?? 0
},

reply (type?: string): ReplyReturn {
reply (this: FastifyReply, type?: string): ReplyReturn {
if (!this.request.session) {
throw new Error('Session not found')
}

const session = this.request.session
const allMessages = session.get('flash') || {}

if (!type) {
const allMessages = this.request.session.get('flash')
this.request.session.set('flash', {})
session.set('flash', {})
return allMessages
}

let data = this.request.session.get('flash')
if (!data) {
data = {}
}

const messages = data[type]
delete data[type]

this.request.session.set('flash', data)
const messages = allMessages[type] || []
const { [type]: _, ...remaining } = allMessages

return messages || []
session.set('flash', remaining)
return messages
},
}
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fp from 'fastify-plugin'
import { flashFactory } from './flash'
import '@fastify/secure-session'

declare module 'fastify' {
export interface FastifyRequest {
Expand All @@ -10,7 +11,7 @@ declare module 'fastify' {
}
}

export = fp<{}>(
export = fp<{ core?: string }>(
function (fastify, _opts, done) {
const flash = flashFactory()

Expand Down
46 changes: 25 additions & 21 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ test('should set error message and and clear up after displaying.', async (t: Te
fastify.get('/test', (req, reply) => {
const count = req.flash('error', 'Something went wrong')

const flash = req.session.get('flash') || {}
t.assert.strictEqual(count, 1)
t.assert.strictEqual(Object.keys(req.session.get('flash')).length, 1)
t.assert.strictEqual(req.session.get('flash').error.length, 1)
t.assert.strictEqual(Object.keys(flash).length, 1)
t.assert.strictEqual((flash).error.length, 1)
const error = reply.flash('error')
reply.send({ error })
})
Expand All @@ -49,9 +50,10 @@ test('should set multiple flash messages.', async (t: TestContext) => {
req.flash('info', 'Welcome')
const count = req.flash('info', 'Check out this great new feature')

const flash = req.session.get('flash') || {}
t.assert.strictEqual(count, 2)
t.assert.strictEqual(Object.keys(req.session.get('flash')).length, 1)
t.assert.strictEqual(req.session.get('flash').info.length, 2)
t.assert.strictEqual(Object.keys(flash).length, 1)
t.assert.strictEqual((flash).info.length, 2)
const info = reply.flash('info')

reply.send({ info })
Expand All @@ -78,7 +80,7 @@ test('should set flash messages in one call.', async (t: TestContext) => {
const count = req.flash('warning', ['username required', 'password required'])
t.assert.strictEqual(count, 2)

const warning = reply.flash('warning')
const warning = reply.flash('warning') as string[]
t.assert.strictEqual(warning.length, 2)

t.assert.strictEqual(warning[0], 'username required')
Expand Down Expand Up @@ -110,7 +112,7 @@ test('should pass flash between requests.', async (t: TestContext) => {
})

fastify.get('/test2', (_, reply) => {
const warning = reply.flash('warning')
const warning = reply.flash('warning') as string[]
t.assert.strictEqual(warning.length, 2)

t.assert.strictEqual(warning[0], 'username required')
Expand All @@ -126,7 +128,7 @@ test('should pass flash between requests.', async (t: TestContext) => {
t.assert.strictEqual(response.payload, '{}')
t.assert.strictEqual(response.statusCode, 200)

const response2 = await (fastify as any).inject({
const response2 = await (fastify).inject({
method: 'GET',
url: '/test2',
cookies: {
Expand Down Expand Up @@ -156,7 +158,7 @@ test('should pass flash between requests. / 2', async (t: TestContext) => {
})

fastify.get('/test2', (_, reply) => {
const warning = reply.flash('warning')
const warning = reply.flash('warning') as string[]
t.assert.strictEqual(warning.length, 2)

t.assert.strictEqual(warning[0], 'username required')
Expand All @@ -172,7 +174,7 @@ test('should pass flash between requests. / 2', async (t: TestContext) => {
t.assert.strictEqual(response.payload, '{}')
t.assert.strictEqual(response.statusCode, 200)

const response2 = await (fastify as any).inject({
const response2 = await (fastify).inject({
method: 'GET',
url: '/test2',
cookies: {
Expand All @@ -198,15 +200,16 @@ test('should independently set, get and clear flash messages of multiple types.'
req.flash('info', 'Welcome back')
req.flash('notice', 'Last login was yesterday')

t.assert.strictEqual(Object.keys(req.session.get('flash')).length, 2)
t.assert.strictEqual(req.session.get('flash').info.length, 1)
t.assert.strictEqual(req.session.get('flash').notice.length, 1)
const flash = req.session.get('flash') || {}
t.assert.strictEqual(Object.keys(flash).length, 2)
t.assert.strictEqual((flash).info.length, 1)
t.assert.strictEqual((flash).notice.length, 1)

const info = reply.flash('info')
const info = reply.flash('info') as string[]
t.assert.strictEqual(info.length, 1)
t.assert.strictEqual(info[0], 'Welcome back')

const notice = reply.flash('notice')
const notice = reply.flash('notice') as string[]
t.assert.strictEqual(notice.length, 1)
t.assert.strictEqual(notice[0], 'Last login was yesterday')

Expand Down Expand Up @@ -235,7 +238,7 @@ test('should return all messages and clear.', async (t: TestContext) => {
req.flash('error', 'Message queue is down')
req.flash('notice', 'Things are looking bleak')

const msgs = reply.flash()
const msgs = reply.flash() as Record<string, string[]>
t.assert.strictEqual(Object.keys(msgs).length, 2)
t.assert.strictEqual(msgs['error'].length, 2)
t.assert.strictEqual(msgs['notice'].length, 1)
Expand Down Expand Up @@ -265,11 +268,11 @@ test('should format messages.', async (t: TestContext) => {

fastify.get('/test', (req, reply) => {
req.flash('info', 'Hello %s', 'Jared')
const jared = reply.flash('info')[0]
const jared = (reply.flash('info') as string[])[0]
t.assert.strictEqual(jared, 'Hello Jared')

req.flash('info', 'Hello %s %s', 'Jared', 'Hanson')
const jaredHanson = reply.flash('info')[0]
const jaredHanson = (reply.flash('info') as string[])[0]

t.assert.strictEqual(jaredHanson, 'Hello Jared Hanson')

Expand Down Expand Up @@ -297,10 +300,11 @@ test('should return empty array when the type is not set', async (t: TestContext
fastify.get('/test', (req, reply) => {
const count = req.flash('info', 'Hello, world!')

const flash = req.session.get('flash') || {}
t.assert.strictEqual(count, 1)
t.assert.strictEqual(Object.keys(req.session.get('flash')).length, 1)
t.assert.strictEqual(req.session.get('flash').info.length, 1)
t.assert.strictEqual(req.session.get('flash').warning, undefined)
t.assert.strictEqual(Object.keys(flash).length, 1)
t.assert.strictEqual((flash).info.length, 1)
t.assert.strictEqual((flash).warning, undefined)
const warning = reply.flash('warning')
reply.send({ warning })
})
Expand All @@ -323,7 +327,7 @@ test('should throw error when try to set flash without message.', async (t: Test
fastify.register(fastifyFlash, {})

fastify.get('/test', (req) => {
req.flash('info')
(req).flash('info')
})

const response = await fastify.inject({
Expand Down
12 changes: 7 additions & 5 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"extends": ["./tsconfig.json"],
"include": [
"./src/**/*.ts"
]
}
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": ["src"]
}
8 changes: 3 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
{
"compilerOptions": {
"moduleResolution": "node",
"moduleResolution": "node16",
"declaration": true,
"target": "es2017",
"module": "commonjs",
"target": "es2022",
"module": "node16",
"outDir": "lib",
"pretty": true,
"noEmitOnError": true,
"experimentalDecorators": true,
"sourceMap": false,
"emitDecoratorMetadata": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"esModuleInterop": true,
Expand Down
Loading