Skip to content

Commit 58ea034

Browse files
committed
v0.4.0
Node 4 support (promises, arrow functions, etc) Removed polling for delete event callback Added support for API Gateway’s lambda proxy mode Significantly improved unit tests
1 parent 257bfac commit 58ea034

File tree

3 files changed

+480
-182
lines changed

3 files changed

+480
-182
lines changed

lib/index.js

Lines changed: 132 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
var VERSION = '0.3.1';
2-
var util = require('util');
1+
var VERSION = '0.4.0';
32
var assert = require('assert');
43
var aws = require('aws-sdk');
54
var s3 = new aws.S3({apiVersion: '2006-03-01'});
@@ -9,41 +8,35 @@ var crypto = require('crypto');
98

109
// replace below with your credentials in the form key:secret
1110
// to create your key go to https://scanii.com/account/settings/keys
12-
var SCANII_CREDS = 'CHANGE';
11+
var SCANII_CREDS = 'CHANGE:ME';
1312
// replace with the URL of your callback lambda function
14-
var CALLBACK_URL = 'ME';
15-
16-
17-
onFindings = function (bucket, key, result, context) {
18-
console.log('RESULT:', internalId(bucket, key), 'has findings', result.findings);
19-
var pending = true;
20-
21-
// sample code that delete objects with findings from S3:
22-
var req = s3.deleteObject({Bucket: bucket, Key: key});
23-
req.on('success', function (response) {
24-
console.log('file', internalId(bucket, key), 'deleted');
25-
context.succeed(util.format('callback for file %s completed successful', result.id));
26-
pending = false;
27-
28-
}).on('error', function (response) {
29-
console.log('error while deleting', internalId(bucket, key));
30-
context.fail(response);
31-
pending = false;
13+
var CALLBACK_URL = 'CHANGE_ME';
14+
15+
var onFindings = function (bucket, key, result) {
16+
return new Promise((resolve, reject) => {
17+
"use strict";
18+
console.log('RESULT:', internalId(bucket, key), 'has findings', result.findings);
19+
20+
// sample code that delete objects with findings from S3:
21+
s3.deleteObject({Bucket: bucket, Key: key}, (error, data) => {
22+
if (error) {
23+
console.error(error, error.stack); // an error occurred
24+
reject(`error while deleting: ${internalId(bucket, key)} please see logs for details`);
25+
} else {
26+
console.log('file', internalId(bucket, key), 'deleted');
27+
resolve(true);
28+
}
29+
});
3230
});
33-
req.send();
34-
var waitWhilePending = function () {
35-
if (pending) {
36-
console.log('waiting');
37-
setTimeout(waitWhilePending, 100);
38-
}
39-
};
40-
setTimeout(waitWhilePending, 100);
41-
4231
};
4332

44-
onNoFindings = function (bucket, key, result, context) {
45-
console.log('RESULT:', internalId(bucket, key), 'has no findings');
46-
context.succeed(util.format('callback for file %s completed successful', result.id));
33+
var onNoFindings = function (bucket, key, result) {
34+
return new Promise((resolve, reject) => {
35+
"use strict";
36+
console.log('RESULT:', internalId(bucket, key), 'has no findings');
37+
console.log(`callback for file ${result.id} completed successful`);
38+
resolve(true);
39+
});
4740

4841
};
4942

@@ -57,29 +50,36 @@ if (process.env.SCANII_CREDS !== undefined) {
5750
}
5851

5952
const API_ENDPOINT = '/v2.1/files/';
60-
61-
console.log('loading function v', VERSION);
62-
63-
var KEY = SCANII_CREDS.split(':')[0];
64-
var SECRET = SCANII_CREDS.split(':')[1];
65-
console.log('using key', KEY);
53+
const KEY = SCANII_CREDS.split(':')[0];
54+
const SECRET = SCANII_CREDS.split(':')[1];
6655

6756
var internalId = function (bucket, key) {
68-
return util.format('s3://%s/%s', bucket, key);
57+
return `s3://${bucket}/${key}`;
6958
};
7059

60+
/**
61+
* Generates a HMAC-SHA1 digital signature for a bucket/key combination using the SECRET as the key
62+
* @param bucket the bucket name
63+
* @param key the key name
64+
* @returns {string} the digitally signed bucket+key combination
65+
*/
7166
var generateSignature = function (bucket, key) {
7267
return crypto.createHmac('sha1', SECRET).update(internalId(bucket, key)).digest('hex');
7368
};
7469

7570
/**
7671
* Handles HTTP result callback
72+
* @param {Object} event the API gateway event to be processed
73+
* @param callback the lambda flow control callback
7774
*/
78-
var handleCallback = function (event, context) {
75+
var handleApiGatewayEvent = (event, callback) => {
7976
console.log('handling callback event');
8077

8178
var r = event;
82-
//console.log('response:', JSON.stringify(r, null, 2));
79+
if (event.body !== undefined) {
80+
// if this is proxy request we yank the actual body from the payload
81+
r = JSON.parse(event.body);
82+
}
8383
console.log("metadata:", r.metadata);
8484

8585
// callback sanity checks
@@ -93,28 +93,56 @@ var handleCallback = function (event, context) {
9393
assert.ok(r.metadata.signature == generateSignature(r.metadata.bucket, r.metadata.key), "invalid signature");
9494
console.log('signature check passed for signature', r.metadata.signature);
9595

96-
if (r.findings !== undefined) {
97-
if (r.findings.length > 0) {
98-
onFindings(r.metadata.bucket, r.metadata.key, r, context);
96+
var handler;
97+
98+
if (r.findings !== undefined && r.findings.length > 0) {
99+
console.log("processing callback with findings ", r.findings);
100+
101+
// if in test mode we really don't dispatch this call
102+
if (process.env.MOCK_EXTERNAL_SERVICES !== undefined) {
103+
handler = Promise.resolve(true);
99104
} else {
100-
onNoFindings(r.metadata.bucket, r.metadata.key, r, context);
105+
handler = onFindings(r.metadata.bucket, r.metadata.key, r);
101106
}
107+
} else {
108+
handler = onNoFindings(r.metadata.bucket, r.metadata.key, r);
102109
}
103110

104-
};
111+
handler.then((result)=> {
112+
"use strict";
113+
if (result === true) {
114+
// returns a API gateway lambda proxy confirming response:
115+
callback(null, {
116+
"statusCode": 200,
117+
"headers": {
118+
"Content-Type": "application/json"
119+
},
120+
"body": JSON.stringify({status: "OK"}, null, 2)
121+
});
122+
}
123+
}).catch((error)=> {
124+
"use strict";
125+
console.error(error);
126+
callback(error);
127+
});
105128

129+
};
106130

107131
/**
108-
* Handles S3 event and submits content for processing
132+
* Handles events from S3 and submits object for processing
133+
* @param event {Object} S3 event to be processed
134+
* @param callback the lambda flow control callback
109135
*/
110-
var handleS3Event = function (event, context) {
111-
console.log('handling S3 event');
112-
136+
var handleS3Event = (event, callback) => {
113137
// Get the object from the event and show its content type
114138
var bucket = event.Records[0].s3.bucket.name;
115139
// see https://forums.aws.amazon.com/thread.jspa?threadID=215813
116140
var key = Object.keys(qs.decode(event.Records[0].s3.object.key))[0];
117141

142+
// sanity checks
143+
assert(bucket !== undefined, "bucket not present in s3 event");
144+
assert(key !== undefined, "key not present in s3 event");
145+
118146
console.log('processing ' + internalId(bucket, key));
119147

120148
// creating signed url for processing
@@ -148,40 +176,66 @@ var handleS3Event = function (event, context) {
148176
headers: {
149177
'Content-Type': 'application/x-www-form-urlencoded',
150178
'Content-Length': payload.length,
151-
'User-Agent': 'scanii-lambda/v' + VERSION
179+
'User-Agent': 'scanii-lambda-v' + VERSION
152180
}
153181
};
154-
console.log(payload);
155-
// calling scanii API v2.1 (http://docs.scanii.com/v2.1/resources.html):
156-
var req = http.request(options, function (res) {
157-
console.log("headers: ", res.headers);
158-
159-
res.on('data', function (data) {
160-
var serviceResponse = JSON.parse(data);
161-
if (res.statusCode == 202) {
162-
console.log('file id:', serviceResponse.id);
163-
context.succeed(serviceResponse.id);
164-
} else {
165-
console.log(serviceResponse);
166-
context.fail(util.format("Error: invalid response from server, message [%s] and http code: %d", serviceResponse.body, res.statusCode));
167-
}
168-
});
169-
res.on('error', function (error) {
170-
context.fail(error);
182+
183+
var processResponse = (res, data) => {
184+
"use strict";
185+
var serviceResponse = JSON.parse(data);
186+
if (res.statusCode == 202) {
187+
console.log('file id:', serviceResponse.id);
188+
callback(null, serviceResponse.id);
189+
} else {
190+
console.log(serviceResponse);
191+
callback(`Error: invalid response from server, message [${serviceResponse.body}] and http code: ${res.statusCode}`);
192+
}
193+
};
194+
195+
// check whether we should return a canned response (used during testing)
196+
if (process.env.MOCK_EXTERNAL_SERVICES !== undefined) {
197+
processResponse(event._mock.response, event._mock.data);
198+
} else {
199+
// calling scanii API v2.1 (http://docs.scanii.com/v2.1/resources.html):
200+
var req = http.request(options, function (res) {
201+
console.log("headers: ", res.headers);
202+
res.on('data', function (data) {
203+
processResponse(res, data);
204+
});
205+
res.on('error', function (error) {
206+
callback(error);
207+
});
171208
});
172-
});
173209

174-
req.write(payload);
175-
req.end();
210+
req.write(payload);
211+
req.end();
176212

213+
}
177214
};
178215

179-
exports.handler = function (event, context) {
180-
console.log('received event:', JSON.stringify(event, null, 2));
181-
if (event.Records !== undefined) {
182-
handleS3Event(event, context);
183-
} else {
184-
handleCallback(event, context);
216+
/**
217+
* lambda entry point function
218+
* @param event event to be processed
219+
* @param context lambda environmental context object
220+
* @param callback the lambda flow control callback
221+
*/
222+
exports.handler = (event, context, callback) => {
223+
console.log(">> starting");
224+
console.log(`>> using key [${KEY}] and function scanii-lambda/v${VERSION}`);
225+
if (process.env.MOCK_EXTERNAL_SERVICES !== undefined) {
226+
console.log(">> mocking external calls!");
227+
}
228+
console.log('>> received event:', JSON.stringify(event, null, 2));
229+
230+
try {
231+
if (event.Records !== undefined) {
232+
handleS3Event(event, callback);
233+
} else {
234+
handleApiGatewayEvent(event, callback);
235+
}
236+
} catch (error) {
237+
console.error(error, error.stack); // an error occurred
238+
callback(error);
185239
}
186240
};
187241

package.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "scanii-mu",
3-
"version": "0.3.1",
2+
"name": "scanii-lambda",
3+
"version": "0.4.0",
44
"description": "",
55
"main": "index.js",
66
"scripts": {
@@ -9,10 +9,8 @@
99
"author": "",
1010
"license": "Apache-2.0",
1111
"dependencies": {
12-
"aws-lambda-mock-context": "^0.2.0",
13-
"aws-sdk": "^2.2.5",
14-
"chai": "^3.4.1",
15-
"consoleplusplus": "^1.3.0",
12+
"aws-lambda-mock-context": "^3.0.0",
13+
"aws-sdk": "^2.6.3",
1614
"mocha": "^2.3.3"
1715
}
1816
}

0 commit comments

Comments
 (0)