this repo has no description
atproto bluesky typescript express
at main 119 lines 3.2 kB view raw
1import crypto from "node:crypto"; 2import path from "node:path"; 3import { fileURLToPath } from "node:url"; 4import express, { Express, Request, Response, NextFunction } from "express"; 5import { AtpAgent, AtpSessionEvent, AtpSessionData } from "@atproto/api"; 6import { engine } from "express-handlebars"; 7import expressSession from "express-session"; 8import cookieParser from "cookie-parser"; 9import bodyParser from "body-parser"; 10import moment from "moment"; 11 12import { NODE_ENV, SESSION_SECRET, PORT, PUBLIC_URL } from "./env.js"; 13 14import router from "./routes/main.js"; 15import mobile from "./routes/mobile.js"; 16import account from "./routes/account.js"; 17import api from "./routes/appview.js"; 18 19import checkUserAgent from "./lib/useragent.js"; 20 21import { registerHelpers } from "./helpers/registerHelpers.js"; 22 23const app: Express = express(); 24const randomSecret = crypto.randomBytes(32).toString("hex"); 25 26registerHelpers(); 27 28app.engine( 29 "hbs", 30 engine({ 31 extname: ".hbs", 32 helpers: { 33 elapsed: function (date: any) { 34 const parsedDate = moment(date); 35 const now = moment(); 36 const duration = moment.duration(now.diff(parsedDate)); 37 if (duration.days() > 0) { 38 return `${duration.days()} days`; 39 } else if (duration.hours() > 0) { 40 return `${duration.hours()} hours`; 41 } else if (duration.minutes() > 0) { 42 return `${duration.minutes()} minutes`; 43 } else { 44 return `${duration.seconds()} seconds`; 45 } 46 }, 47 rssDate: function (date: any) { 48 return moment(date).format("ddd, DD MMM YYYY HH:mm:ss ZZ"); 49 }, 50 }, 51 }), 52); 53 54app.set("view engine", "hbs"); 55app.set( 56 "views", 57 path.join(path.dirname(fileURLToPath(import.meta.url)), "../views"), 58); 59 60app.use(cookieParser()); 61app.use( 62 expressSession({ 63 name: "sid", 64 secret: SESSION_SECRET || randomSecret, 65 resave: false, 66 saveUninitialized: false, 67 cookie: { 68 secure: NODE_ENV === "production", 69 httpOnly: true, 70 sameSite: "lax", 71 maxAge: 1000 * 60 * 60 * 24 * 7, 72 }, 73 genid: () => crypto.randomUUID(), 74 }), 75); 76 77app.use(async (req: Request, res: Response, next: NextFunction) => { 78 const agent = new AtpAgent({ 79 service: req.session.pds || "https://bsky.social", 80 persistSession: (e: AtpSessionEvent, s?: AtpSessionData) => { 81 if (s) { 82 req.session.atp = s; 83 } else { 84 delete req.session.atp; 85 } 86 } 87 }); 88 89 if (req.session.atp) { 90 try { 91 await agent.resumeSession(req.session.atp); 92 } catch (error) { 93 console.error(error); 94 delete req.session.atp; 95 } 96 } 97 98 (req as any).agent = agent; 99 next(); 100}); 101 102app.use(express.static("public")); 103app.use(express.json()); 104app.use(express.urlencoded({ extended: true })); 105app.use(bodyParser.urlencoded({ extended: true })); 106app.use(checkUserAgent); 107app.use("/", router); 108app.use("/m", mobile); 109app.use("/account", account); 110app.use("/xrpc", api); 111 112app.listen(PORT, () => { 113 console.log(`[server] Server is running at ${PUBLIC_URL}`); 114 if (!SESSION_SECRET) { 115 console.warn( 116 "[warning] SESSION_SECRET is not set. Sessions might not work properly,", 117 ); 118 } 119});