1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | Object.defineProperty(exports, "__esModule", { value: true });
|
7 | exports.registerInterceptor = exports.invokeMethodWithInterceptors = exports.intercept = exports.INTERCEPT_CLASS_KEY = exports.mergeInterceptors = exports.INTERCEPT_METHOD_KEY = exports.globalInterceptor = exports.asGlobalInterceptor = exports.InterceptedInvocationContext = void 0;
|
8 | const tslib_1 = require("tslib");
|
9 | const metadata_1 = require("@loopback/metadata");
|
10 | const assert_1 = tslib_1.__importDefault(require("assert"));
|
11 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
12 | const binding_decorator_1 = require("./binding-decorator");
|
13 | const binding_inspector_1 = require("./binding-inspector");
|
14 | const binding_key_1 = require("./binding-key");
|
15 | const binding_sorter_1 = require("./binding-sorter");
|
16 | const interceptor_chain_1 = require("./interceptor-chain");
|
17 | const invocation_1 = require("./invocation");
|
18 | const keys_1 = require("./keys");
|
19 | const value_promise_1 = require("./value-promise");
|
20 | const debug = (0, debug_1.default)('loopback:context:interceptor');
|
21 |
|
22 |
|
23 |
|
24 | class InterceptedInvocationContext extends invocation_1.InvocationContext {
|
25 | |
26 |
|
27 |
|
28 |
|
29 | getGlobalInterceptorBindingKeys() {
|
30 | let bindings = this.findByTag(keys_1.ContextTags.GLOBAL_INTERCEPTOR);
|
31 | bindings = bindings.filter(binding =>
|
32 |
|
33 | this.applicableTo(binding));
|
34 | this.sortGlobalInterceptorBindings(bindings);
|
35 | const keys = bindings.map(b => b.key);
|
36 | debug('Global interceptor binding keys:', keys);
|
37 | return keys;
|
38 | }
|
39 | |
40 |
|
41 |
|
42 |
|
43 |
|
44 | applicableTo(binding) {
|
45 | var _a;
|
46 | const sourceType = (_a = this.source) === null || _a === void 0 ? void 0 : _a.type;
|
47 |
|
48 | if (sourceType == null)
|
49 | return true;
|
50 | const allowedSource = binding.tagMap[keys_1.ContextTags.GLOBAL_INTERCEPTOR_SOURCE];
|
51 | return (
|
52 |
|
53 | allowedSource == null ||
|
54 |
|
55 | allowedSource === sourceType ||
|
56 |
|
57 | (Array.isArray(allowedSource) && allowedSource.includes(sourceType)));
|
58 | }
|
59 | |
60 |
|
61 |
|
62 |
|
63 | sortGlobalInterceptorBindings(bindings) {
|
64 | var _a;
|
65 |
|
66 | const orderedGroups = (_a = this.getSync(keys_1.ContextBindings.GLOBAL_INTERCEPTOR_ORDERED_GROUPS, {
|
67 | optional: true,
|
68 | })) !== null && _a !== void 0 ? _a : [];
|
69 | return (0, binding_sorter_1.sortBindingsByPhase)(bindings, keys_1.ContextTags.GLOBAL_INTERCEPTOR_GROUP, orderedGroups);
|
70 | }
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | loadInterceptors() {
|
79 | var _a, _b;
|
80 | let interceptors = (_a = metadata_1.MetadataInspector.getMethodMetadata(exports.INTERCEPT_METHOD_KEY, this.target, this.methodName)) !== null && _a !== void 0 ? _a : [];
|
81 | const targetClass = typeof this.target === 'function' ? this.target : this.target.constructor;
|
82 | const classInterceptors = (_b = metadata_1.MetadataInspector.getClassMetadata(exports.INTERCEPT_CLASS_KEY, targetClass)) !== null && _b !== void 0 ? _b : [];
|
83 |
|
84 | interceptors = mergeInterceptors(classInterceptors, interceptors);
|
85 | const globalInterceptors = this.getGlobalInterceptorBindingKeys();
|
86 |
|
87 | interceptors = mergeInterceptors(globalInterceptors, interceptors);
|
88 | debug('Interceptors for %s', this.targetName, interceptors);
|
89 | return interceptors;
|
90 | }
|
91 | }
|
92 | exports.InterceptedInvocationContext = InterceptedInvocationContext;
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | function asGlobalInterceptor(group) {
|
99 | return binding => {
|
100 | binding
|
101 |
|
102 | .tag(keys_1.ContextTags.GLOBAL_INTERCEPTOR)
|
103 |
|
104 | .tag({ [keys_1.ContextTags.NAMESPACE]: keys_1.GLOBAL_INTERCEPTOR_NAMESPACE });
|
105 | if (group)
|
106 | binding.tag({ [keys_1.ContextTags.GLOBAL_INTERCEPTOR_GROUP]: group });
|
107 | };
|
108 | }
|
109 | exports.asGlobalInterceptor = asGlobalInterceptor;
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | function globalInterceptor(group, ...specs) {
|
116 | return (0, binding_decorator_1.injectable)(asGlobalInterceptor(group), ...specs);
|
117 | }
|
118 | exports.globalInterceptor = globalInterceptor;
|
119 |
|
120 |
|
121 |
|
122 | exports.INTERCEPT_METHOD_KEY = metadata_1.MetadataAccessor.create('intercept:method');
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | function mergeInterceptors(interceptorsFromSpec, existingInterceptors) {
|
139 | const interceptorsToApply = new Set(interceptorsFromSpec);
|
140 | const appliedInterceptors = new Set(existingInterceptors);
|
141 |
|
142 | for (const i of interceptorsToApply) {
|
143 | if (appliedInterceptors.has(i)) {
|
144 | interceptorsToApply.delete(i);
|
145 | }
|
146 | }
|
147 |
|
148 | for (const i of appliedInterceptors) {
|
149 | interceptorsToApply.add(i);
|
150 | }
|
151 | return Array.from(interceptorsToApply);
|
152 | }
|
153 | exports.mergeInterceptors = mergeInterceptors;
|
154 |
|
155 |
|
156 |
|
157 | exports.INTERCEPT_CLASS_KEY = metadata_1.MetadataAccessor.create('intercept:class');
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | class InterceptClassDecoratorFactory extends metadata_1.ClassDecoratorFactory {
|
163 | mergeWithOwn(ownMetadata, target) {
|
164 | ownMetadata = ownMetadata || [];
|
165 | return mergeInterceptors(this.spec, ownMetadata);
|
166 | }
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | class InterceptMethodDecoratorFactory extends metadata_1.MethodDecoratorFactory {
|
173 | mergeWithOwn(ownMetadata, target, methodName, methodDescriptor) {
|
174 | ownMetadata = ownMetadata || {};
|
175 | const interceptors = ownMetadata[methodName] || [];
|
176 |
|
177 | ownMetadata[methodName] = mergeInterceptors(this.spec, interceptors);
|
178 | return ownMetadata;
|
179 | }
|
180 | }
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | function intercept(...interceptorOrKeys) {
|
203 | return function interceptDecoratorForClassOrMethod(
|
204 | // Class or a prototype
|
205 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
206 | target, method,
|
207 |
|
208 |
|
209 |
|
210 | methodDescriptor) {
|
211 | if (method && methodDescriptor) {
|
212 |
|
213 | return InterceptMethodDecoratorFactory.createDecorator(exports.INTERCEPT_METHOD_KEY, interceptorOrKeys, { decoratorName: '@intercept' })(target, method, methodDescriptor);
|
214 | }
|
215 | if (typeof target === 'function' && !method && !methodDescriptor) {
|
216 |
|
217 | return InterceptClassDecoratorFactory.createDecorator(exports.INTERCEPT_CLASS_KEY, interceptorOrKeys, { decoratorName: '@intercept' })(target);
|
218 | }
|
219 |
|
220 | throw new Error('@intercept cannot be used on a property: ' +
|
221 | metadata_1.DecoratorFactory.getTargetName(target, method, methodDescriptor));
|
222 | };
|
223 | }
|
224 | exports.intercept = intercept;
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | function invokeMethodWithInterceptors(context, target, methodName, args, options = {}) {
|
234 |
|
235 |
|
236 | (0, assert_1.default)(!options.skipInterceptors, 'skipInterceptors is not allowed');
|
237 | const invocationCtx = new InterceptedInvocationContext(context, target, methodName, args, options.source);
|
238 | invocationCtx.assertMethodExists();
|
239 | return (0, value_promise_1.tryWithFinally)(() => {
|
240 | const interceptors = invocationCtx.loadInterceptors();
|
241 | const targetMethodInvoker = () => invocationCtx.invokeTargetMethod(options);
|
242 | interceptors.push(targetMethodInvoker);
|
243 | return (0, interceptor_chain_1.invokeInterceptors)(invocationCtx, interceptors);
|
244 | }, () => invocationCtx.close());
|
245 | }
|
246 | exports.invokeMethodWithInterceptors = invokeMethodWithInterceptors;
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 | function registerInterceptor(ctx, interceptor, options = {}) {
|
254 | var _a, _b, _c;
|
255 | let { global } = options;
|
256 | const { group, source } = options;
|
257 | if (group != null || source != null) {
|
258 |
|
259 | global = global !== false;
|
260 | }
|
261 | const namespace = ((_b = (_a = options.namespace) !== null && _a !== void 0 ? _a : options.defaultNamespace) !== null && _b !== void 0 ? _b : global)
|
262 | ? keys_1.GLOBAL_INTERCEPTOR_NAMESPACE
|
263 | : keys_1.LOCAL_INTERCEPTOR_NAMESPACE;
|
264 | let binding;
|
265 | if ((0, binding_inspector_1.isProviderClass)(interceptor)) {
|
266 | binding = (0, binding_inspector_1.createBindingFromClass)(interceptor, {
|
267 | defaultNamespace: namespace,
|
268 | ...options,
|
269 | });
|
270 | if (binding.tagMap[keys_1.ContextTags.GLOBAL_INTERCEPTOR]) {
|
271 | global = true;
|
272 | }
|
273 | ctx.add(binding);
|
274 | }
|
275 | else {
|
276 | let key = options.key;
|
277 | if (!key) {
|
278 | const name = (_c = options.name) !== null && _c !== void 0 ? _c : interceptor.name;
|
279 | if (!name) {
|
280 | key = binding_key_1.BindingKey.generate(namespace).key;
|
281 | }
|
282 | else {
|
283 | key = `${namespace}.${name}`;
|
284 | }
|
285 | }
|
286 | binding = ctx
|
287 | .bind(key)
|
288 | .to(interceptor);
|
289 | }
|
290 | if (global) {
|
291 | binding.apply(asGlobalInterceptor(group));
|
292 | if (source) {
|
293 | binding.tag({ [keys_1.ContextTags.GLOBAL_INTERCEPTOR_SOURCE]: source });
|
294 | }
|
295 | }
|
296 | return binding;
|
297 | }
|
298 | exports.registerInterceptor = registerInterceptor;
|
299 |
|
\ | No newline at end of file |