this repo has no description
atproto
bluesky
typescript
express
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});