File size: 10,887 Bytes
cfbbc1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MiddlewareModule = void 0;
const common_1 = require("@nestjs/common");
const request_method_enum_1 = require("@nestjs/common/enums/request-method.enum");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const invalid_middleware_exception_1 = require("../errors/exceptions/invalid-middleware.exception");
const runtime_exception_1 = require("../errors/exceptions/runtime.exception");
const context_id_factory_1 = require("../helpers/context-id-factory");
const execution_context_host_1 = require("../helpers/execution-context-host");
const constants_1 = require("../injector/constants");
const request_constants_1 = require("../router/request/request-constants");
const router_exception_filters_1 = require("../router/router-exception-filters");
const router_proxy_1 = require("../router/router-proxy");
const utils_1 = require("../router/utils");
const builder_1 = require("./builder");
const resolver_1 = require("./resolver");
const route_info_path_extractor_1 = require("./route-info-path-extractor");
const routes_mapper_1 = require("./routes-mapper");
class MiddlewareModule {
    constructor() {
        this.routerProxy = new router_proxy_1.RouterProxy();
        this.exceptionFiltersCache = new WeakMap();
        this.logger = new common_1.Logger(MiddlewareModule.name);
    }
    async register(middlewareContainer, container, config, injector, httpAdapter, graphInspector, options) {
        this.appOptions = options;
        const appRef = container.getHttpAdapterRef();
        this.routerExceptionFilter = new router_exception_filters_1.RouterExceptionFilters(container, config, appRef);
        this.routesMapper = new routes_mapper_1.RoutesMapper(container, config);
        this.resolver = new resolver_1.MiddlewareResolver(middlewareContainer, injector);
        this.routeInfoPathExtractor = new route_info_path_extractor_1.RouteInfoPathExtractor(config);
        this.injector = injector;
        this.container = container;
        this.httpAdapter = httpAdapter;
        this.graphInspector = graphInspector;
        const modules = container.getModules();
        await this.resolveMiddleware(middlewareContainer, modules);
    }
    async resolveMiddleware(middlewareContainer, modules) {
        const moduleEntries = [...modules.entries()];
        const loadMiddlewareConfiguration = async ([moduleName, moduleRef]) => {
            await this.loadConfiguration(middlewareContainer, moduleRef, moduleName);
            await this.resolver.resolveInstances(moduleRef, moduleName);
        };
        await Promise.all(moduleEntries.map(loadMiddlewareConfiguration));
    }
    async loadConfiguration(middlewareContainer, moduleRef, moduleKey) {
        const { instance } = moduleRef;
        if (!instance.configure) {
            return;
        }
        const middlewareBuilder = new builder_1.MiddlewareBuilder(this.routesMapper, this.httpAdapter, this.routeInfoPathExtractor);
        try {
            await instance.configure(middlewareBuilder);
        }
        catch (err) {
            if (!this.appOptions.preview) {
                throw err;
            }
            const warningMessage = `Warning! "${moduleRef.name}" module exposes a "configure" method that throws an exception in the preview mode` +
                ` (possibly due to missing dependencies). Note: you can ignore this message, just be aware that some of those conditional middlewares will not be reflected in your graph.`;
            this.logger.warn(warningMessage);
        }
        if (!(middlewareBuilder instanceof builder_1.MiddlewareBuilder)) {
            return;
        }
        const config = middlewareBuilder.build();
        middlewareContainer.insertConfig(config, moduleKey);
    }
    async registerMiddleware(middlewareContainer, applicationRef) {
        const configs = middlewareContainer.getConfigurations();
        const registerAllConfigs = async (moduleKey, middlewareConfig) => {
            for (const config of middlewareConfig) {
                await this.registerMiddlewareConfig(middlewareContainer, config, moduleKey, applicationRef);
            }
        };
        const entriesSortedByDistance = [...configs.entries()].sort(([moduleA], [moduleB]) => {
            const moduleARef = this.container.getModuleByKey(moduleA);
            const moduleBRef = this.container.getModuleByKey(moduleB);
            const isModuleAGlobal = moduleARef.distance === Number.MAX_VALUE;
            const isModuleBGlobal = moduleBRef.distance === Number.MAX_VALUE;
            if (isModuleAGlobal && isModuleBGlobal) {
                return 0;
            }
            if (isModuleAGlobal) {
                return -1;
            }
            if (isModuleBGlobal) {
                return 1;
            }
            return moduleARef.distance - moduleBRef.distance;
        });
        for (const [moduleRef, moduleConfigurations] of entriesSortedByDistance) {
            await registerAllConfigs(moduleRef, [...moduleConfigurations]);
        }
    }
    async registerMiddlewareConfig(middlewareContainer, config, moduleKey, applicationRef) {
        const { forRoutes } = config;
        for (const routeInfo of forRoutes) {
            await this.registerRouteMiddleware(middlewareContainer, routeInfo, config, moduleKey, applicationRef);
        }
    }
    async registerRouteMiddleware(middlewareContainer, routeInfo, config, moduleKey, applicationRef) {
        const middlewareCollection = [].concat(config.middleware);
        const moduleRef = this.container.getModuleByKey(moduleKey);
        for (const metatype of middlewareCollection) {
            const collection = middlewareContainer.getMiddlewareCollection(moduleKey);
            const instanceWrapper = collection.get(metatype);
            if ((0, shared_utils_1.isUndefined)(instanceWrapper)) {
                throw new runtime_exception_1.RuntimeException();
            }
            if (instanceWrapper.isTransient) {
                return;
            }
            this.graphInspector.insertClassNode(moduleRef, instanceWrapper, 'middleware');
            const middlewareDefinition = {
                type: 'middleware',
                methodName: 'use',
                className: instanceWrapper.name,
                classNodeId: instanceWrapper.id,
                metadata: {
                    key: routeInfo.path,
                    path: routeInfo.path,
                    requestMethod: request_method_enum_1.RequestMethod[routeInfo.method] ??
                        'ALL',
                    version: routeInfo.version,
                },
            };
            this.graphInspector.insertEntrypointDefinition(middlewareDefinition, instanceWrapper.id);
            await this.bindHandler(instanceWrapper, applicationRef, routeInfo, moduleRef, collection);
        }
    }
    async bindHandler(wrapper, applicationRef, routeInfo, moduleRef, collection) {
        const { instance, metatype } = wrapper;
        if ((0, shared_utils_1.isUndefined)(instance?.use)) {
            throw new invalid_middleware_exception_1.InvalidMiddlewareException(metatype.name);
        }
        const isStatic = wrapper.isDependencyTreeStatic();
        if (isStatic) {
            const proxy = await this.createProxy(instance);
            return this.registerHandler(applicationRef, routeInfo, proxy);
        }
        const isTreeDurable = wrapper.isDependencyTreeDurable();
        await this.registerHandler(applicationRef, routeInfo, async (req, res, next) => {
            try {
                const contextId = this.getContextId(req, isTreeDurable);
                const contextInstance = await this.injector.loadPerContext(instance, moduleRef, collection, contextId);
                const proxy = await this.createProxy(contextInstance, contextId);
                return proxy(req, res, next);
            }
            catch (err) {
                let exceptionsHandler = this.exceptionFiltersCache.get(instance.use);
                if (!exceptionsHandler) {
                    exceptionsHandler = this.routerExceptionFilter.create(instance, instance.use, undefined);
                    this.exceptionFiltersCache.set(instance.use, exceptionsHandler);
                }
                const host = new execution_context_host_1.ExecutionContextHost([req, res, next]);
                exceptionsHandler.next(err, host);
            }
        });
    }
    async createProxy(instance, contextId = constants_1.STATIC_CONTEXT) {
        const exceptionsHandler = this.routerExceptionFilter.create(instance, instance.use, undefined, contextId);
        const middleware = instance.use.bind(instance);
        return this.routerProxy.createProxy(middleware, exceptionsHandler);
    }
    async registerHandler(applicationRef, routeInfo, proxy) {
        const { method } = routeInfo;
        const paths = this.routeInfoPathExtractor.extractPathsFrom(routeInfo);
        const isMethodAll = (0, utils_1.isRequestMethodAll)(method);
        const requestMethod = request_method_enum_1.RequestMethod[method];
        const router = await applicationRef.createMiddlewareFactory(method);
        const middlewareFunction = isMethodAll
            ? proxy
            : (req, res, next) => {
                const actualRequestMethod = applicationRef.getRequestMethod?.(req);
                if (actualRequestMethod === requestMethod ||
                    (actualRequestMethod === request_method_enum_1.RequestMethod[request_method_enum_1.RequestMethod.HEAD] &&
                        requestMethod === request_method_enum_1.RequestMethod[request_method_enum_1.RequestMethod.GET])) {
                    return proxy(req, res, next);
                }
                return next();
            };
        const pathsToApplyMiddleware = [];
        paths.some(path => path.match(/^\/?$/))
            ? pathsToApplyMiddleware.push('/')
            : pathsToApplyMiddleware.push(...paths);
        pathsToApplyMiddleware.forEach(path => router(path, middlewareFunction));
    }
    getContextId(request, isTreeDurable) {
        const contextId = context_id_factory_1.ContextIdFactory.getByRequest(request);
        if (!request[request_constants_1.REQUEST_CONTEXT_ID]) {
            Object.defineProperty(request, request_constants_1.REQUEST_CONTEXT_ID, {
                value: contextId,
                enumerable: false,
                writable: false,
                configurable: false,
            });
            const requestProviderValue = isTreeDurable
                ? contextId.payload
                : Object.assign(request, contextId.payload);
            this.container.registerRequestProvider(requestProviderValue, contextId);
        }
        return contextId;
    }
}
exports.MiddlewareModule = MiddlewareModule;