icydraw/src/server/index.ts

181 lines
4.0 KiB
TypeScript

import express, { RequestHandler } from 'express';
import session from 'express-session';
import passport from 'passport';
import * as redis from 'redis';
import * as icynetstrat from 'passport-icynet';
import connectRedis from 'connect-redis';
import http from 'http';
import { join } from 'path';
import { Server, Socket } from 'socket.io';
import { IcyNetUser } from './types/user';
import { Canvas } from './object/canvas';
import { CanvasRecord, Placement } from '../common/types/canvas';
import { config } from './config';
const RedisStore = connectRedis(session);
const redisClient = config.redis?.enabled ? redis.createClient() : undefined;
const sessionMiddleware = session({
secret: config.server.sessionSecret,
resave: false,
saveUninitialized: false,
cookie: { secure: process.env.NODE_ENV === 'production', sameSite: 'strict' },
store: config.redis?.enabled
? new RedisStore({ client: redisClient })
: undefined,
});
// todo: store less info in session
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((obj: IcyNetUser, done) => {
done(null, obj);
});
passport.use(
new icynetstrat.Strategy(
{
clientID: config.auth.clientID,
clientSecret: config.auth.clientSecret,
callbackURL: config.auth.callbackURL,
scope: [],
},
function (
accessToken: string,
refreshToken: string,
profile: any,
done: Function,
) {
process.nextTick(function () {
return done(null, profile);
});
},
),
);
const app = express();
if (process.env.NODE_ENV === 'production') {
app.enable('trust proxy');
}
const server = http.createServer(app);
const io = new Server(server);
const checkAuth: RequestHandler = (req, res, next) => {
if (req.isAuthenticated()) {
return next();
}
res.send('not logged in :(');
};
app.use(sessionMiddleware);
app.use(passport.initialize());
app.use(passport.session());
app.get(
'/login',
passport.authenticate('icynet', { scope: [] }),
(req, res) => {},
);
app.get(
'/callback',
passport.authenticate('icynet', { failureRedirect: '/?login=false' }),
(req, res) => {
res.redirect('/?login=true');
}, // auth success
);
app.get('/logout', (req, res) => {
req.logout();
res.redirect('/');
});
app.get('/info', checkAuth, (req, res) => {
res.json(req.user);
});
app.use('/canvas.png', (req, res) =>
res.sendFile(join(__dirname, '..', '..', 'canvas.png')),
);
app.use(express.static(join(__dirname, '..', 'public')));
///
const canvas = new Canvas(config.game.boardSize);
const connections: Socket[] = [];
const changes: CanvasRecord[] = [];
const dummyUser: IcyNetUser = {
id: 0,
username: 'placer',
display_name: 'placer',
uuid: 'placer',
accessToken: 'placer',
};
io.use((socket, next) =>
sessionMiddleware(socket.request as any, {} as any, next as any),
);
io.on('connection', (socket) => {
const session = (socket.request as any).session;
const user =
process.env.SKIP_LOGIN === 'true'
? dummyUser
: (session?.passport?.user as IcyNetUser);
connections.push(socket);
socket.emit(
'me',
user
? {
username: user.username,
display_name: user.display_name,
}
: null,
);
socket.emit('plane', {
size: canvas.size,
canvas: Object.values(canvas.getFullPlane()),
});
socket.on('color', ({ c, x, y, t }: Placement) => {
if (!user || c > 0xffffff || c < 0) {
return;
}
canvas.setPixelAt(c, x, y, user.username);
socket.emit('colorack', { c, x, y, t });
});
socket.on('disconnect', () => {
connections.splice(connections.indexOf(socket), 1);
});
});
canvas.registerChangeHandler((record) => {
changes.push(record);
});
setInterval(() => {
if (changes.length && connections.length) {
connections.forEach((socket) => socket.emit('changes', changes));
changes.length = 0;
}
}, 1000);
///
canvas.initialize().then(() =>
server.listen(config.server.port, '0.0.0.0', () => {
console.log(`Listening at http://localhost:${config.server.port}/`);
}),
);