Files
wildflock/src/app.ts
2026-06-26 12:56:08 +03:00

117 lines
3.3 KiB
TypeScript

import { randomUUID } from 'crypto';
import { feathers } from '@feathersjs/feathers';
import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express';
import configuration from '@feathersjs/configuration';
import socketio from '@feathersjs/socketio';
import session from 'express-session';
import cookieParser from 'cookie-parser';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
import config from 'config';
import type { Application } from './declarations';
import { logger } from './logger';
import { logError } from './hooks/log-error';
import { services } from './services/index';
import { channels } from './channels';
import { Env, getEnv } from './helpers/get-env';
const app: Application = express(feathers());
let sessionStore;
// Load app configuration
app.configure(configuration());
app.use(cors());
app.use(
json({
limit: '20mb',
})
);
app.use(cookieParser());
if (getEnv() === Env.prod) {
const redisClient = createClient({
url: config.get('redis.url'),
// Periodically PING so a half-open connection (e.g. after the Redis
// server restarts behind a stable ClusterIP) is detected and a
// reconnect is triggered. Without this, node-redis keeps queueing
// commands on the dead socket and express-session never gets a reply,
// so every session-writing request (e.g. /auth-oidc) hangs forever
// until the pod is restarted.
pingInterval: 10000,
socket: {
keepAlive: 5000,
connectTimeout: 10000,
// Keep retrying with capped backoff instead of giving up.
reconnectStrategy: (retries) => Math.min(retries * 100, 3000),
},
});
// node-redis throws (and can crash the process) if an 'error' is emitted
// with no listener; log it and let the reconnect strategy recover.
redisClient.on('error', (err) => logger.error(`Redis session store error: ${err}`));
sessionStore = new RedisStore({
prefix: 'walias:',
client: redisClient,
});
redisClient.connect().catch((err) => logger.error(`Redis initial connect failed: ${err}`));
}
app.use(
session({
store: sessionStore,
secret: config.get('sessionSecret') || randomUUID(),
resave: false,
saveUninitialized: false,
cookie: { secure: false },
})
);
// Propagate session to request.params in feathers services
app.use(function (req, _res, next) {
req.feathers = {
...req.feathers,
session: req.session,
};
next();
});
app.use(urlencoded({ extended: true }));
// Host the public folder
app.use('/', serveStatic(app.get('public')));
// Configure services and real-time functionality
app.configure(rest());
app.configure(
socketio({
cors: {
origin: app.get('origins'),
},
})
);
app.configure(services);
app.configure(channels);
// Configure a middleware for 404s and the error handler
app.use(notFound());
app.use(errorHandler({ logger }));
// Register hooks that run on all service methods
app.hooks({
around: {
all: [logError],
},
before: {},
after: {},
error: {},
});
// Register application setup and teardown hooks here
app.hooks({
setup: [],
teardown: [],
});
export { app };