@@ -7,15 +7,16 @@ import jwt from 'jsonwebtoken';
7
7
import dayjsModule from 'dayjs' ;
8
8
import { Redis } from 'ioredis' ;
9
9
import { logger } from './logger' ;
10
- import fs from 'node:fs/promises' ;
11
- import { Request } from 'express ' ;
10
+ import fsp from 'node:fs/promises' ;
11
+ import fs from 'node:fs ' ;
12
12
import utc from 'dayjs/plugin/utc' ;
13
13
import nodemailer from 'nodemailer' ;
14
14
import { db , redis } from './db/db' ;
15
15
import { Queue , Worker , Job } from 'bullmq' ;
16
16
import timezone from 'dayjs/plugin/timezone' ;
17
17
import { appConfig , emailConfig , oauthConfig , sessionConfig } from './config' ;
18
18
import { GithubUserEmail , GitHubOauthToken , ApiKeyPayload , User } from './types' ;
19
+ import { Application , Request , Response , NextFunction } from 'express' ;
19
20
20
21
export function dayjs ( date : string | Date = new Date ( ) ) {
21
22
dayjsModule . extend ( utc ) ;
@@ -234,7 +235,7 @@ export async function sendGeneralEmail({
234
235
message : string ;
235
236
} ) {
236
237
try {
237
- const templateContent = await fs . readFile (
238
+ const templateContent = await fsp . readFile (
238
239
path . resolve ( path . join ( process . cwd ( ) , 'src' , 'views' , 'emails' , 'general.html' ) ) ,
239
240
'utf-8' ,
240
241
) ;
@@ -284,3 +285,66 @@ export const modifyUserSessionById = async (
284
285
logger . error ( 'No session found for user ID:' , userId ) ;
285
286
return null ;
286
287
} ;
288
+
289
+ export function reload ( {
290
+ app,
291
+ watch,
292
+ options = { } ,
293
+ } : {
294
+ app : Application ;
295
+ watch : { path : string ; extensions : string [ ] } [ ] ;
296
+ options ?: { pollInterval ?: number ; quiet ?: boolean } ;
297
+ } ) : void {
298
+ if ( appConfig . env !== 'development' ) return ;
299
+
300
+ const pollInterval = options . pollInterval || 50 ;
301
+ const quiet = options . quiet || false ;
302
+ let changeDetected = false ;
303
+
304
+ watch . forEach ( ( { path : dir , extensions } ) => {
305
+ const extensionsSet = new Set ( extensions ) ;
306
+ fs . watch ( dir , { recursive : true } , ( _ : fs . WatchEventType , filename : string | null ) => {
307
+ if ( filename && extensionsSet . has ( filename . slice ( filename . lastIndexOf ( '.' ) ) ) ) {
308
+ if ( ! quiet ) logger . info ( '[reload] File changed: %s' , filename ) ;
309
+ changeDetected = true ;
310
+ }
311
+ } ) ;
312
+ } ) ;
313
+
314
+ app . get ( '/wait-for-reload' , ( req : Request , res : Response ) => {
315
+ const timer = setInterval ( ( ) => {
316
+ if ( changeDetected ) {
317
+ changeDetected = false ;
318
+ clearInterval ( timer ) ;
319
+ res . send ( ) ;
320
+ }
321
+ } , pollInterval ) ;
322
+
323
+ req . on ( 'close' , ( ) => clearInterval ( timer ) ) ;
324
+ } ) ;
325
+
326
+ const clientScript = `
327
+ <script>
328
+ (async function poll() {
329
+ try {
330
+ await fetch('/wait-for-reload');
331
+ location.reload();
332
+ } catch {
333
+ location.reload();
334
+ }
335
+ })();
336
+ </script>\n\t` ;
337
+
338
+ app . use ( ( req : Request , res : Response , next : NextFunction ) => {
339
+ const originalSend = res . send . bind ( res ) ;
340
+
341
+ res . send = function ( body : any ) : Response {
342
+ if ( typeof body === 'string' && body . includes ( '</head>' ) ) {
343
+ body = body . replace ( '</head>' , clientScript + '</head>' ) ;
344
+ }
345
+ return originalSend ( body ) ;
346
+ } ;
347
+
348
+ next ( ) ;
349
+ } ) ;
350
+ }
0 commit comments