@sendsafely/sendsafely 1.1.0 → 1.1.2
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/FetchRequest.js +99 -0
- package/FileUtil.js +110 -0
- package/LICENSE +490 -0
- package/SendSafely.js +197 -316
- package/package.json +4 -3
package/FetchRequest.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fetch = require('make-fetch-happen');
|
|
2
|
+
const sjcl = require("sjcl");
|
|
3
|
+
const URL = require('url').URL;
|
|
4
|
+
|
|
5
|
+
function FetchRequest(param) {
|
|
6
|
+
if(!param.hasOwnProperty('signedRequest')) {
|
|
7
|
+
if(typeof param !== 'object') {
|
|
8
|
+
throw new Error('FetchRequest: Invalid parameters');
|
|
9
|
+
}
|
|
10
|
+
if(!param.hasOwnProperty('url')) {
|
|
11
|
+
throw new Error('FetchRequest: url is missing');
|
|
12
|
+
}
|
|
13
|
+
if(!param.hasOwnProperty('apiKey')) {
|
|
14
|
+
throw new Error('FetchRequest: apiKey is missing');
|
|
15
|
+
}
|
|
16
|
+
if(!param.hasOwnProperty('apiKeySecret')) {
|
|
17
|
+
throw new Error('FetchRequest: apiKeySecret is missing');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let myself = this;
|
|
22
|
+
myself.url = param.url; // scheme + domain + port for signed request, full url for non signed request
|
|
23
|
+
myself.apiPrefix = '/api/v2.0';
|
|
24
|
+
myself.apiKey = param.apiKey;
|
|
25
|
+
myself.apiKeySecret = param.apiKeySecret;
|
|
26
|
+
myself.requestAPI = param.hasOwnProperty('requestAPI') ? param.requestAPI: 'NODE_API';
|
|
27
|
+
myself.options = {};
|
|
28
|
+
|
|
29
|
+
myself.sendRequest = function (url, options) {
|
|
30
|
+
return fetch(url, options);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
myself.sendSignedRequest = function (endpoint, data) {
|
|
34
|
+
buildHttpsOptions(endpoint, data);
|
|
35
|
+
return fetch(myself.url + myself.apiPrefix + endpoint.url, myself.options);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let buildHttpsOptions = function(endpoint, data) {
|
|
39
|
+
if(typeof endpoint !== 'object') {
|
|
40
|
+
throw new Error('FetchRequest: Invalid endpoint parameters');
|
|
41
|
+
}
|
|
42
|
+
if(!endpoint.hasOwnProperty('url')) {
|
|
43
|
+
throw new Error('FetchRequest: url is missing');
|
|
44
|
+
}
|
|
45
|
+
if(!endpoint.hasOwnProperty('HTTPMethod')) {
|
|
46
|
+
throw new Error('FetchRequest: HTTPMethod is missing');
|
|
47
|
+
}
|
|
48
|
+
if(!endpoint.hasOwnProperty('mimetype')) {
|
|
49
|
+
throw new Error('FetchRequest: mimetype is missing');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let timestamp = dateString();
|
|
53
|
+
let signature = myself.apiKey + myself.apiPrefix + endpoint.url.split("?")[0] + timestamp;
|
|
54
|
+
if(endpoint.hasOwnProperty('messageData')) {
|
|
55
|
+
signature += JSON.stringify(endpoint.messageData);
|
|
56
|
+
} else if(data !== '' && data !== null) {
|
|
57
|
+
signature += JSON.stringify(data);
|
|
58
|
+
}
|
|
59
|
+
signature = signMessage(signature);
|
|
60
|
+
|
|
61
|
+
let method = endpoint.HTTPMethod;
|
|
62
|
+
let headers = {
|
|
63
|
+
'Content-Type': endpoint.mimetype,
|
|
64
|
+
'ss-api-key':myself.apiKey,
|
|
65
|
+
'ss-request-timestamp': timestamp,
|
|
66
|
+
'ss-request-signature': signature,
|
|
67
|
+
'ss-request-api': myself.requestAPI,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let options = {
|
|
71
|
+
headers: headers,
|
|
72
|
+
method: method,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if(data !== null) {
|
|
76
|
+
if(endpoint.mimetype.includes('multipart/form-data')) {
|
|
77
|
+
options.headers['Content-Length'] = Buffer.from(data).length;
|
|
78
|
+
options.body = data;
|
|
79
|
+
} else {
|
|
80
|
+
options.body = JSON.stringify(data);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
myself.options = options;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
let dateString = function() {
|
|
89
|
+
let time = new Date().toISOString();
|
|
90
|
+
return time.substr(0, 19) + "+0000";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let signMessage = function(messageString) {
|
|
94
|
+
let hmacFunction = new sjcl.misc.hmac(sjcl.codec.utf8String.toBits(myself.apiKeySecret), sjcl.hash.sha256);// Key, Hash
|
|
95
|
+
return sjcl.codec.hex.fromBits(hmacFunction.encrypt(messageString));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {FetchRequest};
|
package/FileUtil.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function FileUtil(param) {
|
|
5
|
+
|
|
6
|
+
if(param === undefined || typeof param !== 'object') {
|
|
7
|
+
throw new Error('FileUtil: Invalid parameters');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if(!param.hasOwnProperty('filePath')) {
|
|
11
|
+
throw new Error('FileUtil: filePath is needed');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if(!param.hasOwnProperty('callback')) {
|
|
15
|
+
throw new Error('FileUtil: callback is needed');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if(param.hasOwnProperty('callback') && typeof param.callback !== 'function') {
|
|
19
|
+
throw new Error('FileUtil: callback must be a function');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let myself = this;
|
|
23
|
+
this.SEGMENT_SIZE = 2621440;
|
|
24
|
+
this.filePath = param.filePath;
|
|
25
|
+
this.callback = param.callback;
|
|
26
|
+
this.file = {size: 0, name: '', totalParts: 0};
|
|
27
|
+
this.readableStream = fs.createReadStream(myself.filePath);
|
|
28
|
+
this.reading = false;
|
|
29
|
+
this.eof = false;
|
|
30
|
+
this.data = [];
|
|
31
|
+
this.tempSize = 0;
|
|
32
|
+
|
|
33
|
+
this.init = function() {
|
|
34
|
+
return new Promise(function(resolve) {
|
|
35
|
+
fs.stat(myself.filePath, (err, stats) => {
|
|
36
|
+
if (err) {
|
|
37
|
+
throw new Error('FileUtil: File does not exist, ' + myself.filePath);
|
|
38
|
+
} else {
|
|
39
|
+
myself.file.size = stats.size;
|
|
40
|
+
myself.file.name = path.basename(myself.filePath);
|
|
41
|
+
|
|
42
|
+
if(myself.file.size > (myself.SEGMENT_SIZE/4)) {
|
|
43
|
+
myself.file.totalParts = Math.ceil((myself.file.size-(myself.SEGMENT_SIZE/4))/myself.SEGMENT_SIZE);
|
|
44
|
+
} else {
|
|
45
|
+
myself.file.totalParts = 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
resolve(myself.file);
|
|
49
|
+
|
|
50
|
+
myself.readableStream.on('readable', function() {
|
|
51
|
+
// keep reading chunk until it reaches SEGMENT_SIZE
|
|
52
|
+
if(myself.reading && !myself.eof) {
|
|
53
|
+
processChunk();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
myself.readableStream.on('end', function() {
|
|
58
|
+
// done reading file
|
|
59
|
+
if(!myself.eof) {
|
|
60
|
+
myself.eof = true;
|
|
61
|
+
callback(true);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.read = function() {
|
|
71
|
+
if(!myself.reading && !myself.eof) {
|
|
72
|
+
myself.reading = true;
|
|
73
|
+
processChunk();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function processChunk() {
|
|
78
|
+
if(myself.tempSize === myself.SEGMENT_SIZE) {
|
|
79
|
+
// callback when data size reaches SEGMENT_SIZE
|
|
80
|
+
callback(false);
|
|
81
|
+
} else {
|
|
82
|
+
let chunk = myself.readableStream.read();
|
|
83
|
+
if(chunk !== null) {
|
|
84
|
+
myself.data.push(new Uint8Array(chunk));
|
|
85
|
+
myself.tempSize += chunk.length;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function callback(isComplete) {
|
|
91
|
+
myself.tempSize = 0;
|
|
92
|
+
myself.reading = false;
|
|
93
|
+
myself.callback({data: concatenate(myself.data), complete: isComplete});
|
|
94
|
+
myself.data = [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function concatenate(arrays) {
|
|
98
|
+
var totalLength = arrays.reduce(function(total, arr) {
|
|
99
|
+
return total + arr.length
|
|
100
|
+
}, 0);
|
|
101
|
+
var result = new Uint8Array(totalLength);
|
|
102
|
+
arrays.reduce(function(offset, arr){
|
|
103
|
+
result.set(arr, offset);
|
|
104
|
+
return offset + arr.length;
|
|
105
|
+
}, 0);
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {FileUtil};
|