serverless-offline 8.5.0 → 8.6.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
@@ -244,7 +244,7 @@ to calling it via `aws-sdk`.
244
244
 
245
245
  ## The `process.env.IS_OFFLINE` variable
246
246
 
247
- Will be `"true"` in your handlers and thorough the plugin.
247
+ Will be `"true"` in your handlers and throughout the plugin.
248
248
 
249
249
  ## Docker and Layers
250
250
 
@@ -531,8 +531,6 @@ Where the `event` is received in the lambda handler function.
531
531
 
532
532
  There's support for [websocketsApiRouteSelectionExpression](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html) in it's basic form: `$request.body.x.y.z`, where the default value is `$request.body.action`.
533
533
 
534
- Authorizers and wss:// are currently not supported.
535
-
536
534
  ## Usage with Webpack
537
535
 
538
536
  Use [serverless-webpack](https://github.com/serverless-heaven/serverless-webpack) to compile and bundle your ES-next code
@@ -596,7 +594,54 @@ For each debug run:
596
594
 
597
595
  The system will start in wait status. This will also automatically start the chrome browser and wait for you to set breakpoints for inspection. Set the breakpoints as needed and, then, click the play button for the debugging to continue.
598
596
 
599
- Depending on the breakpoint, you may need to call the URL path for your function in seperate browser window for your serverless function to be run and made available for debugging.
597
+ Depending on the breakpoint, you may need to call the URL path for your function in separate browser window for your serverless function to be run and made available for debugging.
598
+
599
+ ### Interactive Debugging with Visual Studio Code (VSC)
600
+
601
+ With newer versions of node (6.3+) the node inspector is already part of your node environment and you can take advantage of debugging inside your IDE with source-map support. Here is the example configuration to debug interactively with VSC. It has two steps.
602
+
603
+ #### Step 1 : Adding a launch configuration in IDE
604
+
605
+ Add a new [launch configuration](https://code.visualstudio.com/docs/editor/debugging) to VSC like this:
606
+
607
+ ```json
608
+ {
609
+
610
+ "type": "node",
611
+ "request": "launch",
612
+ "name": "Debug Serverless Offline",
613
+ "cwd": "${workspaceFolder}",
614
+ "runtimeExecutable": "npm",
615
+ "runtimeArgs": [
616
+ "run",
617
+ "debug"
618
+ ],
619
+ "sourceMaps": true
620
+ }
621
+
622
+ ```
623
+
624
+ #### Step2 : Adding a debug script
625
+
626
+ You will also need to add a `debug` script reference in your `package.json file`
627
+
628
+ Add this to the `scripts` section:
629
+
630
+ > Unix/Mac: `"debug" : "export SLS_DEBUG=* && node --inspect /usr/local/bin/serverless offline"`
631
+
632
+ > Windows: `"debug": "SET SLS_DEBUG=* && node --inspect node_modules\\serverless\\bin\\serverless offline"`
633
+
634
+ Example:
635
+
636
+ ```json
637
+ ....
638
+ "scripts": {
639
+ "debug" : "SET SLS_DEBUG=* && node --inspect node_modules\\serverless\\bin\\serverless offline"
640
+ }
641
+ ```
642
+
643
+ In VSC, you can, then, add breakpoints to your code. To start a debug sessions you can either start your script in `package.json` by clicking the hovering debug intellisense icon or by going to your debug pane and selecting the Debug Serverless Offline configuration.
644
+
600
645
 
601
646
  ## Resource permissions and AWS profile
602
647
 
@@ -96,6 +96,10 @@ class ServerlessOffline {
96
96
  lifecycleEvents: ['init', 'ready', 'end'],
97
97
  options: _index2.commandOptions,
98
98
  usage: 'Simulates API Gateway to call your lambda functions offline using backward compatible initialization.'
99
+ },
100
+ functionsUpdated: {
101
+ type: 'entrypoint',
102
+ lifecycleEvents: ['cleanup']
99
103
  }
100
104
  },
101
105
  lifecycleEvents: ['start'],
@@ -106,6 +110,7 @@ class ServerlessOffline {
106
110
  this.hooks = {
107
111
  'offline:start:init': this.start.bind(this),
108
112
  'offline:start:ready': this.ready.bind(this),
113
+ 'offline:functionsUpdated:cleanup': this.cleanupFunctions.bind(this),
109
114
  'offline:start': this._startWithExplicitEnd.bind(this),
110
115
  'offline:start:end': this.end.bind(this)
111
116
  };
@@ -203,6 +208,13 @@ class ServerlessOffline {
203
208
  process.exit(0);
204
209
  }
205
210
  }
211
+
212
+ async cleanupFunctions() {
213
+ if (_classPrivateFieldLooseBase(this, _lambda)[_lambda]) {
214
+ (0, _serverlessLog.default)('Forcing cleanup of Lambda functions');
215
+ await _classPrivateFieldLooseBase(this, _lambda)[_lambda].cleanup();
216
+ }
217
+ }
206
218
  /**
207
219
  * Entry point for the plugin (sls offline) when running 'sls offline'
208
220
  * The call to this.end() would terminate the process before 'offline:start:end' could be consumed
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = authFunctionNameExtractor;
7
7
 
8
- var _serverlessLog2 = _interopRequireDefault(require("../../serverlessLog.js"));
8
+ var _serverlessLog2 = _interopRequireDefault(require("../serverlessLog.js"));
9
9
 
10
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
11
 
@@ -17,7 +17,7 @@ var _hapi = require("@hapi/hapi");
17
17
 
18
18
  var _module = require("module");
19
19
 
20
- var _authFunctionNameExtractor = _interopRequireDefault(require("./authFunctionNameExtractor.js"));
20
+ var _authFunctionNameExtractor = _interopRequireDefault(require("../authFunctionNameExtractor.js"));
21
21
 
22
22
  var _authJWTSettingsExtractor = _interopRequireDefault(require("./authJWTSettingsExtractor.js"));
23
23
 
@@ -7,7 +7,7 @@ exports.default = createAuthScheme;
7
7
 
8
8
  var _boom = _interopRequireDefault(require("@hapi/boom"));
9
9
 
10
- var _authCanExecuteResource = _interopRequireDefault(require("./authCanExecuteResource.js"));
10
+ var _authCanExecuteResource = _interopRequireDefault(require("../authCanExecuteResource.js"));
11
11
 
12
12
  var _debugLog = _interopRequireDefault(require("../../debugLog.js"));
13
13
 
@@ -169,7 +169,13 @@ class LambdaProxyIntegrationEventV2 {
169
169
  const httpMethod = method.toUpperCase();
170
170
  const requestTime = (0, _index.formatToClfTime)(received);
171
171
  const requestTimeEpoch = received;
172
- const cookies = Object.entries(_classPrivateFieldLooseBase(this, _request)[_request].state).map(([key, value]) => `${key}=${value}`);
172
+ const cookies = Object.entries(_classPrivateFieldLooseBase(this, _request)[_request].state).flatMap(([key, value]) => {
173
+ if (Array.isArray(value)) {
174
+ return value.map(v => `${key}=${v}`);
175
+ }
176
+
177
+ return `${key}=${value}`;
178
+ });
173
179
  return {
174
180
  version: '2.0',
175
181
  routeKey: _classPrivateFieldLooseBase(this, _routeKey)[_routeKey],
@@ -17,6 +17,10 @@ var _index2 = require("../../config/index.js");
17
17
 
18
18
  var _index3 = require("../../utils/index.js");
19
19
 
20
+ var _authFunctionNameExtractor = _interopRequireDefault(require("../authFunctionNameExtractor.js"));
21
+
22
+ var _authCanExecuteResource = _interopRequireDefault(require("../authCanExecuteResource.js"));
23
+
20
24
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
25
 
22
26
  function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
@@ -36,8 +40,14 @@ var _lambda = /*#__PURE__*/_classPrivateFieldLooseKey("lambda");
36
40
 
37
41
  var _options = /*#__PURE__*/_classPrivateFieldLooseKey("options");
38
42
 
43
+ var _serverless = /*#__PURE__*/_classPrivateFieldLooseKey("serverless");
44
+
39
45
  var _webSocketRoutes = /*#__PURE__*/_classPrivateFieldLooseKey("webSocketRoutes");
40
46
 
47
+ var _webSocketAuthorizers = /*#__PURE__*/_classPrivateFieldLooseKey("webSocketAuthorizers");
48
+
49
+ var _webSocketAuthorizersCache = /*#__PURE__*/_classPrivateFieldLooseKey("webSocketAuthorizersCache");
50
+
41
51
  var _websocketsApiRouteSelectionExpression = /*#__PURE__*/_classPrivateFieldLooseKey("websocketsApiRouteSelectionExpression");
42
52
 
43
53
  var _idleTimeouts = /*#__PURE__*/_classPrivateFieldLooseKey("idleTimeouts");
@@ -58,10 +68,22 @@ class WebSocketClients {
58
68
  writable: true,
59
69
  value: null
60
70
  });
71
+ Object.defineProperty(this, _serverless, {
72
+ writable: true,
73
+ value: null
74
+ });
61
75
  Object.defineProperty(this, _webSocketRoutes, {
62
76
  writable: true,
63
77
  value: new Map()
64
78
  });
79
+ Object.defineProperty(this, _webSocketAuthorizers, {
80
+ writable: true,
81
+ value: new Map()
82
+ });
83
+ Object.defineProperty(this, _webSocketAuthorizersCache, {
84
+ writable: true,
85
+ value: new Map()
86
+ });
65
87
  Object.defineProperty(this, _websocketsApiRouteSelectionExpression, {
66
88
  writable: true,
67
89
  value: null
@@ -76,6 +98,7 @@ class WebSocketClients {
76
98
  });
77
99
  _classPrivateFieldLooseBase(this, _lambda)[_lambda] = lambda;
78
100
  _classPrivateFieldLooseBase(this, _options)[_options] = options;
101
+ _classPrivateFieldLooseBase(this, _serverless)[_serverless] = serverless;
79
102
  _classPrivateFieldLooseBase(this, _websocketsApiRouteSelectionExpression)[_websocketsApiRouteSelectionExpression] = serverless.service.provider.websocketsApiRouteSelectionExpression || _index2.DEFAULT_WEBSOCKETS_API_ROUTE_SELECTION_EXPRESSION;
80
103
 
81
104
  if (v3Utils) {
@@ -161,7 +184,9 @@ class WebSocketClients {
161
184
  }
162
185
 
163
186
  async verifyClient(connectionId, request) {
164
- const route = _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].get('$connect');
187
+ const routeName = '$connect';
188
+
189
+ const route = _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].get(routeName);
165
190
 
166
191
  if (!route) {
167
192
  return {
@@ -172,6 +197,96 @@ class WebSocketClients {
172
197
 
173
198
  const connectEvent = new _index.WebSocketConnectEvent(connectionId, request, _classPrivateFieldLooseBase(this, _options)[_options]).create();
174
199
 
200
+ const authFunName = _classPrivateFieldLooseBase(this, _webSocketAuthorizers)[_webSocketAuthorizers].get(routeName);
201
+
202
+ if (authFunName) {
203
+ const authorizerFunction = _classPrivateFieldLooseBase(this, _lambda)[_lambda].get(authFunName);
204
+
205
+ const authorizeEvent = new _index.WebSocketAuthorizerEvent(connectionId, request, _classPrivateFieldLooseBase(this, _serverless)[_serverless].service.provider, _classPrivateFieldLooseBase(this, _options)[_options]).create();
206
+ authorizerFunction.setEvent(authorizeEvent);
207
+
208
+ if (this.log) {
209
+ this.log.notice();
210
+ this.log.notice(`Running Authorization function for ${routeName} (λ: ${authFunName})`);
211
+ } else {
212
+ console.log(''); // Just to make things a little pretty
213
+
214
+ (0, _serverlessLog.default)(`Running Authorization function for ${routeName} (λ: ${authFunName})`);
215
+ }
216
+
217
+ try {
218
+ const result = await authorizerFunction.runHandler();
219
+ if (result === 'Unauthorized') return {
220
+ verified: false,
221
+ statusCode: 401
222
+ };
223
+ const policy = result; // Validate that the policy document has the principalId set
224
+
225
+ if (!policy.principalId) {
226
+ if (this.log) {
227
+ this.log.notice(`Authorization response did not include a principalId: (λ: ${authFunName})`);
228
+ } else {
229
+ (0, _serverlessLog.default)(`Authorization response did not include a principalId: (λ: ${authFunName})`);
230
+ }
231
+
232
+ return {
233
+ verified: false,
234
+ statusCode: 403
235
+ };
236
+ }
237
+
238
+ if (!(0, _authCanExecuteResource.default)(policy.policyDocument, authorizeEvent.methodArn)) {
239
+ if (this.log) {
240
+ this.log.notice(`Authorization response didn't authorize user to access resource: (λ: ${authFunName})`);
241
+ } else {
242
+ (0, _serverlessLog.default)(`Authorization response didn't authorize user to access resource: (λ: ${authFunName})`);
243
+ }
244
+
245
+ return {
246
+ verified: false,
247
+ statusCode: 403
248
+ };
249
+ }
250
+
251
+ if (this.log) {
252
+ this.log.notice(`Authorization function returned a successful response: (λ: ${authFunName})`);
253
+ } else {
254
+ (0, _serverlessLog.default)(`Authorization function returned a successful response: (λ: ${authFunName})`);
255
+ }
256
+
257
+ _classPrivateFieldLooseBase(this, _webSocketAuthorizersCache)[_webSocketAuthorizersCache].set(connectionId, {
258
+ identity: {
259
+ apiKey: policy.usageIdentifierKey,
260
+ sourceIp: authorizeEvent.requestContext.sourceIp,
261
+ userAgent: authorizeEvent.headers['user-agent'] || ''
262
+ },
263
+ authorizer: {
264
+ integrationLatency: '42',
265
+ principalId: policy.principalId,
266
+ ...policy.context
267
+ }
268
+ });
269
+ } catch (err) {
270
+ if (this.log) {
271
+ this.log.debug(`Error in route handler '${routeName}' authorizer`, err);
272
+ } else {
273
+ (0, _debugLog.default)(`Error in route handler '${routeName}' authorizer`, err);
274
+ }
275
+
276
+ return {
277
+ verified: false,
278
+ statusCode: 500
279
+ };
280
+ }
281
+ }
282
+
283
+ const authorizerData = _classPrivateFieldLooseBase(this, _webSocketAuthorizersCache)[_webSocketAuthorizersCache].get(connectionId);
284
+
285
+ if (authorizerData) {
286
+ connectEvent.identity = authorizerData.identity;
287
+ connectEvent.authorizer = authorizerData.authorizer;
288
+ }
289
+
175
290
  const lambdaFunction = _classPrivateFieldLooseBase(this, _lambda)[_lambda].get(route.functionKey);
176
291
 
177
292
  lambdaFunction.setEvent(connectEvent);
@@ -186,6 +301,8 @@ class WebSocketClients {
186
301
  statusCode
187
302
  };
188
303
  } catch (err) {
304
+ _classPrivateFieldLooseBase(this, _webSocketAuthorizersCache)[_webSocketAuthorizersCache].delete(connectionId);
305
+
189
306
  if (this.log) {
190
307
  this.log.debug(`Error in route handler '${route.functionKey}'`, err);
191
308
  } else {
@@ -290,7 +407,14 @@ class WebSocketClients {
290
407
 
291
408
  this._clearIdleTimeout(webSocketClient);
292
409
 
293
- this._processEvent(webSocketClient, connectionId, '$disconnect', disconnectEvent);
410
+ const authorizerData = _classPrivateFieldLooseBase(this, _webSocketAuthorizersCache)[_webSocketAuthorizersCache].get(connectionId);
411
+
412
+ if (authorizerData) {
413
+ disconnectEvent.identity = authorizerData.identity;
414
+ disconnectEvent.authorizer = authorizerData.authorizer;
415
+ }
416
+
417
+ this._processEvent(webSocketClient, connectionId, '$disconnect', disconnectEvent).finally(() => _classPrivateFieldLooseBase(this, _webSocketAuthorizersCache)[_webSocketAuthorizersCache].delete(connectionId));
294
418
  });
295
419
  webSocketClient.on('message', message => {
296
420
  if (this.log) {
@@ -309,12 +433,76 @@ class WebSocketClients {
309
433
 
310
434
  const event = new _index.WebSocketEvent(connectionId, route, message).create();
311
435
 
436
+ const authorizerData = _classPrivateFieldLooseBase(this, _webSocketAuthorizersCache)[_webSocketAuthorizersCache].get(connectionId);
437
+
438
+ if (authorizerData) {
439
+ event.identity = authorizerData.identity;
440
+ event.authorizer = authorizerData.authorizer;
441
+ }
442
+
312
443
  this._onWebSocketUsed(connectionId);
313
444
 
314
445
  this._processEvent(webSocketClient, connectionId, route, event);
315
446
  });
316
447
  }
317
448
 
449
+ _extractAuthFunctionName(endpoint) {
450
+ if (typeof endpoint.authorizer === 'object' && endpoint.authorizer.type && endpoint.authorizer.type.toUpperCase() === 'TOKEN') {
451
+ if (this.log) {
452
+ this.log.debug(`Websockets does not support the TOKEN authorization type`);
453
+ } else {
454
+ (0, _debugLog.default)(`WARNING: Websockets does not support the TOKEN authorization type`);
455
+ }
456
+
457
+ return null;
458
+ }
459
+
460
+ const result = (0, _authFunctionNameExtractor.default)(endpoint, null, this);
461
+ return result.unsupportedAuth ? null : result.authorizerName;
462
+ }
463
+
464
+ _configureAuthorization(endpoint, functionKey) {
465
+ if (!endpoint.authorizer) {
466
+ return;
467
+ }
468
+
469
+ if (endpoint.route === '$connect') {
470
+ const authFunctionName = this._extractAuthFunctionName(endpoint);
471
+
472
+ if (!authFunctionName) {
473
+ return;
474
+ }
475
+
476
+ if (this.log) {
477
+ this.log.notice(`Configuring Authorization: ${functionKey} ${authFunctionName}`);
478
+ } else {
479
+ (0, _serverlessLog.default)(`Configuring Authorization: ${functionKey} ${authFunctionName}`);
480
+ }
481
+
482
+ const authFunction = _classPrivateFieldLooseBase(this, _serverless)[_serverless].service.getFunction(authFunctionName);
483
+
484
+ if (!authFunction) {
485
+ if (this.log) {
486
+ this.log.error(`Authorization function ${authFunctionName} does not exist`);
487
+ } else {
488
+ (0, _serverlessLog.default)(`WARNING: Authorization function ${authFunctionName} does not exist`);
489
+ }
490
+
491
+ return;
492
+ }
493
+
494
+ _classPrivateFieldLooseBase(this, _webSocketAuthorizers)[_webSocketAuthorizers].set(endpoint.route, authFunctionName);
495
+
496
+ return;
497
+ }
498
+
499
+ if (this.log) {
500
+ this.log.notice(`Configuring Authorization is supported only on $connect route`);
501
+ } else {
502
+ (0, _serverlessLog.default)(`Configuring Authorization is supported only on $connect route`);
503
+ }
504
+ }
505
+
318
506
  addRoute(functionKey, definition) {
319
507
  // set the route name
320
508
  _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].set(definition.route, {
@@ -322,6 +510,10 @@ class WebSocketClients {
322
510
  definition
323
511
  });
324
512
 
513
+ if (!_classPrivateFieldLooseBase(this, _options)[_options].noAuth) {
514
+ this._configureAuthorization(definition, functionKey);
515
+ }
516
+
325
517
  if (this.log) {
326
518
  this.log.notice(`route '${definition}'`);
327
519
  } else {
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _WebSocketRequestContext = _interopRequireDefault(require("./WebSocketRequestContext.js"));
9
+
10
+ var _index = require("../../../utils/index.js");
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
15
+
16
+ var id = 0;
17
+
18
+ function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
19
+
20
+ var _connectionId = /*#__PURE__*/_classPrivateFieldLooseKey("connectionId");
21
+
22
+ var _httpsProtocol = /*#__PURE__*/_classPrivateFieldLooseKey("httpsProtocol");
23
+
24
+ var _rawHeaders = /*#__PURE__*/_classPrivateFieldLooseKey("rawHeaders");
25
+
26
+ var _url = /*#__PURE__*/_classPrivateFieldLooseKey("url");
27
+
28
+ var _websocketPort = /*#__PURE__*/_classPrivateFieldLooseKey("websocketPort");
29
+
30
+ var _provider = /*#__PURE__*/_classPrivateFieldLooseKey("provider");
31
+
32
+ class WebSocketAuthorizerEvent {
33
+ constructor(connectionId, request, provider, options) {
34
+ Object.defineProperty(this, _connectionId, {
35
+ writable: true,
36
+ value: null
37
+ });
38
+ Object.defineProperty(this, _httpsProtocol, {
39
+ writable: true,
40
+ value: null
41
+ });
42
+ Object.defineProperty(this, _rawHeaders, {
43
+ writable: true,
44
+ value: null
45
+ });
46
+ Object.defineProperty(this, _url, {
47
+ writable: true,
48
+ value: null
49
+ });
50
+ Object.defineProperty(this, _websocketPort, {
51
+ writable: true,
52
+ value: null
53
+ });
54
+ Object.defineProperty(this, _provider, {
55
+ writable: true,
56
+ value: null
57
+ });
58
+ const {
59
+ httpsProtocol,
60
+ websocketPort
61
+ } = options;
62
+ const {
63
+ rawHeaders,
64
+ url
65
+ } = request;
66
+ _classPrivateFieldLooseBase(this, _connectionId)[_connectionId] = connectionId;
67
+ _classPrivateFieldLooseBase(this, _httpsProtocol)[_httpsProtocol] = httpsProtocol;
68
+ _classPrivateFieldLooseBase(this, _rawHeaders)[_rawHeaders] = rawHeaders;
69
+ _classPrivateFieldLooseBase(this, _url)[_url] = url;
70
+ _classPrivateFieldLooseBase(this, _websocketPort)[_websocketPort] = websocketPort;
71
+ _classPrivateFieldLooseBase(this, _provider)[_provider] = provider;
72
+ }
73
+
74
+ create() {
75
+ const headers = (0, _index.parseHeaders)(_classPrivateFieldLooseBase(this, _rawHeaders)[_rawHeaders]);
76
+ const multiValueHeaders = (0, _index.parseMultiValueHeaders)(_classPrivateFieldLooseBase(this, _rawHeaders)[_rawHeaders]);
77
+ const multiValueQueryStringParameters = (0, _index.parseMultiValueQueryStringParameters)(_classPrivateFieldLooseBase(this, _url)[_url]);
78
+ const queryStringParameters = (0, _index.parseQueryStringParameters)(_classPrivateFieldLooseBase(this, _url)[_url]);
79
+ const requestContext = new _WebSocketRequestContext.default('CONNECT', '$connect', _classPrivateFieldLooseBase(this, _connectionId)[_connectionId]).create();
80
+ return {
81
+ type: 'REQUEST',
82
+ methodArn: `arn:aws:execute-api:${_classPrivateFieldLooseBase(this, _provider)[_provider].region}:${requestContext.accountId}:${requestContext.apiId}/${requestContext.stage}/${requestContext.routeKey}`,
83
+ headers,
84
+ multiValueHeaders,
85
+ // NOTE: multiValueQueryStringParameters and queryStringParameters
86
+ // properties are only defined if they have values
87
+ ...(multiValueQueryStringParameters && {
88
+ multiValueQueryStringParameters
89
+ }),
90
+ ...(queryStringParameters && {
91
+ queryStringParameters
92
+ }),
93
+ requestContext
94
+ };
95
+ }
96
+
97
+ }
98
+
99
+ exports.default = WebSocketAuthorizerEvent;
@@ -3,6 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "WebSocketAuthorizerEvent", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _WebSocketAuthorizerEvent.default;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "WebSocketConnectEvent", {
7
13
  enumerable: true,
8
14
  get: function () {
@@ -28,4 +34,6 @@ var _WebSocketDisconnectEvent = _interopRequireDefault(require("./WebSocketDisco
28
34
 
29
35
  var _WebSocketEvent = _interopRequireDefault(require("./WebSocketEvent.js"));
30
36
 
37
+ var _WebSocketAuthorizerEvent = _interopRequireDefault(require("./WebSocketAuthorizerEvent.js"));
38
+
31
39
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -177,7 +177,7 @@ class LambdaFunction {
177
177
  name,
178
178
  package: functionPackage = {}
179
179
  } = functionDefinition;
180
- const [handlerPath, handlerName] = (0, _index3.splitHandlerPathAndName)(handler);
180
+ const [handlerPath, handlerName, handlerModuleNesting] = (0, _index3.splitHandlerPathAndName)(handler);
181
181
  const memorySize = functionDefinition.memorySize || provider.memorySize || _index2.DEFAULT_LAMBDA_MEMORY_SIZE;
182
182
  const runtime = functionDefinition.runtime || provider.runtime || _index2.DEFAULT_LAMBDA_RUNTIME;
183
183
  const timeout = (functionDefinition.timeout || provider.timeout || _index2.DEFAULT_LAMBDA_TIMEOUT) * 1000; // this._executionTimeout = null
@@ -212,6 +212,7 @@ class LambdaFunction {
212
212
  functionKey,
213
213
  handler,
214
214
  handlerName,
215
+ handlerModuleNesting,
215
216
  codeDir: _classPrivateFieldLooseBase(this, _codeDir)[_codeDir],
216
217
  handlerPath: (0, _path.resolve)(_classPrivateFieldLooseBase(this, _codeDir)[_codeDir], handlerPath),
217
218
  runtime,
@@ -269,7 +270,7 @@ class LambdaFunction {
269
270
  LAMBDA_RUNTIME_DIR: '/var/runtime',
270
271
  LAMBDA_TASK_ROOT: '/var/task',
271
272
  LANG: 'en_US.UTF-8',
272
- LD_LIBRARY_PATH: '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib',
273
+ LD_LIBRARY_PATH: '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib',
273
274
  NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules'
274
275
  };
275
276
  }
@@ -75,6 +75,7 @@ class HandlerRunner {
75
75
  functionKey,
76
76
  handlerName,
77
77
  handlerPath,
78
+ handlerModuleNesting,
78
79
  runtime,
79
80
  timeout
80
81
  } = _classPrivateFieldLooseBase(this, _funOptions)[_funOptions];
@@ -141,7 +142,7 @@ class HandlerRunner {
141
142
  const {
142
143
  default: InProcessRunner
143
144
  } = await Promise.resolve().then(() => _interopRequireWildcard(require('./in-process-runner/index.js')));
144
- return new InProcessRunner(functionKey, handlerPath, handlerName, _classPrivateFieldLooseBase(this, _env)[_env], timeout, allowCache);
145
+ return new InProcessRunner(functionKey, handlerPath, handlerName, handlerModuleNesting, _classPrivateFieldLooseBase(this, _env)[_env], timeout, allowCache);
145
146
  }
146
147
 
147
148
  if (_index.supportedGo.has(runtime)) {
@@ -27,6 +27,8 @@ var _handlerName = /*#__PURE__*/_classPrivateFieldLooseKey("handlerName");
27
27
 
28
28
  var _handlerPath = /*#__PURE__*/_classPrivateFieldLooseKey("handlerPath");
29
29
 
30
+ var _handlerModuleNesting = /*#__PURE__*/_classPrivateFieldLooseKey("handlerModuleNesting");
31
+
30
32
  var _timeout = /*#__PURE__*/_classPrivateFieldLooseKey("timeout");
31
33
 
32
34
  var _allowCache = /*#__PURE__*/_classPrivateFieldLooseKey("allowCache");
@@ -49,6 +51,10 @@ class ChildProcessRunner {
49
51
  writable: true,
50
52
  value: null
51
53
  });
54
+ Object.defineProperty(this, _handlerModuleNesting, {
55
+ writable: true,
56
+ value: null
57
+ });
52
58
  Object.defineProperty(this, _timeout, {
53
59
  writable: true,
54
60
  value: null
@@ -61,6 +67,7 @@ class ChildProcessRunner {
61
67
  functionKey,
62
68
  handlerName,
63
69
  handlerPath,
70
+ handlerModuleNesting,
64
71
  timeout
65
72
  } = funOptions;
66
73
 
@@ -75,6 +82,7 @@ class ChildProcessRunner {
75
82
  _classPrivateFieldLooseBase(this, _functionKey)[_functionKey] = functionKey;
76
83
  _classPrivateFieldLooseBase(this, _handlerName)[_handlerName] = handlerName;
77
84
  _classPrivateFieldLooseBase(this, _handlerPath)[_handlerPath] = handlerPath;
85
+ _classPrivateFieldLooseBase(this, _handlerModuleNesting)[_handlerModuleNesting] = handlerModuleNesting;
78
86
  _classPrivateFieldLooseBase(this, _timeout)[_timeout] = timeout;
79
87
  _classPrivateFieldLooseBase(this, _allowCache)[_allowCache] = allowCache;
80
88
  } // no-op
@@ -84,7 +92,7 @@ class ChildProcessRunner {
84
92
  cleanup() {}
85
93
 
86
94
  async run(event, context) {
87
- const childProcess = (0, _execa.node)(childProcessHelperPath, [_classPrivateFieldLooseBase(this, _functionKey)[_functionKey], _classPrivateFieldLooseBase(this, _handlerName)[_handlerName], _classPrivateFieldLooseBase(this, _handlerPath)[_handlerPath]], {
95
+ const childProcess = (0, _execa.node)(childProcessHelperPath, [_classPrivateFieldLooseBase(this, _functionKey)[_functionKey], _classPrivateFieldLooseBase(this, _handlerName)[_handlerName], _classPrivateFieldLooseBase(this, _handlerPath)[_handlerPath], _classPrivateFieldLooseBase(this, _handlerModuleNesting)[_handlerModuleNesting]], {
88
96
  env: _classPrivateFieldLooseBase(this, _env)[_env],
89
97
  stdio: 'inherit'
90
98
  });
@@ -244,7 +244,10 @@ class DockerContainer {
244
244
  // Add `host.docker.internal` DNS name to access host from inside the container
245
245
  // https://github.com/docker/for-linux/issues/264
246
246
  const gatewayIp = await this._getBridgeGatewayIp();
247
- dockerArgs.push('--add-host', `host.docker.internal:${gatewayIp}`);
247
+
248
+ if (gatewayIp) {
249
+ dockerArgs.push('--add-host', `host.docker.internal:${gatewayIp}`);
250
+ }
248
251
  }
249
252
 
250
253
  if (_classPrivateFieldLooseBase(this, _dockerOptions)[_dockerOptions].network) {
@@ -58,8 +58,8 @@ const clearModule = (fP, opts) => {
58
58
  delete require.cache[filePath];
59
59
 
60
60
  for (const c of cld) {
61
- // Unload any non node_modules children
62
- if (!c.filename.match(/node_modules/)) {
61
+ // Unload any non node_modules and non-binary children
62
+ if (!c.filename.match(/\/node_modules\//i) && !c.filename.match(/\.node$/i)) {
63
63
  clearModule(c.id, { ...options,
64
64
  cleanup: false
65
65
  });
@@ -74,7 +74,7 @@ const clearModule = (fP, opts) => {
74
74
  cleanup = false;
75
75
 
76
76
  for (const fn of Object.keys(require.cache)) {
77
- if (require.cache[fn] && require.cache[fn].id !== '.' && require.cache[fn].parent && require.cache[fn].parent.id !== '.' && !require.cache[require.cache[fn].parent.id]) {
77
+ if (require.cache[fn] && require.cache[fn].id !== '.' && require.cache[fn].parent && require.cache[fn].parent.id !== '.' && !require.cache[require.cache[fn].parent.id] && !fn.match(/\/node_modules\//i) && !fn.match(/\.node$/i)) {
78
78
  delete require.cache[fn];
79
79
  cleanup = true;
80
80
  }
@@ -92,12 +92,14 @@ var _handlerName = /*#__PURE__*/_classPrivateFieldLooseKey("handlerName");
92
92
 
93
93
  var _handlerPath = /*#__PURE__*/_classPrivateFieldLooseKey("handlerPath");
94
94
 
95
+ var _handlerModuleNesting = /*#__PURE__*/_classPrivateFieldLooseKey("handlerModuleNesting");
96
+
95
97
  var _timeout = /*#__PURE__*/_classPrivateFieldLooseKey("timeout");
96
98
 
97
99
  var _allowCache = /*#__PURE__*/_classPrivateFieldLooseKey("allowCache");
98
100
 
99
101
  class InProcessRunner {
100
- constructor(functionKey, handlerPath, handlerName, env, timeout, allowCache) {
102
+ constructor(functionKey, handlerPath, handlerName, handlerModuleNesting, env, timeout, allowCache) {
101
103
  Object.defineProperty(this, _env, {
102
104
  writable: true,
103
105
  value: null
@@ -114,6 +116,10 @@ class InProcessRunner {
114
116
  writable: true,
115
117
  value: null
116
118
  });
119
+ Object.defineProperty(this, _handlerModuleNesting, {
120
+ writable: true,
121
+ value: null
122
+ });
117
123
  Object.defineProperty(this, _timeout, {
118
124
  writable: true,
119
125
  value: null
@@ -126,6 +132,7 @@ class InProcessRunner {
126
132
  _classPrivateFieldLooseBase(this, _functionKey)[_functionKey] = functionKey;
127
133
  _classPrivateFieldLooseBase(this, _handlerName)[_handlerName] = handlerName;
128
134
  _classPrivateFieldLooseBase(this, _handlerPath)[_handlerPath] = handlerPath;
135
+ _classPrivateFieldLooseBase(this, _handlerModuleNesting)[_handlerModuleNesting] = handlerModuleNesting;
129
136
  _classPrivateFieldLooseBase(this, _timeout)[_timeout] = timeout;
130
137
  _classPrivateFieldLooseBase(this, _allowCache)[_allowCache] = allowCache;
131
138
  } // no-op
@@ -152,9 +159,17 @@ class InProcessRunner {
152
159
  });
153
160
  }
154
161
 
155
- const {
156
- [_classPrivateFieldLooseBase(this, _handlerName)[_handlerName]]: handler
157
- } = await Promise.resolve(`${_classPrivateFieldLooseBase(this, _handlerPath)[_handlerPath]}`).then(s => _interopRequireWildcard(require(s)));
162
+ let handler;
163
+
164
+ try {
165
+ const handlerPathExport = await Promise.resolve(`${_classPrivateFieldLooseBase(this, _handlerPath)[_handlerPath]}`).then(s => _interopRequireWildcard(require(s))); // this supports handling of nested handler paths like <pathToFile>/<fileName>.object1.object2.object3.handler
166
+ // a use case for this, is when the handler is further down the export tree or in nested objects
167
+ // NOTE: this feature is supported in AWS Lambda
168
+
169
+ handler = _classPrivateFieldLooseBase(this, _handlerModuleNesting)[_handlerModuleNesting].reduce((obj, key) => obj[key], handlerPathExport);
170
+ } catch (error) {
171
+ throw new Error(`offline: one of the module nesting ${_classPrivateFieldLooseBase(this, _handlerModuleNesting)[_handlerModuleNesting]} for handler ${_classPrivateFieldLooseBase(this, _handlerName)[_handlerName]} is undefined or not exported`);
172
+ }
158
173
 
159
174
  if (typeof handler !== 'function') {
160
175
  throw new Error(`offline: handler '${_classPrivateFieldLooseBase(this, _handlerName)[_handlerName]}' in ${_classPrivateFieldLooseBase(this, _handlerPath)[_handlerPath]} is not a function`);
@@ -116,8 +116,7 @@ class JavaRunner {
116
116
  data: input,
117
117
  function: _classPrivateFieldLooseBase(this, _functionName)[_functionName],
118
118
  jsonOutput: true,
119
- serverlessOffline: true,
120
- allowCache: _classPrivateFieldLooseBase(this, _allowCache)[_allowCache]
119
+ serverlessOffline: true
121
120
  });
122
121
  const httpOptions = {
123
122
  method: 'POST',
@@ -138,8 +138,6 @@ class RubyRunner {
138
138
  } else {
139
139
  console.log(stderr);
140
140
  }
141
-
142
- return stderr;
143
141
  }
144
142
 
145
143
  return this._parsePayload(stdout);
@@ -39,6 +39,7 @@ class WorkerThreadRunner {
39
39
  functionKey,
40
40
  handlerName,
41
41
  handlerPath,
42
+ handlerModuleNesting,
42
43
  timeout
43
44
  } = funOptions;
44
45
  _classPrivateFieldLooseBase(this, _allowCache)[_allowCache] = allowCache;
@@ -49,6 +50,7 @@ class WorkerThreadRunner {
49
50
  functionKey,
50
51
  handlerName,
51
52
  handlerPath,
53
+ handlerModuleNesting,
52
54
  timeout
53
55
  }
54
56
  });
@@ -10,7 +10,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
10
10
  const {
11
11
  functionKey,
12
12
  handlerName,
13
- handlerPath
13
+ handlerPath,
14
+ handlerModuleNesting
14
15
  } = _worker_threads.workerData;
15
16
 
16
17
  _worker_threads.parentPort.on('message', async messageData => {
@@ -22,7 +23,7 @@ _worker_threads.parentPort.on('message', async messageData => {
22
23
  allowCache
23
24
  } = messageData; // TODO we could probably cache this in the module scope?
24
25
 
25
- const inProcessRunner = new _index.default(functionKey, handlerPath, handlerName, process.env, timeout, allowCache);
26
+ const inProcessRunner = new _index.default(functionKey, handlerPath, handlerName, handlerModuleNesting, process.env, timeout, allowCache);
26
27
  const result = await inProcessRunner.run(event, context); // TODO check serializeability (contains function, symbol etc)
27
28
 
28
29
  port.postMessage(result);
@@ -8,18 +8,18 @@ exports.default = splitHandlerPathAndName;
8
8
  // some-folder/src.index => some-folder/src
9
9
  function splitHandlerPathAndName(handler) {
10
10
  // Split handler into method name and path i.e. handler.run
11
- // Support Ruby paths with namespace resolution operators e.g.
11
+ const prepathDelimiter = handler.lastIndexOf('/');
12
+ const prepath = handler.substr(0, prepathDelimiter + 1); // include '/' for path
13
+
14
+ const postpath = handler.substr(prepathDelimiter + 1); // Support Ruby paths with namespace resolution operators e.g.
12
15
  // ./src/somefolder/source.LambdaFunctions::Handler.process
13
16
  // prepath: ./src/somefolder/
14
17
  // postpath: source.LambdaFunctions::Handler.process
15
18
  // filename: source
16
19
  // path: ./src/somefolder/source
17
20
  // name: LambdaFunctions::Handler.process
18
- if (handler.match(/::/)) {
19
- const prepathDelimiter = handler.lastIndexOf('/');
20
- const prepath = handler.substr(0, prepathDelimiter + 1); // include '/' for path
21
21
 
22
- const postpath = handler.substr(prepathDelimiter + 1);
22
+ if (handler.match(/::/)) {
23
23
  const nameDelimiter = postpath.indexOf('.');
24
24
  const filename = postpath.substr(0, nameDelimiter);
25
25
  const path = prepath + filename;
@@ -30,8 +30,12 @@ function splitHandlerPathAndName(handler) {
30
30
  // name: run
31
31
 
32
32
 
33
- const delimiter = handler.lastIndexOf('.');
34
- const path = handler.substr(0, delimiter);
35
- const name = handler.substr(delimiter + 1);
36
- return [path, name];
33
+ const [filename, ...moduleNesting] = postpath.split('.');
34
+ const [name] = moduleNesting.slice(-1);
35
+ const path = prepath + filename; // module nesting has been added to support when the
36
+ // handler function is buried deep inside of a module
37
+ // e.g /src/somefoler/handlers/index.layer1.layer2.handler
38
+ // AWS supports this feature
39
+
40
+ return [path, name, moduleNesting];
37
41
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dedicatedTo": "Blue, a great migrating bird.",
3
3
  "name": "serverless-offline",
4
- "version": "8.5.0",
4
+ "version": "8.6.0",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
7
  "main": "dist/main.js",
@@ -117,6 +117,7 @@
117
117
  "Kiryl Yermakou (https://github.com/rma4ok)",
118
118
  "kobanyan (https://github.com/kobanyan)",
119
119
  "Leonardo Alifraco (https://github.com/lalifraco-devspark)",
120
+ "Leonardo Medici (https://github.com/doclm)",
120
121
  "Luke Chavers (https://github.com/vmadman)",
121
122
  "Manuel Böhm (https://github.com/boehmers)",
122
123
  "Marc Campbell (https://github.com/marccampbell)",
@@ -204,7 +205,7 @@
204
205
  "@hapi/boom": "^9.1.4",
205
206
  "@hapi/h2o2": "^9.1.0",
206
207
  "@hapi/hapi": "^20.2.1",
207
- "aws-sdk": "^2.1076.0",
208
+ "aws-sdk": "^2.1097.0",
208
209
  "boxen": "^5.1.2",
209
210
  "chalk": "^4.1.2",
210
211
  "cuid": "^2.1.8",
@@ -225,21 +226,20 @@
225
226
  "p-queue": "^6.6.2",
226
227
  "p-retry": "^4.6.1",
227
228
  "please-upgrade-node": "^3.2.0",
228
- "portfinder": "^1.0.28",
229
229
  "semver": "^7.3.5",
230
230
  "update-notifier": "^5.1.0",
231
231
  "velocityjs": "^2.0.6",
232
232
  "ws": "^7.5.7"
233
233
  },
234
234
  "devDependencies": {
235
- "@babel/cli": "^7.17.3",
236
- "@babel/core": "^7.17.5",
235
+ "@babel/cli": "^7.17.6",
236
+ "@babel/core": "^7.17.8",
237
237
  "@babel/plugin-proposal-class-properties": "^7.16.7",
238
238
  "@babel/plugin-proposal-dynamic-import": "^7.16.7",
239
239
  "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
240
240
  "@babel/plugin-proposal-optional-chaining": "^7.16.7",
241
- "@babel/plugin-transform-modules-commonjs": "^7.16.8",
242
- "@babel/register": "^7.17.0",
241
+ "@babel/plugin-transform-modules-commonjs": "^7.17.7",
242
+ "@babel/register": "^7.17.7",
243
243
  "archiver": "^5.3.0",
244
244
  "babel-eslint": "^10.1.0",
245
245
  "copyfiles": "^2.4.1",
@@ -253,9 +253,9 @@
253
253
  "jest": "^26.6.3",
254
254
  "lint-staged": "^11.2.6",
255
255
  "p-map": "^4.0.0",
256
- "prettier": "^2.5.1",
256
+ "prettier": "^2.6.0",
257
257
  "rimraf": "^3.0.2",
258
- "serverless": "^2.72.2",
258
+ "serverless": "^2.72.3",
259
259
  "standard-version": "^9.3.2"
260
260
  },
261
261
  "peerDependencies": {