Last active
August 15, 2022 11:48
-
-
Save paulrobello/3d701297437422861e956913cbc482c3 to your computer and use it in GitHub Desktop.
Koa v2 with SocketIO v2 shared session
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// example of using a Redis store for shared session between koa and socket.io | |
import Koa from 'koa'; | |
import KoaSession from 'koa-session'; | |
import redisStore from 'koa-redis'; | |
import { Socket } from 'socket.io'; | |
import socketIO from 'socket.io'; | |
import { SocketIOKoaSession, SessionSocket } from './SocketIOKoaSession'; | |
const app = new Koa(); | |
const sessionStore = redisStore({}); | |
const CONFIG = { | |
rolling: true, | |
resave: true, | |
saveUninitialized: true, | |
store: sessionStore | |
}; | |
app.keys = ['123456']; | |
const session = KoaSession(CONFIG, app); | |
app.use(session); | |
app.use((ctx, next) => { | |
// only increment views for root url | |
if (ctx.url === '/') { | |
ctx.session.views = (ctx.session.views || 0) + 1; | |
} | |
return next(); | |
}); | |
const server = app.listen(3000); | |
// create Socket.io app instance | |
const io = socketIO(server, {}); | |
// add Socket.io middleware to parse Koa-session cookie | |
io.use(SocketIOKoaSession(app, CONFIG)); | |
io.on('connect', async (socket: Socket) => { | |
// the SocketIOKoaSession middleware will change Socket to SessionSocket | |
const sock = socket as SessionSocket; | |
// get session from socket | |
let sess = sock.session; | |
console.log('socket connected', sock.id); | |
// this should never happen since the middleware will throw error if session cookie does not exist | |
if (!sess) { | |
console.log('NO SESSION', sock.id); | |
return; | |
} | |
// refresh sesison from store at regular intervals in case its been updated by other shared sessions | |
const sessInt = setInterval(() => { | |
sess.reload().then((newSess) => { | |
const oldViews = sess.views; | |
sess = newSess; | |
if (sess.views !== oldViews){ | |
// emit new number of views on session | |
socket.emit('views', sess.views); | |
} | |
}); | |
}, 60000); | |
sock.on('disconnect', () => { | |
console.log('socket disconnected', sock.id); | |
clearInterval(sessInt); | |
}); | |
// emit current number of views on session | |
socket.emit('views', sess.views); | |
}); | |
app.context.io = io; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SessionObj ensures that reload,save and destroy are functions, any other key is fair game | |
export interface SessionObj { | |
reload: () => Promise<any>, | |
save: () => Promise<any>, | |
destroy: () => Promise<any>, | |
[key: string]: any | |
} | |
// add our session member to SocketIO.Socket | |
export interface SessionSocket extends SocketIO.Socket { | |
session: SessionObj | |
} | |
export const SocketIOKoaSession = (app: Application, opt: any) => { | |
const store = opt.store; | |
const key = opt.key || 'koa:sess'; | |
return async (socket: Socket, next: any) => { | |
if (!socket.handshake.headers.cookie) { | |
return next(new Error('no cookies')); | |
} | |
// get access to koa context so we can get session cookie | |
// createContext does not need the ServerResponse to get us access to to the cookies | |
// however it is a required parameter that does not allow null or undefined | |
const ctx = app.createContext(socket.request, null as unknown as ServerResponse); | |
const sid = ctx.cookies.get(key, opt); | |
if (!sid) { | |
return next(new Error('no koa session cookie set')); | |
} | |
// cast socket to SessionSocket so we have access to session member | |
const sessionSocket = socket as SessionSocket; | |
// allow socket.io handlers to destroy the session | |
const destroy = () => store.destroy(sid); | |
// allow socket.io handlers to reload session from store | |
const reload = () => store.get(sid).then((newSession: any) => { | |
// save old reload reference to new session | |
newSession.reload = sessionSocket.session.reload; | |
// overwrite old session with session from store | |
sessionSocket.session = newSession || {}; | |
sessionSocket.session.save = () => store.set(sid, newSession); | |
sessionSocket.session.destroy = destroy; | |
return newSession; | |
}); | |
//load session from store or create empty session | |
sessionSocket.session = (await store.get(sid)) || {}; | |
// add our methods to allow socket.io to reload, save and destroy session | |
sessionSocket.session.reload = reload; | |
sessionSocket.session.save = () => store.set(sid, sessionSocket.session); | |
sessionSocket.session.destroy = destroy; | |
await next(); | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment