-
Notifications
You must be signed in to change notification settings - Fork 0
/
showHTML.ts
165 lines (162 loc) · 4.98 KB
/
showHTML.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { middlewareCreator } from '../../utils';
import type { HTMLGenerator, SendFile } from '../../services';
import type { AsyncExpressMiddleware, Response, NextFunction } from '../../types';
/**
* The options to customize the behavior of the middleware.
*
* @group Middlewares/ShowHTML
*/
export type ShowHTMLOptions = {
/**
* The name of the file the middleware will serve. If the {@link HTMLGenerator} service
* is available, it will be overriden by the service.
*
* @default 'index.html'
*/
file: string;
};
/**
* The options to construct a {@link ShowHTML}.
*
* @group Middlewares/ShowHTML
*/
export type ShowHTMLConstructorOptions = Partial<ShowHTMLOptions> & {
/**
* A dictionary with the dependencies to inject.
*/
inject: {
sendFile: SendFile;
/**
* A function to get a possible {@link HTMLGenerator}. This is injected as a "getter"
* to not interrupt the DIC "lifecycle": middlewares are initialized right when the
* app starts, and injecting a reference would force the service to be initialized
* too, even if a request is not being made.
*/
getHTMLGenerator?: () => HTMLGenerator | undefined;
};
};
/**
* The options for the middleware creator that will mount an instance of {@link ShowHTML}.
*
* @group Middlewares/ShowHTML
*/
export type ShowHTMLMiddlewareOptions = Partial<ShowHTMLOptions> & {
/**
* The name of an {@link HTMLGenerator} service already available in the application.
*
* @default 'htmlGenerator'
*/
htmlGeneratorServiceName?: string;
};
/**
* A very simple middleware service to send an HTML on a server response. The special
* _'feature'_ of this service is that it can be hooked up to an {@link HTMLGenerator}
* service and it will automatically server the file generated by it.
*
* @group Middleware Classes
* @group Middlewares/ShowHTML
* @prettierignore
*/
export class ShowHTML {
/**
* The service that serves a file.
*/
protected readonly _sendFile: SendFile;
/**
* A function to get a possible {@link HTMLGenerator}. This is injected as a "getter"
* to not interrupt the DIC "lifecycle": middlewares are initialized right when the
* app starts, and injecting a reference would force the service to be initialized
* too, even if a request is not being made.
*/
protected readonly _getHTMLGenerator: () => HTMLGenerator | undefined;
/**
* The customization options for the middleware.
*/
protected _options: ShowHTMLOptions;
/**
* Whether or not the file is ready to be served. In case the middleware uses an
* {@link HTMLGenerator} service, the file needs to be generated before being available,
* and that's why this flag exists.
*/
protected _fileReady: boolean = false;
/**
* @param options The options to construct the class.
*/
constructor({ inject, ...options }: ShowHTMLConstructorOptions) {
this._sendFile = inject.sendFile;
this._getHTMLGenerator = inject.getHTMLGenerator || (() => undefined);
this._options = {
file: 'index.html',
...options,
};
}
/**
* Generates the middleware that serves the HTML file.
*/
getMiddleware(): AsyncExpressMiddleware {
return async (_, res, next) => {
// If the file is ready to be served, serve it.
if (this._fileReady) {
this._sendResponse(res, next);
return;
}
const htmlGenerator = this._getHTMLGenerator();
// If there's no generator, switch the flag and just serve the file.
if (!htmlGenerator) {
this._fileReady = true;
this._sendResponse(res, next);
return;
}
try {
// Wait for the HTML to be generated.
await htmlGenerator.whenReady();
// Update the local option.
this._options.file = htmlGenerator.options.file;
// Switch the flag and serve the file.
this._fileReady = true;
this._sendResponse(res, next);
} catch (error) {
next(error);
}
};
}
/**
* The customization options.
*/
get options(): Readonly<ShowHTMLOptions> {
return { ...this._options };
}
/**
* Serves the HTML file to the response.
*
* @param res The response object generated by the application.
* @param next The function to call the next middleware.
*/
protected _sendResponse(res: Response, next: NextFunction): void {
res.setHeader('Content-Type', 'text/html');
this._sendFile({
res,
next,
filepath: this._options.file,
});
}
}
/**
* Creates the middleware that serves an HTML file in the response.
*
* @group Middlewares
* @group Middlewares/ShowHTML
*/
export const showHTMLMiddleware = middlewareCreator(
(options: ShowHTMLMiddlewareOptions = {}) =>
(app) => {
const { htmlGeneratorServiceName = 'htmlGenerator', ...rest } = options;
return new ShowHTML({
inject: {
sendFile: app.get('sendFile'),
getHTMLGenerator: () => app.try(htmlGeneratorServiceName),
},
...rest,
}).getMiddleware();
},
);