Skip to content

Instantly share code, notes, and snippets.

@paulrobello
Last active August 15, 2022 11:48
Show Gist options
  • Save paulrobello/3d701297437422861e956913cbc482c3 to your computer and use it in GitHub Desktop.
Save paulrobello/3d701297437422861e956913cbc482c3 to your computer and use it in GitHub Desktop.
Koa v2 with SocketIO v2 shared session
// 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;
// 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