diff --git a/README.md b/README.md index 16e036c..d589a09 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,19 @@ A session store. Needs the following methods: Stores written for [express-session](https://github.com/expressjs/session) need an adapter that implements `compareAndSet`. +When the store is backed by Redis, you can make its connection identify this plugin to the server through [`CLIENT SETINFO`](https://redis.io/docs/latest/commands/client-setinfo/), so that it shows up in `CLIENT LIST` as `node-redis(fastify-session_v)` instead of a plain `node-redis`: + +```js +const { createClient } = require('redis') +const { RedisStore } = require('connect-redis') + +const client = createClient({ clientInfoTag: fastifySession.getDefaultClientInfoTag() }) +client.connect().catch(console.error) +const store = new RedisStore({ client }) +``` + +`getDefaultClientInfoTag()` returns `fastify-session_v`; ioredis accepts the same `clientInfoTag` option. + If you are terminating HTTPs at the reverse proxy, you need to add the `trustProxy` setting to your fastify instance if you want to use secure cookies. diff --git a/benchmark/bench.js b/benchmark/bench.js index 404392c..276c8c5 100644 --- a/benchmark/bench.js +++ b/benchmark/bench.js @@ -18,7 +18,9 @@ function createServer (sessionPlugin, cookiePlugin, storeType) { if (storeType === 'redis') { if (!redisClient) { - redisClient = createClient() + redisClient = createClient({ + clientInfoTag: fastifySession.getDefaultClientInfoTag() + }) redisClientConnecting = redisClient.connect() } store = new RedisStore({ client: redisClient }) diff --git a/examples/redis.js b/examples/redis.js index a9d1c6a..e23f77f 100644 --- a/examples/redis.js +++ b/examples/redis.js @@ -8,7 +8,9 @@ const { RedisStore } = require('connect-redis') const fastify = Fastify() -const redisClient = createClient() +const redisClient = createClient({ + clientInfoTag: fastifySession.getDefaultClientInfoTag() +}) redisClient.connect().catch(console.error) class AtomicRedisStore extends RedisStore { diff --git a/index.js b/index.js index 7fd0c2d..54c3593 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,20 @@ const Store = require('./lib/store') const Session = require('./lib/session') const { getSessionGeneration } = require('./lib/sessionMetadata') +/** + * Get the default clientInfoTag for identifying the framework in Redis CLIENT SETINFO. + * This can be used when creating Redis clients for session stores. + * @returns {string} The client info tag (e.g., "fastify-session_v11.1.1") + */ +function getDefaultClientInfoTag () { + try { + const version = require('./lib/version') + return `fastify-session_v${version}` + } catch { + return 'fastify-session' + } +} + function fastifySession (fastify, options, next) { const error = checkOptions(options) if (error) { @@ -288,3 +302,4 @@ module.exports.fastifySession = fastifySession module.exports.Store = Store module.exports.MemoryStore = Store +module.exports.getDefaultClientInfoTag = getDefaultClientInfoTag diff --git a/lib/version.js b/lib/version.js new file mode 100644 index 0000000..aefff17 --- /dev/null +++ b/lib/version.js @@ -0,0 +1,4 @@ +'use strict' + +// This file is auto-generated during the release process +module.exports = '11.1.6' diff --git a/package.json b/package.json index 58c89b7..6e012be 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "redis": "docker run -p 6379:6379 --rm redis", "benchmark": "node benchmark/bench.js", "lint": "eslint", - "lint:fix": "eslint --fix" + "lint:fix": "eslint --fix", + "prepack": "node scripts/generate-version.js", + "version": "node scripts/generate-version.js" }, "keywords": [ "session", diff --git a/scripts/generate-version.js b/scripts/generate-version.js new file mode 100644 index 0000000..abae985 --- /dev/null +++ b/scripts/generate-version.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node +'use strict' + +const { writeFileSync } = require('node:fs') +const { join } = require('node:path') + +const pkg = require('../package.json') + +const versionFilePath = join(__dirname, '..', 'lib', 'version.js') +const content = `'use strict' + +// This file is auto-generated during the release process +module.exports = '${pkg.version}' +` + +writeFileSync(versionFilePath, content, 'utf8') +console.log(`Generated lib/version.js with version ${pkg.version}`) diff --git a/test/clientInfoTag.test.js b/test/clientInfoTag.test.js new file mode 100644 index 0000000..ebed374 --- /dev/null +++ b/test/clientInfoTag.test.js @@ -0,0 +1,46 @@ +'use strict' + +const test = require('node:test') +const fastifySession = require('..') + +test('getDefaultClientInfoTag should return version tag', (t) => { + t.plan(1) + const tag = fastifySession.getDefaultClientInfoTag() + t.assert.match(tag, /^fastify-session_v\d+\.\d+\.\d+$/) +}) + +test('getDefaultClientInfoTag should be accessible from module', (t) => { + t.plan(1) + t.assert.strictEqual(typeof fastifySession.getDefaultClientInfoTag, 'function') +}) + +test('getDefaultClientInfoTag should fallback to fastify-session when version is unavailable', (t) => { + t.plan(1) + + // Mock require to simulate missing version.js + const Module = require('module') + const originalRequire = Module.prototype.require + + Module.prototype.require = function (id) { + if (id === './lib/version') { + throw new Error('Cannot find module') + } + return originalRequire.apply(this, arguments) + } + + // Clear the require cache + delete require.cache[require.resolve('..')] + delete require.cache[require.resolve('../lib/version')] + + const fastifySessionMocked = require('..') + const tag = fastifySessionMocked.getDefaultClientInfoTag() + + // Restore original require + Module.prototype.require = originalRequire + + // Clear cache again + delete require.cache[require.resolve('..')] + delete require.cache[require.resolve('../lib/version')] + + t.assert.strictEqual(tag, 'fastify-session') +}) diff --git a/test/version.test.js b/test/version.test.js new file mode 100644 index 0000000..6c802f1 --- /dev/null +++ b/test/version.test.js @@ -0,0 +1,14 @@ +'use strict' + +const fs = require('node:fs') +const path = require('node:path') +const { test } = require('node:test') + +test('lib/version.js should be the same as package.json', t => { + t.plan(1) + + const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8')) + const version = require('../lib/version') + + t.assert.strictEqual(version, json.version) +}) diff --git a/types/index.d.ts b/types/index.d.ts index 8e7b89f..ae8e348 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -24,6 +24,7 @@ declare module 'fastify' { type FastifySession = FastifyPluginCallback & { Store: fastifySession.MemoryStore, MemoryStore: fastifySession.MemoryStore, + getDefaultClientInfoTag: () => string, } type Callback = (err?: any) => void @@ -230,6 +231,14 @@ declare namespace fastifySession { export const Store: MemoryStore + /** + * Get the default clientInfoTag for identifying the framework in Redis CLIENT SETINFO. + * This can be used when creating Redis clients for session stores. + * @link https://redis.io/docs/latest/commands/client-setinfo/ + * @returns The client info tag (e.g., "fastify-session_v11.1.1") + */ + export function getDefaultClientInfoTag (): string + export const fastifySession: FastifySession export { fastifySession as default } } diff --git a/types/index.tst.ts b/types/index.tst.ts index 2da05aa..7c180ea 100644 --- a/types/index.tst.ts +++ b/types/index.tst.ts @@ -37,6 +37,7 @@ declare module 'fastify' { expect(plugin.Store).type.toBe() expect(plugin.MemoryStore).type.toBe() +expect(plugin.getDefaultClientInfoTag()).type.toBe() const secret = 'ABCDEFGHIJKLNMOPQRSTUVWXYZ012345'