@gravity-ui/gateway 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,6 @@ A flexible and powerful Express controller for working with REST and gRPC APIs i
7
7
  - [Installation](#installation)
8
8
  - [Basic Usage](#basic-usage)
9
9
  - [Configuration](#configuration)
10
- - [Config Structure](#config-structure)
11
10
  - [Validation Schema](#validation-schema)
12
11
  - [Using the API in Node.js](#using-the-api-in-nodejs)
13
12
  - [Schema Scopes](#schema-scopes)
@@ -92,9 +91,18 @@ type AxiosRetryCondition = IAxiosRetryConfig['retryCondition'];
92
91
 
93
92
  type ControllerType = 'rest' | 'grpc';
94
93
 
94
+ type ProxyHeadersFunctionExtra = {
95
+ service: string;
96
+ action: string;
97
+
98
+ protopath?: string;
99
+ protokey?: string;
100
+ };
101
+
95
102
  type ProxyHeadersFunction = (
96
103
  headers: IncomingHttpHeaders,
97
104
  type: ControllerType,
105
+ extra: ProxyHeadersFunctionExtra,
98
106
  ) => IncomingHttpHeaders;
99
107
  type ProxyHeaders = string[] | ProxyHeadersFunction;
100
108
  type ResponseContentType = AxiosResponse['headers']['Content-Type'];
@@ -205,6 +213,49 @@ interface GatewayConfig {
205
213
  }
206
214
  ```
207
215
 
216
+ ### `proxyHeaders`
217
+
218
+ `GatewayConfig.proxyHeaders` is an optional method that allows setting headers for requests at the entire `gateway` level:
219
+
220
+ ```javascript
221
+ const proxyHeaders = (headers, actionType, {service, action}) => {
222
+ const normalizedHeaders = {...headers};
223
+
224
+ if (actionType === 'rest' && service === 'mail') {
225
+ normalizedHeaders['x-mail-service-action'] = action;
226
+ }
227
+
228
+ return normalizedHeaders;
229
+ };
230
+
231
+ const {controller: gatewayController} = getGatewayControllers(
232
+ {root: Schema},
233
+ {...config, proxyHeaders},
234
+ );
235
+ ```
236
+
237
+ You can set headers for a specific action using `ApiServiceBaseActionConfig.proxyHeaders`:
238
+
239
+ ```javascript
240
+ const schema = {
241
+ userService: {
242
+ serviceName: 'users',
243
+ endpoints: {...},
244
+ actions: {
245
+ getProfile: {
246
+ path: () => '/profile',
247
+ method: 'GET',
248
+ proxyHeaders: (headers) => ({...headers, ['x-users-service-action']: 'get-profile'}),
249
+ },
250
+ },
251
+ },
252
+ };
253
+ ```
254
+
255
+ The `GatewayConfig.proxyHeaders` and `ApiServiceBaseActionConfig.proxyHeaders` are merged when the action is called. The strategy for merging headers is not guaranteed.
256
+
257
+ It is recommended to use `GatewayConfig.proxyHeaders` for assigning headers that are common to the entire application or a large number of actions. Otherwise, it is preferable to use `ApiServiceBaseActionConfig.proxyHeaders`.
258
+
208
259
  ### Validation Schema
209
260
 
210
261
  By default, for path params in REST actions, the following regexp is used: `/^((?!(\.\.|\?|#|\\|\/)).)*$/i`.
@@ -86,7 +86,7 @@ function decodeResponse(response, packageRoot, ctx, encodedFields = [], ErrorCon
86
86
  }
87
87
  });
88
88
  }
89
- function createMetadata({ options, actionConfig, config, params, serviceName, ctx, }) {
89
+ function createMetadata({ options, actionConfig, config, params, serviceName, proxyHeadersCaller, ctx, }) {
90
90
  var _a;
91
91
  const { headers, requestId, authArgs } = actionConfig;
92
92
  const proxyHeaders = [...constants_1.DEFAULT_PROXY_HEADERS];
@@ -95,13 +95,13 @@ function createMetadata({ options, actionConfig, config, params, serviceName, ct
95
95
  'accept-language': headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru,
96
96
  };
97
97
  if (typeof options.proxyHeaders === 'function') {
98
- Object.assign(metadata, options.proxyHeaders(Object.assign({}, headers), 'grpc'));
98
+ Object.assign(metadata, proxyHeadersCaller(options.proxyHeaders));
99
99
  }
100
100
  else if (Array.isArray(options.proxyHeaders)) {
101
101
  proxyHeaders.push(...options.proxyHeaders);
102
102
  }
103
103
  if (typeof config.proxyHeaders === 'function') {
104
- Object.assign(metadata, config.proxyHeaders(Object.assign({}, headers), 'grpc'));
104
+ Object.assign(metadata, proxyHeadersCaller(config.proxyHeaders));
105
105
  }
106
106
  else if (Array.isArray(config.proxyHeaders)) {
107
107
  proxyHeaders.push(...config.proxyHeaders);
@@ -385,7 +385,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
385
385
  }
386
386
  return async function action(actionConfig) {
387
387
  var _a;
388
- const { args, requestId, headers, ctx: parentCtx, userId } = actionConfig;
388
+ const { args, requestId, headers, ctx: parentCtx, userId, abortSignal } = actionConfig;
389
389
  const { action } = config;
390
390
  const lang = headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru; // header might be empty string
391
391
  const ctx = parentCtx.create(`Gateway ${serviceName} ${actionName} [grpc]`, {
@@ -401,7 +401,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
401
401
  service: serviceName,
402
402
  action: actionName,
403
403
  requestTime: 0,
404
- requestId: actionConfig.requestId,
404
+ requestId,
405
405
  requestMethod: action,
406
406
  requestUrl: config.protoKey,
407
407
  traceId: ((_a = ctx.getTraceId) === null || _a === void 0 ? void 0 : _a.call(ctx)) || '',
@@ -414,11 +414,19 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
414
414
  'x-request-id': requestId,
415
415
  'x-gateway-version': constants_1.VERSION,
416
416
  };
417
- if ('protoPath' in config) {
418
- debugHeaders['x-api-request-protopath'] = config.protoPath;
417
+ const protopath = 'protoPath' in config ? config.protoPath : undefined;
418
+ if (protopath) {
419
+ debugHeaders['x-api-request-protopath'] = protopath;
419
420
  }
421
+ let proxyHeadersExtra;
422
+ const proxyHeadersCaller = (proxyHeadersFunc) => {
423
+ if (proxyHeadersExtra === undefined) {
424
+ proxyHeadersExtra = (0, common_2.getProxyHeadersArgs)(serviceName, actionName, config);
425
+ }
426
+ return proxyHeadersFunc(Object.assign({}, headers), 'rest', proxyHeadersExtra);
427
+ };
420
428
  if (typeof options.proxyDebugHeaders === 'function') {
421
- Object.assign(debugHeaders, options.proxyDebugHeaders(Object.assign({}, headers), 'grpc'));
429
+ Object.assign(debugHeaders, proxyHeadersCaller(options.proxyDebugHeaders));
422
430
  }
423
431
  else if (Array.isArray(options.proxyDebugHeaders)) {
424
432
  for (const headerName of options.proxyDebugHeaders) {
@@ -461,6 +469,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
461
469
  (0, common_2.handleError)(ErrorConstructor, error, ctx, 'getService failed');
462
470
  throw error;
463
471
  }
472
+ let stopListeningForAbort = null;
464
473
  // eslint-disable-next-line complexity
465
474
  return new Promise((resolve, reject) => {
466
475
  var _a, _b, _c, _d;
@@ -505,6 +514,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
505
514
  config,
506
515
  params,
507
516
  serviceName,
517
+ proxyHeadersCaller,
508
518
  ctx,
509
519
  });
510
520
  if (!service[action]) {
@@ -522,6 +532,12 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
522
532
  });
523
533
  const actionCall = service[action].bind(service);
524
534
  const stream = actionCall(body, serviceMetadata, serviceOptions);
535
+ stopListeningForAbort = (0, grpc_1.listenForAbort)({
536
+ signal: abortSignal,
537
+ config,
538
+ call: stream,
539
+ reject,
540
+ });
525
541
  stream.on('error', (error) => {
526
542
  ctx.log('ServerStream error', {
527
543
  debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
@@ -532,6 +548,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
532
548
  ctx.log('ServerStream status changed', status);
533
549
  });
534
550
  stream.on('end', () => {
551
+ stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
535
552
  ctx.log('ServerStream request completed', {
536
553
  debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
537
554
  });
@@ -553,6 +570,15 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
553
570
  }
554
571
  const actionCall = service[action].bind(service);
555
572
  const stream = actionCall(serviceMetadata, serviceOptions, actionConfig.callback);
573
+ stopListeningForAbort = (0, grpc_1.listenForAbort)({
574
+ signal: abortSignal,
575
+ config,
576
+ call: stream,
577
+ reject,
578
+ });
579
+ stream.once('end', () => {
580
+ stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
581
+ });
556
582
  resolve({ debugHeaders, stream });
557
583
  return;
558
584
  }
@@ -562,6 +588,12 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
562
588
  });
563
589
  const actionCall = service[action].bind(service);
564
590
  const stream = actionCall(serviceMetadata, serviceOptions);
591
+ stopListeningForAbort = (0, grpc_1.listenForAbort)({
592
+ signal: abortSignal,
593
+ config,
594
+ call: stream,
595
+ reject,
596
+ });
565
597
  stream.on('error', (error) => {
566
598
  ctx.log('BidiStream error', {
567
599
  debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
@@ -572,6 +604,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
572
604
  ctx.log('BidiStream status changed', status);
573
605
  });
574
606
  stream.on('end', () => {
607
+ stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
575
608
  ctx.log('BidiStream request completed', {
576
609
  debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
577
610
  });
@@ -619,12 +652,14 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
619
652
  (0, common_2.handleError)(ErrorConstructor, error, ctx, 'getService failed');
620
653
  throw error;
621
654
  }
655
+ stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
622
656
  // Update service
623
657
  actionCall = service[action].bind(service);
624
658
  callAction();
625
659
  return;
626
660
  }
627
661
  if (error) {
662
+ stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
628
663
  reject(new parse_error_1.GrpcError('gRPC request error', (0, parse_error_1.parseGrpcError)(error, root, lang, config.decodeAnyMessageProtoLoaderOptions), error));
629
664
  return;
630
665
  }
@@ -660,11 +695,18 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
660
695
  debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
661
696
  });
662
697
  ctx.end();
698
+ stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
663
699
  return resolve({ responseData, responseHeaders, debugHeaders });
664
700
  });
665
701
  call.on('status', (status) => {
666
702
  trailingMetadata = status.metadata.toJSON();
667
703
  });
704
+ stopListeningForAbort = (0, grpc_1.listenForAbort)({
705
+ signal: abortSignal,
706
+ config,
707
+ call,
708
+ reject,
709
+ });
668
710
  };
669
711
  callAction();
670
712
  }
@@ -672,6 +714,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
672
714
  }).catch((error) => {
673
715
  const grpcError = (0, parse_error_1.isGrpcError)(error) ? error : (0, parse_error_1.grpcErrorFactory)(error);
674
716
  processError(grpcError);
717
+ stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
675
718
  return Promise.reject({ error: grpcError.getGatewayError(), debugHeaders });
676
719
  });
677
720
  };
@@ -28,7 +28,7 @@ function createMixedAction(config, api, serviceName, actionName, extra, ErrorCon
28
28
  });
29
29
  const contextApi = (0, create_context_api_1.generateContextApi)(api, Object.assign(Object.assign({}, context), { ctx }));
30
30
  try {
31
- const responseData = await config(contextApi, args, Object.assign({ headers: actionConfig.headers, lang: actionConfig.headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru, ctx }, extra));
31
+ const responseData = await config(contextApi, args, Object.assign(Object.assign({ headers: actionConfig.headers, lang: actionConfig.headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru, ctx }, extra), { abortSignal: actionConfig.abortSignal }));
32
32
  ctx.log('Request completed');
33
33
  return {
34
34
  responseData,
@@ -40,7 +40,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
40
40
  /* eslint-disable complexity */
41
41
  return async function action(actionConfig) {
42
42
  var _a, _b, _c, _d, _e, _f, _g, _h;
43
- const { args, requestId, headers: requestHeaders, ctx: parentCtx, authArgs, userId, } = actionConfig;
43
+ const { args, requestId, headers: requestHeaders, ctx: parentCtx, authArgs, userId, abortSignal, } = actionConfig;
44
44
  const debugHeaders = {};
45
45
  const lang = requestHeaders[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru; // header might be empty string
46
46
  const serviceName = (options === null || options === void 0 ? void 0 : options.serviceName) || serviceKey;
@@ -113,14 +113,21 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
113
113
  'accept-language': lang,
114
114
  'x-gateway-version': constants_1.VERSION,
115
115
  };
116
+ let proxyHeadersExtra;
117
+ const proxyHeadersCaller = (proxyHeadersFunc) => {
118
+ if (proxyHeadersExtra === undefined) {
119
+ proxyHeadersExtra = (0, common_1.getProxyHeadersArgs)(serviceName, actionName);
120
+ }
121
+ return proxyHeadersFunc(Object.assign({}, requestHeaders), 'rest', proxyHeadersExtra);
122
+ };
116
123
  if (typeof options.proxyHeaders === 'function') {
117
- Object.assign(actionHeaders, options.proxyHeaders(Object.assign({}, requestHeaders), 'rest'));
124
+ Object.assign(actionHeaders, proxyHeadersCaller(options.proxyHeaders));
118
125
  }
119
126
  else if (Array.isArray(options.proxyHeaders)) {
120
127
  proxyHeaders.push(...options.proxyHeaders);
121
128
  }
122
129
  if (typeof config.proxyHeaders === 'function') {
123
- Object.assign(actionHeaders, config.proxyHeaders(Object.assign({}, requestHeaders), 'rest'));
130
+ Object.assign(actionHeaders, proxyHeadersCaller(config.proxyHeaders));
124
131
  }
125
132
  else if (Array.isArray(config.proxyHeaders)) {
126
133
  proxyHeaders.push(...config.proxyHeaders);
@@ -185,7 +192,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
185
192
  debugHeaders['x-api-content-type'] = headers['content-type'];
186
193
  }
187
194
  if (typeof options.proxyDebugHeaders === 'function') {
188
- Object.assign(debugHeaders, options.proxyDebugHeaders(Object.assign({}, requestHeaders), 'rest'));
195
+ Object.assign(debugHeaders, proxyHeadersCaller(options.proxyDebugHeaders));
189
196
  }
190
197
  else if (Array.isArray(options.proxyDebugHeaders)) {
191
198
  for (const headerName of options.proxyDebugHeaders) {
@@ -220,6 +227,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
220
227
  params: query,
221
228
  headers: ctx ? Object.assign(Object.assign({}, ctx.getMetadata()), headers) : headers,
222
229
  maxRedirects: config.maxRedirects,
230
+ signal: config.abortOnClientDisconnect ? abortSignal : undefined,
223
231
  };
224
232
  if (config.paramsSerializer) {
225
233
  Object.assign(requestConfig, {
package/build/index.js CHANGED
@@ -176,6 +176,11 @@ function generateGatewayApiController(schemasByScope, Api, config, controllerAct
176
176
  }
177
177
  const args = req.method === 'GET' ? req.query : req.body;
178
178
  try {
179
+ const abortController = new AbortController();
180
+ const handleCloseConnection = () => {
181
+ abortController.abort();
182
+ };
183
+ req.connection.once('close', handleCloseConnection);
179
184
  const apiAction = Api[scope][service][action];
180
185
  if (onBeforeAction) {
181
186
  const actionConfig = (_c = (_b = (_a = schemasByScope[scope]) === null || _a === void 0 ? void 0 : _a[service]) === null || _b === void 0 ? void 0 : _b.actions) === null || _c === void 0 ? void 0 : _c[action];
@@ -194,7 +199,9 @@ function generateGatewayApiController(schemasByScope, Api, config, controllerAct
194
199
  args,
195
200
  authArgs: config.getAuthArgs(req, res),
196
201
  userId,
202
+ abortSignal: abortController.signal,
197
203
  });
204
+ req.connection.removeListener('close', handleCloseConnection);
198
205
  if (withDebugHeaders) {
199
206
  res.set(debugHeaders);
200
207
  }
@@ -32,6 +32,7 @@ export interface ApiActionConfig<Context extends GatewayContext, TRequestData, T
32
32
  callback?: (response: TResponseData) => void;
33
33
  authArgs?: Record<string, unknown>;
34
34
  userId?: string;
35
+ abortSignal?: AbortSignal;
35
36
  }
36
37
  export interface GRPCActionData {
37
38
  [key: string]: unknown;
@@ -67,7 +68,13 @@ export interface GatewayError {
67
68
  }
68
69
  export type GrpcRetryCondition = (error: ServiceError) => boolean;
69
70
  export type AxiosRetryCondition = IAxiosRetryConfig['retryCondition'];
70
- export type ProxyHeadersFunction = (headers: IncomingHttpHeaders, type: ControllerType) => IncomingHttpHeaders;
71
+ export type ProxyHeadersFunctionExtra = {
72
+ service: string;
73
+ action: string;
74
+ protopath?: string;
75
+ protokey?: string;
76
+ };
77
+ export type ProxyHeadersFunction = (headers: IncomingHttpHeaders, type: ControllerType, extra: ProxyHeadersFunctionExtra) => IncomingHttpHeaders;
71
78
  export type ProxyHeaders = string[] | ProxyHeadersFunction;
72
79
  export type ProxyResponseHeadersFunction = (headers: Headers, type: ControllerType) => Headers;
73
80
  export type ProxyResponseHeaders = string[] | ProxyResponseHeadersFunction;
@@ -131,6 +138,7 @@ export interface ApiServiceBaseActionConfig<Context extends GatewayContext, TOut
131
138
  proxyHeaders?: ProxyHeaders;
132
139
  proxyResponseHeaders?: ProxyResponseHeaders;
133
140
  metadata?: Record<string, string | number | boolean>;
141
+ abortOnClientDisconnect?: boolean;
134
142
  }
135
143
  export interface ApiServiceRestActionConfig<Context extends GatewayContext, TOutput, TParams = undefined, TTransformed = TOutput> extends ApiServiceBaseActionConfig<Context, TOutput, TParams, TTransformed> {
136
144
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
@@ -168,6 +176,7 @@ export interface ApiServiceMixedExtra<Context extends GatewayContext, Req extend
168
176
  ctx: Context;
169
177
  config: GatewayConfig<Context, Req, Res>;
170
178
  grpcContext: GrpcContext;
179
+ abortSignal?: AbortSignal;
171
180
  }
172
181
  export type ApiServiceMixedActionConfig<Context extends GatewayContext, Req extends GatewayRequest<Context>, Res extends GatewayResponse, TOutput, TParams = undefined, TTransformed = TOutput> = (api: unknown, args: TParams, extra: ApiServiceMixedExtra<Context, Req, Res>) => Promise<TTransformed>;
173
182
  export type ApiServiceActionConfig<Context extends GatewayContext, Req extends GatewayRequest<Context>, Res extends GatewayResponse, TOutput, TParams = undefined, TTransformed = TOutput> = ApiServiceRestActionConfig<Context, TOutput, TParams, TTransformed> | ApiServiceGrpcActionConfig<Context, TOutput, TParams, TTransformed> | ApiServiceMixedActionConfig<Context, Req, Res, TOutput, TParams, TTransformed>;
@@ -1,6 +1,6 @@
1
1
  import * as grpc from '@grpc/grpc-js';
2
2
  import _ from 'lodash';
3
- import { ActionEndpoint, ExtendedActionEndpoint, ExtendedGrpcActionEndpoint, ExtendedRestActionEndpoint, Headers } from '../models/common';
3
+ import { ActionEndpoint, ApiServiceGrpcActionConfig, ExtendedActionEndpoint, ExtendedGrpcActionEndpoint, ExtendedRestActionEndpoint, Headers, ProxyHeadersFunctionExtra } from '../models/common';
4
4
  import { Dict, GatewayContext } from '../models/context';
5
5
  import { AppErrorConstructor } from '../models/error';
6
6
  export declare function isExtendedActionEndpoint(endpoint: ActionEndpoint): endpoint is ExtendedActionEndpoint;
@@ -13,3 +13,4 @@ export declare function getKeys<T extends object>(obj: T): (keyof T)[];
13
13
  export declare function sanitizeDebugHeaders(debugHeaders: Headers): _.Omit<Headers, "x-api-request-body">;
14
14
  export declare function getHeadersFromMetadata(metadata: Record<string, grpc.MetadataValue[]>, prefix?: string): Record<string, string>;
15
15
  export declare function handleError<Context extends GatewayContext>(ErrorConstructor: AppErrorConstructor, error: unknown, ctx: Context, message: string, extra?: Dict): void;
16
+ export declare const getProxyHeadersArgs: <Context extends GatewayContext>(serviceName: string, actionName: string, grpcConfig?: ApiServiceGrpcActionConfig<Context, any, any> | undefined) => ProxyHeadersFunctionExtra;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.handleError = exports.getHeadersFromMetadata = exports.sanitizeDebugHeaders = exports.getKeys = exports.isExtendedRestActionEndpoint = exports.isExtendedGrpcActionEndpoint = exports.isExtendedActionEndpoint = void 0;
6
+ exports.getProxyHeadersArgs = exports.handleError = exports.getHeadersFromMetadata = exports.sanitizeDebugHeaders = exports.getKeys = exports.isExtendedRestActionEndpoint = exports.isExtendedGrpcActionEndpoint = exports.isExtendedActionEndpoint = void 0;
7
7
  const lodash_1 = __importDefault(require("lodash"));
8
8
  function isExtendedActionEndpoint(endpoint) {
9
9
  return (endpoint === null || endpoint === void 0 ? void 0 : endpoint.path) !== undefined;
@@ -49,3 +49,14 @@ function handleError(ErrorConstructor, error, ctx, message, extra) {
49
49
  }
50
50
  }
51
51
  exports.handleError = handleError;
52
+ const getProxyHeadersArgs = (serviceName, actionName, grpcConfig) => {
53
+ const protopath = grpcConfig && 'protoPath' in grpcConfig ? grpcConfig.protoPath : undefined;
54
+ const protokey = grpcConfig === null || grpcConfig === void 0 ? void 0 : grpcConfig.protoKey;
55
+ return {
56
+ service: serviceName,
57
+ action: actionName,
58
+ protopath,
59
+ protokey,
60
+ };
61
+ };
62
+ exports.getProxyHeadersArgs = getProxyHeadersArgs;
@@ -1,5 +1,15 @@
1
1
  import * as grpc from '@grpc/grpc-js';
2
+ import { ClientDuplexStream, ClientReadableStream, ClientUnaryCall, ClientWritableStream } from '@grpc/grpc-js';
2
3
  import * as protobufjs from 'protobufjs';
3
4
  export declare function decodeAnyMessageRecursively(root: protobufjs.Root, message?: unknown, decodeAnyMessageProtoLoaderOptions?: protobufjs.IConversionOptions): unknown;
4
5
  export declare function isRetryableGrpcError(error?: grpc.ServiceError): boolean;
5
6
  export declare function isRecreateServiceError(error?: grpc.ServiceError): boolean;
7
+ export type ListenForAbortArgs = {
8
+ signal?: AbortSignal;
9
+ config: {
10
+ abortOnClientDisconnect?: boolean;
11
+ };
12
+ call: ClientUnaryCall | ClientReadableStream<unknown> | ClientWritableStream<unknown> | ClientDuplexStream<unknown, unknown>;
13
+ reject: (err: Error) => void;
14
+ };
15
+ export declare function listenForAbort({ signal, config, call, reject }: ListenForAbortArgs): () => void;
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  /* eslint-disable camelcase */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.isRecreateServiceError = exports.isRetryableGrpcError = exports.decodeAnyMessageRecursively = void 0;
4
+ exports.listenForAbort = exports.isRecreateServiceError = exports.isRetryableGrpcError = exports.decodeAnyMessageRecursively = void 0;
5
5
  const constants_1 = require("../constants");
6
+ const parse_error_1 = require("./parse-error");
6
7
  function isEncodedMessage(message) {
7
8
  return Boolean(message.type_url && message.value);
8
9
  }
@@ -54,3 +55,25 @@ function isRecreateServiceError(error) {
54
55
  return constants_1.RECREATE_SERVICE_CODES.includes(error.code);
55
56
  }
56
57
  exports.isRecreateServiceError = isRecreateServiceError;
58
+ function listenForAbort({ signal, config, call, reject }) {
59
+ if (!signal || !config.abortOnClientDisconnect) {
60
+ return () => null;
61
+ }
62
+ const handleAbortSignal = () => {
63
+ call.cancel();
64
+ reject(new parse_error_1.GrpcError('Request was cancelled.', {
65
+ status: 499,
66
+ code: 'REQUEST_WAS_CANCELLED',
67
+ message: 'Request was cancelled because the original connection was disconnected.',
68
+ }));
69
+ };
70
+ if (signal.aborted) {
71
+ handleAbortSignal();
72
+ return () => null;
73
+ }
74
+ signal.addEventListener('abort', handleAbortSignal);
75
+ return () => {
76
+ signal.removeEventListener('abort', handleAbortSignal);
77
+ };
78
+ }
79
+ exports.listenForAbort = listenForAbort;
@@ -116,6 +116,10 @@ function parseRestError(error, lang) {
116
116
  status = 504;
117
117
  description = lang === constants_1.Lang.Ru ? 'Превышено время ожидания ответа' : 'Timeout exceeded';
118
118
  }
119
+ else if (code === 'ERR_CANCELED') {
120
+ status = 499;
121
+ description = lang === constants_1.Lang.Ru ? 'Запрос был отменен.' : 'Request was cancelled.';
122
+ }
119
123
  else {
120
124
  status = 500;
121
125
  description =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/gateway",
3
- "version": "4.5.0",
3
+ "version": "4.7.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",