@suprsend/node-sdk 0.1.0 → 1.0.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/dist/attachment.js +26 -0
- package/dist/bulk_response.js +60 -0
- package/dist/constants.js +40 -0
- package/dist/event.js +154 -85
- package/dist/events_bulk.js +424 -0
- package/dist/index.js +89 -39
- package/dist/request_json/event.json +5 -0
- package/dist/request_json/workflow.json +116 -9
- package/dist/{identity.js → subscriber.js} +195 -50
- package/dist/{identity_helper.js → subscriber_helper.js} +191 -59
- package/dist/subscribers_bulk.js +447 -0
- package/dist/utils.js +210 -1
- package/dist/workflow.js +124 -59
- package/dist/workflows_bulk.js +424 -0
- package/package.json +7 -4
- package/src/attachment.js +12 -0
- package/src/bulk_response.js +35 -0
- package/src/constants.js +28 -0
- package/src/event.js +118 -75
- package/src/events_bulk.js +234 -0
- package/src/index.js +67 -33
- package/src/request_json/event.json +5 -0
- package/src/request_json/workflow.json +116 -9
- package/src/{identity.js → subscriber.js} +118 -34
- package/src/{identity_helper.js → subscriber_helper.js} +196 -53
- package/src/subscribers_bulk.js +235 -0
- package/src/utils.js +136 -0
- package/src/workflow.js +94 -45
- package/src/workflows_bulk.js +234 -0
- package/dist/config.js +0 -13
- package/src/config.js +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suprsend/node-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Suprsend Node SDK to trigger workflow from backend",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"suprsend-node-sdk",
|
|
12
|
-
"
|
|
12
|
+
"node",
|
|
13
|
+
"sdk",
|
|
14
|
+
"notifications"
|
|
13
15
|
],
|
|
14
16
|
"author": "SuprSend Developers",
|
|
15
17
|
"license": "MIT",
|
|
@@ -22,14 +24,15 @@
|
|
|
22
24
|
},
|
|
23
25
|
"dependencies": {
|
|
24
26
|
"@babel/runtime": "^7.16.3",
|
|
25
|
-
"axios": "^0.
|
|
27
|
+
"axios": "^0.27.2",
|
|
26
28
|
"jsonschema": "^1.4.0",
|
|
29
|
+
"lodash": "^4.17.21",
|
|
27
30
|
"mime-types": "^2.1.34",
|
|
28
31
|
"uuid": "^8.3.2"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
34
|
"@babel/cli": "^7.16.0",
|
|
32
|
-
"@babel/core": "^7.
|
|
35
|
+
"@babel/core": "^7.18.6",
|
|
33
36
|
"@babel/plugin-transform-runtime": "^7.16.4",
|
|
34
37
|
"@babel/preset-env": "^7.16.4",
|
|
35
38
|
"babel-plugin-add-module-exports": "^1.0.4"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import mime from "mime-types";
|
|
3
|
+
import { base64Encode, resolveTilde } from "./utils";
|
|
4
|
+
|
|
5
|
+
export default function get_attachment_json_for_file(file_path) {
|
|
6
|
+
const abs_path = path.resolve(resolveTilde(file_path));
|
|
7
|
+
return {
|
|
8
|
+
filename: path.basename(abs_path),
|
|
9
|
+
contentType: mime.lookup(abs_path),
|
|
10
|
+
data: base64Encode(abs_path),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export default class BulkResponse {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.status;
|
|
4
|
+
this.failed_records = [];
|
|
5
|
+
this.total = 0;
|
|
6
|
+
this.success = 0;
|
|
7
|
+
this.failure = 0;
|
|
8
|
+
this.warnings = [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
merge_chunk_response(ch_resp) {
|
|
12
|
+
if (!ch_resp) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// possible status: success/partial/fail
|
|
16
|
+
if (!this.status) {
|
|
17
|
+
this.status = ch_resp["status"];
|
|
18
|
+
} else {
|
|
19
|
+
if (this.status === "success") {
|
|
20
|
+
if (ch_resp.status === "fail") {
|
|
21
|
+
this.status = "partial";
|
|
22
|
+
}
|
|
23
|
+
} else if (this.status === "fail") {
|
|
24
|
+
if (ch_resp.status === "success") {
|
|
25
|
+
this.status = "partial";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
this.total += ch_resp.total || 0;
|
|
30
|
+
this.success += ch_resp.success || 0;
|
|
31
|
+
this.failure += ch_resp.failure || 0;
|
|
32
|
+
const failed_recs = ch_resp.failed_records || [];
|
|
33
|
+
this.failed_records = [...this.failed_records, ...failed_recs];
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Default urls
|
|
2
|
+
export const DEFAULT_URL = "https://hub.suprsend.com/";
|
|
3
|
+
export const DEFAULT_UAT_URL =
|
|
4
|
+
"https://collector-staging.suprsend.workers.dev/";
|
|
5
|
+
|
|
6
|
+
// a API call should not have apparent body size of more than 800KB
|
|
7
|
+
export const BODY_MAX_APPARENT_SIZE_IN_BYTES = 800 * 1024; // 800 * 1024
|
|
8
|
+
export const BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE = "800KB";
|
|
9
|
+
|
|
10
|
+
// in general url-size wont exceed 2048 chars or 2048 utf-8 bytes
|
|
11
|
+
export const ATTACHMENT_URL_POTENTIAL_SIZE_IN_BYTES = 2100;
|
|
12
|
+
|
|
13
|
+
// few keys added in-flight, amounting to almost 200 bytes increase per workflow-body
|
|
14
|
+
export const WORKFLOW_RUNTIME_KEYS_POTENTIAL_SIZE_IN_BYTES = 200;
|
|
15
|
+
|
|
16
|
+
// max workflow-records in one bulk api call.
|
|
17
|
+
export const MAX_WORKFLOWS_IN_BULK_API = 100;
|
|
18
|
+
// max event-records in one bulk api call
|
|
19
|
+
export const MAX_EVENTS_IN_BULK_API = 100;
|
|
20
|
+
|
|
21
|
+
export const ALLOW_ATTACHMENTS_IN_BULK_API = false;
|
|
22
|
+
export const ATTACHMENT_UPLOAD_ENABLED = false;
|
|
23
|
+
|
|
24
|
+
// -- single Identity event limit
|
|
25
|
+
export const IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES = 2 * 1024;
|
|
26
|
+
export const IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE = "2KB";
|
|
27
|
+
|
|
28
|
+
export const MAX_IDENTITY_EVENTS_IN_BULK_API = 400;
|
package/src/event.js
CHANGED
|
@@ -5,12 +5,16 @@ import {
|
|
|
5
5
|
has_special_char,
|
|
6
6
|
uuid,
|
|
7
7
|
epoch_milliseconds,
|
|
8
|
+
validate_track_event_schema,
|
|
9
|
+
get_apparent_event_size,
|
|
8
10
|
} from "./utils";
|
|
9
11
|
import get_request_signature from "./signature";
|
|
10
|
-
import { Validator } from "jsonschema";
|
|
11
12
|
import axios from "axios";
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
import get_attachment_json_for_file from "./attachment";
|
|
14
|
+
import {
|
|
15
|
+
BODY_MAX_APPARENT_SIZE_IN_BYTES,
|
|
16
|
+
BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
|
|
17
|
+
} from "./constants";
|
|
14
18
|
|
|
15
19
|
const RESERVED_EVENT_NAMES = [
|
|
16
20
|
"$identify",
|
|
@@ -22,12 +26,97 @@ const RESERVED_EVENT_NAMES = [
|
|
|
22
26
|
"$user_logout",
|
|
23
27
|
];
|
|
24
28
|
|
|
25
|
-
class
|
|
29
|
+
export default class Event {
|
|
30
|
+
constructor(distinct_id, event_name, properties, idempotency_key) {
|
|
31
|
+
this.distinct_id = distinct_id;
|
|
32
|
+
this.event_name = event_name;
|
|
33
|
+
this.properties = properties;
|
|
34
|
+
this.idempotency_key = idempotency_key;
|
|
35
|
+
// --- validate
|
|
36
|
+
this.__validate_distinct_id();
|
|
37
|
+
this.__validate_event_name();
|
|
38
|
+
this.__validate_properties();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
__validate_distinct_id() {
|
|
42
|
+
if (this.distinct_id instanceof String) {
|
|
43
|
+
throw new SuprsendError(
|
|
44
|
+
"distinct_id must be a string. an Id which uniquely identify a user in your app"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const distinct_id = this.distinct_id.trim();
|
|
48
|
+
if (!distinct_id) {
|
|
49
|
+
throw new SuprsendError("distinct_id missing");
|
|
50
|
+
}
|
|
51
|
+
this.distinct_id = distinct_id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
__validate_properties() {
|
|
55
|
+
if (!this.properties) {
|
|
56
|
+
this.properties = {};
|
|
57
|
+
}
|
|
58
|
+
if (!(this.properties instanceof Object)) {
|
|
59
|
+
throw new SuprsendError("properties must be a dictionary");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
__check_event_prefix(event_name) {
|
|
64
|
+
if (!RESERVED_EVENT_NAMES.includes(event_name)) {
|
|
65
|
+
if (has_special_char(event_name)) {
|
|
66
|
+
throw new SuprsendError(
|
|
67
|
+
"event_names starting with [$,ss_] are reserved by SuprSend"
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
__validate_event_name() {
|
|
74
|
+
if (!is_string(this.event_name)) {
|
|
75
|
+
throw new SuprsendError("event_name must be a string");
|
|
76
|
+
}
|
|
77
|
+
const event_name = this.event_name.trim();
|
|
78
|
+
this.__check_event_prefix(event_name);
|
|
79
|
+
this.event_name = event_name;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
add_attachment(file_path) {
|
|
83
|
+
const attachment = get_attachment_json_for_file(file_path);
|
|
84
|
+
// --- add the attachment to properties->$attachments
|
|
85
|
+
if (!this.properties["$attachments"]) {
|
|
86
|
+
this.properties["$attachments"] = [];
|
|
87
|
+
}
|
|
88
|
+
this.properties["$attachments"].push(attachment);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get_final_json(config, is_part_of_bulk = false) {
|
|
92
|
+
const super_props = { $ss_sdk_version: config.user_agent };
|
|
93
|
+
let event_dict = {
|
|
94
|
+
$insert_id: uuid(),
|
|
95
|
+
$time: epoch_milliseconds(),
|
|
96
|
+
event: this.event_name,
|
|
97
|
+
env: config.workspace_key,
|
|
98
|
+
distinct_id: this.distinct_id,
|
|
99
|
+
properties: { ...this.properties, ...super_props },
|
|
100
|
+
};
|
|
101
|
+
if (this.idempotency_key) {
|
|
102
|
+
event_dict["$idempotency_key"] = this.idempotency_key;
|
|
103
|
+
}
|
|
104
|
+
event_dict = validate_track_event_schema(event_dict);
|
|
105
|
+
const apparent_size = get_apparent_event_size(event_dict, is_part_of_bulk);
|
|
106
|
+
if (apparent_size > BODY_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
107
|
+
throw new SuprsendError(
|
|
108
|
+
`Event properties too big - ${apparent_size} Bytes,must not cross ${BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return [event_dict, apparent_size];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class EventCollector {
|
|
26
116
|
constructor(config) {
|
|
27
117
|
this.config = config;
|
|
28
118
|
this.__url = this.__get_url();
|
|
29
119
|
this.__headers = this.__common_headers();
|
|
30
|
-
this.__supr_props = this.__super_properties();
|
|
31
120
|
}
|
|
32
121
|
|
|
33
122
|
__get_url() {
|
|
@@ -54,63 +143,9 @@ class EventCollector {
|
|
|
54
143
|
};
|
|
55
144
|
}
|
|
56
145
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
__check_event_prefix(event_name) {
|
|
64
|
-
if (!RESERVED_EVENT_NAMES.includes(event_name)) {
|
|
65
|
-
if (has_special_char(event_name)) {
|
|
66
|
-
throw new SuprsendError(
|
|
67
|
-
"event_names starting with [$,ss_] are reserved"
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
__validate_event_name(event_name) {
|
|
74
|
-
if (!is_string(event_name)) {
|
|
75
|
-
throw new SuprsendError("event_name must be a string");
|
|
76
|
-
}
|
|
77
|
-
event_name = event_name.trim();
|
|
78
|
-
this.__check_event_prefix(event_name);
|
|
79
|
-
return event_name;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
validate_track_event_schema(data) {
|
|
83
|
-
if (!data["properties"]) {
|
|
84
|
-
data["properties"] = {};
|
|
85
|
-
}
|
|
86
|
-
const schema = event_schema;
|
|
87
|
-
var v = new Validator();
|
|
88
|
-
const validated_data = v.validate(data, schema);
|
|
89
|
-
if (validated_data.valid) {
|
|
90
|
-
return data;
|
|
91
|
-
} else {
|
|
92
|
-
const error_obj = validated_data.errors[0];
|
|
93
|
-
const error_msg = `${error_obj.property} ${error_obj.message}`;
|
|
94
|
-
throw new SuprsendError(error_msg);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
collect(distinct_id, event_name, properties = {}) {
|
|
99
|
-
event_name = this.__validate_event_name(event_name);
|
|
100
|
-
if (!is_object(properties)) {
|
|
101
|
-
throw new SuprsendError("properties must be a dictionary");
|
|
102
|
-
}
|
|
103
|
-
properties = { ...properties, ...this.__supr_props };
|
|
104
|
-
let event = {
|
|
105
|
-
$insert_id: uuid(),
|
|
106
|
-
$time: epoch_milliseconds(),
|
|
107
|
-
event: event_name,
|
|
108
|
-
env: this.config.env_key,
|
|
109
|
-
distinct_id: distinct_id,
|
|
110
|
-
properties: properties,
|
|
111
|
-
};
|
|
112
|
-
event = this.validate_track_event_schema(event);
|
|
113
|
-
return this.send(event);
|
|
146
|
+
collect(event) {
|
|
147
|
+
const [event_dict, event_size] = event.get_final_json(this.config, false);
|
|
148
|
+
return this.send(event_dict);
|
|
114
149
|
}
|
|
115
150
|
|
|
116
151
|
async send(event) {
|
|
@@ -122,27 +157,35 @@ class EventCollector {
|
|
|
122
157
|
"POST",
|
|
123
158
|
content_text,
|
|
124
159
|
headers,
|
|
125
|
-
this.config.
|
|
160
|
+
this.config.workspace_secret
|
|
126
161
|
);
|
|
127
|
-
headers["Authorization"] = `${this.config.
|
|
162
|
+
headers["Authorization"] = `${this.config.workspace_key}:${signature}`;
|
|
128
163
|
}
|
|
129
164
|
try {
|
|
130
|
-
const response = await axios.post(this.__url, content_text, {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
const response = await axios.post(this.__url, content_text, { headers });
|
|
166
|
+
const ok_response = Math.floor(response.status / 100) == 2;
|
|
167
|
+
if (ok_response) {
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
status: "success",
|
|
171
|
+
status_code: response.status,
|
|
172
|
+
message: response.statusText,
|
|
173
|
+
};
|
|
174
|
+
} else {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
status: "fail",
|
|
178
|
+
status_code: response.status,
|
|
179
|
+
message: response.statusText,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
138
182
|
} catch (err) {
|
|
139
183
|
return {
|
|
140
|
-
status_code: 400,
|
|
141
184
|
success: false,
|
|
185
|
+
status: "fail",
|
|
186
|
+
status_code: err.status || 500,
|
|
142
187
|
message: err.message,
|
|
143
188
|
};
|
|
144
189
|
}
|
|
145
190
|
}
|
|
146
191
|
}
|
|
147
|
-
|
|
148
|
-
export default EventCollector;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ALLOW_ATTACHMENTS_IN_BULK_API,
|
|
3
|
+
BODY_MAX_APPARENT_SIZE_IN_BYTES,
|
|
4
|
+
BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
|
|
5
|
+
MAX_EVENTS_IN_BULK_API,
|
|
6
|
+
} from "./constants";
|
|
7
|
+
import get_request_signature from "./signature";
|
|
8
|
+
import BulkResponse from "./bulk_response";
|
|
9
|
+
import Event from "./event";
|
|
10
|
+
import { SuprsendError } from "./utils";
|
|
11
|
+
import { cloneDeep } from "lodash";
|
|
12
|
+
import axios from "axios";
|
|
13
|
+
|
|
14
|
+
export class BulkEventsFactory {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
new_instance() {
|
|
20
|
+
return new BulkEvents(this.config);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class _BulkEventsChunk {
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.__chunk = [];
|
|
28
|
+
this.__url = this.__get_url();
|
|
29
|
+
this.__headers = this.__common_headers();
|
|
30
|
+
|
|
31
|
+
this.__running_size = 0;
|
|
32
|
+
this.__running_length = 0;
|
|
33
|
+
this.response;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
__get_url() {
|
|
37
|
+
let url_template = "event/";
|
|
38
|
+
if (this.config.include_signature_param) {
|
|
39
|
+
if (this.config.auth_enabled) {
|
|
40
|
+
url_template = url_template + "?verify=true";
|
|
41
|
+
} else {
|
|
42
|
+
url_template = url_template + "?verify=false";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const url_formatted = `${this.config.base_url}${url_template}`;
|
|
46
|
+
return url_formatted;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
__common_headers() {
|
|
50
|
+
return {
|
|
51
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
52
|
+
"User-Agent": this.config.user_agent,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
__dynamic_headers() {
|
|
57
|
+
return {
|
|
58
|
+
Date: new Date().toUTCString(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
__add_event_to_chunk(event, event_size) {
|
|
63
|
+
// First add size, then body to reduce effects of race condition
|
|
64
|
+
this.__running_size += event_size;
|
|
65
|
+
this.__chunk.push(event);
|
|
66
|
+
this.__running_length += 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
__check_limit_reached() {
|
|
70
|
+
if (
|
|
71
|
+
this.__running_length >= MAX_EVENTS_IN_BULK_API ||
|
|
72
|
+
this.__running_size >= BODY_MAX_APPARENT_SIZE_IN_BYTES
|
|
73
|
+
) {
|
|
74
|
+
return true;
|
|
75
|
+
} else {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try_to_add_into_chunk(event, event_size) {
|
|
81
|
+
if (!event) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (this.__check_limit_reached()) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (event_size > BODY_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
88
|
+
throw new SuprsendError(
|
|
89
|
+
`workflow body (discounting attachment if any) too big - ${event_size} Bytes, must not cross ${BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (this.__running_size + event_size > BODY_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (!ALLOW_ATTACHMENTS_IN_BULK_API) {
|
|
96
|
+
delete event.properties["$attachments"];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.__add_event_to_chunk(event, event_size);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async trigger() {
|
|
104
|
+
const headers = { ...this.__headers, ...this.__dynamic_headers() };
|
|
105
|
+
const content_text = JSON.stringify(this.__chunk);
|
|
106
|
+
// Based on whether signature is required or not, add Authorization header
|
|
107
|
+
if (this.config.auth_enabled) {
|
|
108
|
+
const signature = get_request_signature(
|
|
109
|
+
this.__url,
|
|
110
|
+
"POST",
|
|
111
|
+
content_text,
|
|
112
|
+
headers,
|
|
113
|
+
this.config.workspace_secret
|
|
114
|
+
);
|
|
115
|
+
headers["Authorization"] = `${this.config.workspace_key}:${signature}`;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const response = await axios.post(this.__url, content_text, { headers });
|
|
119
|
+
const ok_response = Math.floor(response.status / 100) == 2;
|
|
120
|
+
if (ok_response) {
|
|
121
|
+
this.response = {
|
|
122
|
+
status: "success",
|
|
123
|
+
status_code: response.status,
|
|
124
|
+
total: this.__chunk.length,
|
|
125
|
+
success: this.__chunk.length,
|
|
126
|
+
failure: 0,
|
|
127
|
+
failed_records: [],
|
|
128
|
+
};
|
|
129
|
+
} else {
|
|
130
|
+
this.response = {
|
|
131
|
+
status: "fail",
|
|
132
|
+
status_code: response.status,
|
|
133
|
+
total: this.__chunk.length,
|
|
134
|
+
success: 0,
|
|
135
|
+
failure: this.__chunk.length,
|
|
136
|
+
failed_records: this.__chunk.map((item) => ({
|
|
137
|
+
record: item,
|
|
138
|
+
error: response.statusText,
|
|
139
|
+
code: response.status,
|
|
140
|
+
})),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const error_status = err.status || 500;
|
|
145
|
+
return {
|
|
146
|
+
status: "fail",
|
|
147
|
+
status_code: error_status,
|
|
148
|
+
message: err.message,
|
|
149
|
+
total: this.__chunk.length,
|
|
150
|
+
success: 0,
|
|
151
|
+
failure: this.__chunk.length,
|
|
152
|
+
failed_records: this.__chunk.map((item) => ({
|
|
153
|
+
record: item,
|
|
154
|
+
error: err.message,
|
|
155
|
+
code: error_status,
|
|
156
|
+
})),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class BulkEvents {
|
|
163
|
+
constructor(config) {
|
|
164
|
+
this.config = config;
|
|
165
|
+
this.__events = [];
|
|
166
|
+
this.__pending_records = [];
|
|
167
|
+
this.chunks = [];
|
|
168
|
+
this.response = new BulkResponse();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
__validate_events() {
|
|
172
|
+
if (!this.__events) {
|
|
173
|
+
throw new SuprsendError("events list is empty in bulk request");
|
|
174
|
+
}
|
|
175
|
+
for (let ev of this.__events) {
|
|
176
|
+
const is_part_of_bulk = true;
|
|
177
|
+
const [ev_json, body_size] = ev.get_final_json(
|
|
178
|
+
this.config,
|
|
179
|
+
is_part_of_bulk
|
|
180
|
+
);
|
|
181
|
+
this.__pending_records.push([ev_json, body_size]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
__chunkify(start_idx = 0) {
|
|
186
|
+
const curr_chunk = new _BulkEventsChunk(this.config);
|
|
187
|
+
this.chunks.push(curr_chunk);
|
|
188
|
+
const entries = this.__pending_records.slice(start_idx).entries();
|
|
189
|
+
for (const [rel_idx, rec] of entries) {
|
|
190
|
+
const is_added = curr_chunk.try_to_add_into_chunk(rec[0], rec[1]);
|
|
191
|
+
if (!is_added) {
|
|
192
|
+
// create chunks from remaining records
|
|
193
|
+
this.__chunkify(start_idx + rel_idx);
|
|
194
|
+
// Don't forget to break. As current loop must not continue further
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
append(...events) {
|
|
201
|
+
if (!events) {
|
|
202
|
+
throw new SuprsendError(
|
|
203
|
+
"events list empty. must pass one or more events"
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
for (let ev of events) {
|
|
207
|
+
if (!ev) {
|
|
208
|
+
throw new SuprsendError("null/empty element found in bulk instance");
|
|
209
|
+
}
|
|
210
|
+
if (!(ev instanceof Event)) {
|
|
211
|
+
throw new SuprsendError(
|
|
212
|
+
"element must be an instance of suprsend.Event"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const ev_copy = cloneDeep(ev);
|
|
216
|
+
this.__events.push(ev_copy);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async trigger() {
|
|
221
|
+
this.__validate_events();
|
|
222
|
+
this.__chunkify();
|
|
223
|
+
for (const [c_idx, ch] of this.chunks.entries()) {
|
|
224
|
+
if (this.config.req_log_level > 0) {
|
|
225
|
+
console.log(`DEBUG: triggering api call for chunk: ${c_idx}`);
|
|
226
|
+
}
|
|
227
|
+
// do api call
|
|
228
|
+
await ch.trigger();
|
|
229
|
+
// merge response
|
|
230
|
+
this.response.merge_chunk_response(ch.response);
|
|
231
|
+
}
|
|
232
|
+
return this.response;
|
|
233
|
+
}
|
|
234
|
+
}
|