@suprsend/node-sdk 1.7.0 → 1.8.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 +21 -12
- package/dist/bulk_response.js +24 -0
- package/dist/event.js +55 -19
- package/dist/events_bulk.js +63 -43
- package/dist/index.js +4 -4
- package/dist/subscriber.js +22 -8
- package/dist/subscriber_list.js +56 -5
- package/dist/subscribers_bulk.js +70 -48
- package/dist/utils.js +41 -3
- package/dist/workflow.js +34 -7
- package/dist/workflows_bulk.js +60 -46
- package/package.json +1 -1
- package/src/attachment.js +23 -11
- package/src/bulk_response.js +22 -0
- package/src/event.js +50 -14
- package/src/events_bulk.js +45 -32
- package/src/index.js +7 -5
- package/src/subscriber.js +20 -8
- package/src/subscriber_list.js +50 -3
- package/src/subscribers_bulk.js +50 -30
- package/src/utils.js +21 -2
- package/src/workflow.js +26 -6
- package/src/workflows_bulk.js +42 -34
- package/types/index.d.ts +5 -0
package/src/bulk_response.js
CHANGED
|
@@ -32,4 +32,26 @@ export default class BulkResponse {
|
|
|
32
32
|
const failed_recs = ch_resp.failed_records || [];
|
|
33
33
|
this.failed_records = [...this.failed_records, ...failed_recs];
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
static empty_chunk_success_response() {
|
|
37
|
+
return {
|
|
38
|
+
status: "success",
|
|
39
|
+
status_code: 200,
|
|
40
|
+
total: 0,
|
|
41
|
+
success: 0,
|
|
42
|
+
failure: 0,
|
|
43
|
+
failed_records: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static invalid_records_chunk_response(invalid_records) {
|
|
48
|
+
return {
|
|
49
|
+
status: "fail",
|
|
50
|
+
status_code: 500,
|
|
51
|
+
total: invalid_records.length,
|
|
52
|
+
success: 0,
|
|
53
|
+
failure: invalid_records.length,
|
|
54
|
+
failed_records: invalid_records,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
35
57
|
}
|
package/src/event.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
epoch_milliseconds,
|
|
7
7
|
validate_track_event_schema,
|
|
8
8
|
get_apparent_event_size,
|
|
9
|
+
InputValueError,
|
|
9
10
|
} from "./utils";
|
|
10
11
|
import get_request_signature from "./signature";
|
|
11
12
|
import axios from "axios";
|
|
@@ -32,38 +33,36 @@ export default class Event {
|
|
|
32
33
|
this.properties = properties;
|
|
33
34
|
this.idempotency_key = kwargs?.idempotency_key;
|
|
34
35
|
this.brand_id = kwargs?.brand_id;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.
|
|
38
|
-
|
|
36
|
+
|
|
37
|
+
// default values
|
|
38
|
+
if (!this.properties) {
|
|
39
|
+
this.properties = {};
|
|
40
|
+
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
__validate_distinct_id() {
|
|
42
|
-
if (this.distinct_id
|
|
43
|
-
throw new
|
|
44
|
+
if (typeof this.distinct_id !== "string") {
|
|
45
|
+
throw new InputValueError(
|
|
44
46
|
"distinct_id must be a string. an Id which uniquely identify a user in your app"
|
|
45
47
|
);
|
|
46
48
|
}
|
|
47
49
|
const distinct_id = this.distinct_id.trim();
|
|
48
50
|
if (!distinct_id) {
|
|
49
|
-
throw new
|
|
51
|
+
throw new InputValueError("distinct_id missing");
|
|
50
52
|
}
|
|
51
53
|
this.distinct_id = distinct_id;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
__validate_properties() {
|
|
55
|
-
if (!this.properties) {
|
|
56
|
-
this.properties = {};
|
|
57
|
-
}
|
|
58
57
|
if (!(this.properties instanceof Object)) {
|
|
59
|
-
throw new
|
|
58
|
+
throw new InputValueError("properties must be a dictionary");
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
__check_event_prefix(event_name) {
|
|
64
63
|
if (!RESERVED_EVENT_NAMES.includes(event_name)) {
|
|
65
64
|
if (has_special_char(event_name)) {
|
|
66
|
-
throw new
|
|
65
|
+
throw new InputValueError(
|
|
67
66
|
"event_names starting with [$,ss_] are reserved by SuprSend"
|
|
68
67
|
);
|
|
69
68
|
}
|
|
@@ -72,9 +71,12 @@ export default class Event {
|
|
|
72
71
|
|
|
73
72
|
__validate_event_name() {
|
|
74
73
|
if (!is_string(this.event_name)) {
|
|
75
|
-
throw new
|
|
74
|
+
throw new InputValueError("event_name must be a string");
|
|
76
75
|
}
|
|
77
76
|
const event_name = this.event_name.trim();
|
|
77
|
+
if (!event_name) {
|
|
78
|
+
throw new InputValueError("event_name missing");
|
|
79
|
+
}
|
|
78
80
|
this.__check_event_prefix(event_name);
|
|
79
81
|
this.event_name = event_name;
|
|
80
82
|
}
|
|
@@ -82,11 +84,25 @@ export default class Event {
|
|
|
82
84
|
add_attachment(file_path, kwargs = {}) {
|
|
83
85
|
const file_name = kwargs?.file_name;
|
|
84
86
|
const ignore_if_error = kwargs?.ignore_if_error ?? false;
|
|
87
|
+
|
|
88
|
+
// if properties is not a dict, not raising error while adding attachment.
|
|
89
|
+
if (!(this.properties instanceof Object)) {
|
|
90
|
+
console.log(
|
|
91
|
+
"WARNING: attachment cannot be added. please make sure properties is a dictionary. Event" +
|
|
92
|
+
JSON.stringify(this.as_json())
|
|
93
|
+
);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
const attachment = get_attachment_json(
|
|
86
98
|
file_path,
|
|
87
99
|
file_name,
|
|
88
100
|
ignore_if_error
|
|
89
101
|
);
|
|
102
|
+
|
|
103
|
+
if (!attachment) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
90
106
|
// --- add the attachment to properties->$attachments
|
|
91
107
|
if (!this.properties["$attachments"]) {
|
|
92
108
|
this.properties["$attachments"] = [];
|
|
@@ -95,6 +111,11 @@ export default class Event {
|
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
get_final_json(config, is_part_of_bulk = false) {
|
|
114
|
+
// --- validate
|
|
115
|
+
this.__validate_distinct_id();
|
|
116
|
+
this.__validate_event_name();
|
|
117
|
+
this.__validate_properties();
|
|
118
|
+
|
|
98
119
|
const super_props = { $ss_sdk_version: config.user_agent };
|
|
99
120
|
let event_dict = {
|
|
100
121
|
$insert_id: uuid(),
|
|
@@ -113,12 +134,27 @@ export default class Event {
|
|
|
113
134
|
event_dict = validate_track_event_schema(event_dict);
|
|
114
135
|
const apparent_size = get_apparent_event_size(event_dict, is_part_of_bulk);
|
|
115
136
|
if (apparent_size > SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
116
|
-
throw new
|
|
137
|
+
throw new InputValueError(
|
|
117
138
|
`Event size too big - ${apparent_size} Bytes,must not cross ${SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
|
|
118
139
|
);
|
|
119
140
|
}
|
|
120
141
|
return [event_dict, apparent_size];
|
|
121
142
|
}
|
|
143
|
+
|
|
144
|
+
as_json() {
|
|
145
|
+
const event_dict = {
|
|
146
|
+
event: this.event_name,
|
|
147
|
+
distinct_id: this.distinct_id,
|
|
148
|
+
properties: this.properties,
|
|
149
|
+
};
|
|
150
|
+
if (this.idempotency_key) {
|
|
151
|
+
event_dict["$idempotency_key"] = this.idempotency_key;
|
|
152
|
+
}
|
|
153
|
+
if (this.brand_id) {
|
|
154
|
+
event_dict["brand_id"] = this.brand_id;
|
|
155
|
+
}
|
|
156
|
+
return event_dict;
|
|
157
|
+
}
|
|
122
158
|
}
|
|
123
159
|
|
|
124
160
|
export class EventCollector {
|
package/src/events_bulk.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import get_request_signature from "./signature";
|
|
10
10
|
import BulkResponse from "./bulk_response";
|
|
11
11
|
import Event from "./event";
|
|
12
|
-
import { SuprsendError } from "./utils";
|
|
12
|
+
import { InputValueError, SuprsendError, invalid_record_json } from "./utils";
|
|
13
13
|
import { cloneDeep } from "lodash";
|
|
14
14
|
import axios from "axios";
|
|
15
15
|
|
|
@@ -79,7 +79,7 @@ class _BulkEventsChunk {
|
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
81
|
if (event_size > SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
82
|
-
throw new
|
|
82
|
+
throw new InputValueError(
|
|
83
83
|
`Event properties too big - ${event_size} Bytes, must not cross ${SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
|
|
84
84
|
);
|
|
85
85
|
}
|
|
@@ -135,7 +135,7 @@ class _BulkEventsChunk {
|
|
|
135
135
|
}
|
|
136
136
|
} catch (err) {
|
|
137
137
|
const error_status = err.status || 500;
|
|
138
|
-
|
|
138
|
+
this.response = {
|
|
139
139
|
status: "fail",
|
|
140
140
|
status_code: error_status,
|
|
141
141
|
message: err.message,
|
|
@@ -159,19 +159,23 @@ class BulkEvents {
|
|
|
159
159
|
this.__pending_records = [];
|
|
160
160
|
this.chunks = [];
|
|
161
161
|
this.response = new BulkResponse();
|
|
162
|
+
// invalid_record json: {"record": event-json, "error": error_str, "code": 500}
|
|
163
|
+
this.__invalid_records = [];
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
__validate_events() {
|
|
165
|
-
if (!this.__events) {
|
|
166
|
-
throw new SuprsendError("events list is empty in bulk request");
|
|
167
|
-
}
|
|
168
167
|
for (let ev of this.__events) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
try {
|
|
169
|
+
const is_part_of_bulk = true;
|
|
170
|
+
const [ev_json, body_size] = ev.get_final_json(
|
|
171
|
+
this.config,
|
|
172
|
+
is_part_of_bulk
|
|
173
|
+
);
|
|
174
|
+
this.__pending_records.push([ev_json, body_size]);
|
|
175
|
+
} catch (ex) {
|
|
176
|
+
const inv_rec = invalid_record_json(ev.as_json(), ex);
|
|
177
|
+
this.__invalid_records.push(inv_rec);
|
|
178
|
+
}
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
|
|
@@ -192,35 +196,44 @@ class BulkEvents {
|
|
|
192
196
|
|
|
193
197
|
append(...events) {
|
|
194
198
|
if (!events) {
|
|
195
|
-
|
|
196
|
-
"events list empty. must pass one or more events"
|
|
197
|
-
);
|
|
199
|
+
return;
|
|
198
200
|
}
|
|
199
201
|
for (let ev of events) {
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (!(ev instanceof Event)) {
|
|
204
|
-
throw new SuprsendError(
|
|
205
|
-
"element must be an instance of suprsend.Event"
|
|
206
|
-
);
|
|
202
|
+
if (ev && ev instanceof Event) {
|
|
203
|
+
const ev_copy = cloneDeep(ev);
|
|
204
|
+
this.__events.push(ev_copy);
|
|
207
205
|
}
|
|
208
|
-
const ev_copy = cloneDeep(ev);
|
|
209
|
-
this.__events.push(ev_copy);
|
|
210
206
|
}
|
|
211
207
|
}
|
|
212
208
|
|
|
213
209
|
async trigger() {
|
|
214
210
|
this.__validate_events();
|
|
215
|
-
this.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
if (this.__invalid_records.length > 0) {
|
|
212
|
+
const ch_response = BulkResponse.invalid_records_chunk_response(
|
|
213
|
+
this.__invalid_records
|
|
214
|
+
);
|
|
215
|
+
this.response.merge_chunk_response(ch_response);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.__pending_records.length) {
|
|
219
|
+
this.__chunkify();
|
|
220
|
+
for (const [c_idx, ch] of this.chunks.entries()) {
|
|
221
|
+
if (this.config.req_log_level > 0) {
|
|
222
|
+
console.log(`DEBUG: triggering api call for chunk: ${c_idx}`);
|
|
223
|
+
}
|
|
224
|
+
// do api call
|
|
225
|
+
await ch.trigger();
|
|
226
|
+
// merge response
|
|
227
|
+
this.response.merge_chunk_response(ch.response);
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
// if no records. i.e. invalid_records.length and pending_records.length both are 0
|
|
231
|
+
// then add empty success response
|
|
232
|
+
if (this.__invalid_records.length === 0) {
|
|
233
|
+
this.response.merge_chunk_response(
|
|
234
|
+
BulkResponse.empty_chunk_success_response()
|
|
235
|
+
);
|
|
219
236
|
}
|
|
220
|
-
// do api call
|
|
221
|
-
await ch.trigger();
|
|
222
|
-
// merge response
|
|
223
|
-
this.response.merge_chunk_response(ch.response);
|
|
224
237
|
}
|
|
225
238
|
return this.response;
|
|
226
239
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SuprsendConfigError, InputValueError } from "./utils";
|
|
2
2
|
import get_attachment_json from "./attachment";
|
|
3
3
|
import Workflow, { _WorkflowTrigger } from "./workflow";
|
|
4
4
|
import { BulkWorkflowsFactory } from "./workflows_bulk";
|
|
@@ -85,11 +85,11 @@ class Suprsend {
|
|
|
85
85
|
add_attachment(body, file_path, kwargs = {}) {
|
|
86
86
|
const file_name = kwargs?.file_name;
|
|
87
87
|
const ignore_if_error = kwargs?.ignore_if_error ?? false;
|
|
88
|
-
if (!body
|
|
88
|
+
if (!body?.data) {
|
|
89
89
|
body.data = {};
|
|
90
90
|
}
|
|
91
|
-
if (!body.data instanceof Object) {
|
|
92
|
-
throw new
|
|
91
|
+
if (!(body.data instanceof Object)) {
|
|
92
|
+
throw new InputValueError("data must be an object");
|
|
93
93
|
}
|
|
94
94
|
const attachment = get_attachment_json(
|
|
95
95
|
file_path,
|
|
@@ -120,7 +120,9 @@ class Suprsend {
|
|
|
120
120
|
|
|
121
121
|
track_event(event) {
|
|
122
122
|
if (!(event instanceof Event)) {
|
|
123
|
-
throw new
|
|
123
|
+
throw new InputValueError(
|
|
124
|
+
"argument must be an instance of suprsend.Event"
|
|
125
|
+
);
|
|
124
126
|
}
|
|
125
127
|
return this._eventcollector.collect(event);
|
|
126
128
|
}
|
package/src/subscriber.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
is_empty,
|
|
7
7
|
is_string,
|
|
8
8
|
get_apparent_identity_event_size,
|
|
9
|
+
InputValueError,
|
|
9
10
|
} from "./utils";
|
|
10
11
|
import get_request_signature from "./signature";
|
|
11
12
|
import axios from "axios";
|
|
@@ -27,13 +28,13 @@ export default class SubscriberFactory {
|
|
|
27
28
|
|
|
28
29
|
get_instance(distinct_id) {
|
|
29
30
|
if (!is_string(distinct_id)) {
|
|
30
|
-
throw new
|
|
31
|
+
throw new InputValueError(
|
|
31
32
|
"distinct_id must be a string. an Id which uniquely identify a user in your app"
|
|
32
33
|
);
|
|
33
34
|
}
|
|
34
35
|
distinct_id = distinct_id.trim();
|
|
35
36
|
if (!distinct_id) {
|
|
36
|
-
throw new
|
|
37
|
+
throw new InputValueError("distinct_id must be passed");
|
|
37
38
|
}
|
|
38
39
|
return new Subscriber(this.config, distinct_id);
|
|
39
40
|
}
|
|
@@ -50,6 +51,7 @@ export class Subscriber {
|
|
|
50
51
|
this.__info = [];
|
|
51
52
|
this.user_operations = [];
|
|
52
53
|
this._helper = new _SubscriberInternalHelper();
|
|
54
|
+
this.__warnings_list = [];
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
__get_url() {
|
|
@@ -82,10 +84,20 @@ export class Subscriber {
|
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
as_json() {
|
|
88
|
+
const event_dict = {
|
|
89
|
+
distinct_id: this.distinct_id,
|
|
90
|
+
$user_operations: this.user_operations,
|
|
91
|
+
warnings: this.__warnings_list,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return event_dict;
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
validate_event_size(event_dict) {
|
|
86
98
|
const apparent_size = get_apparent_identity_event_size(event_dict);
|
|
87
99
|
if (apparent_size > IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
88
|
-
throw new
|
|
100
|
+
throw new InputValueError(
|
|
89
101
|
`User Event size too big - ${apparent_size} Bytes, must not cross ${IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
|
|
90
102
|
);
|
|
91
103
|
}
|
|
@@ -93,25 +105,25 @@ export class Subscriber {
|
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
validate_body(is_part_of_bulk = false) {
|
|
96
|
-
|
|
108
|
+
this.__warnings_list = [];
|
|
97
109
|
if (!is_empty(this.__info)) {
|
|
98
110
|
const msg = `[distinct_id: ${this.distinct_id}]${this.__info.join("\n")}`;
|
|
99
|
-
|
|
111
|
+
this.__warnings_list.push(msg);
|
|
100
112
|
console.log(`WARNING: ${msg}`);
|
|
101
113
|
}
|
|
102
114
|
if (!is_empty(this.__errors)) {
|
|
103
115
|
const msg = `[distinct_id: ${this.distinct_id}] ${this.__errors.join(
|
|
104
116
|
"\n"
|
|
105
117
|
)}`;
|
|
106
|
-
|
|
118
|
+
this.__warnings_list.push(msg);
|
|
107
119
|
const err_msg = `ERROR: ${msg}`;
|
|
108
120
|
if (is_part_of_bulk) {
|
|
109
121
|
console.log(err_msg);
|
|
110
122
|
} else {
|
|
111
|
-
throw new
|
|
123
|
+
throw new InputValueError(err_msg);
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
|
-
return
|
|
126
|
+
return this.__warnings_list;
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
async save() {
|
package/src/subscriber_list.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
get_apparent_list_broadcast_body_size,
|
|
6
6
|
uuid,
|
|
7
7
|
epoch_milliseconds,
|
|
8
|
+
InputValueError,
|
|
8
9
|
} from "./utils";
|
|
9
10
|
import get_request_signature from "./signature";
|
|
10
11
|
import axios from "axios";
|
|
@@ -12,17 +13,51 @@ import {
|
|
|
12
13
|
SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES,
|
|
13
14
|
SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
|
|
14
15
|
} from "./constants";
|
|
16
|
+
import get_attachment_json from "./attachment";
|
|
15
17
|
|
|
16
18
|
class SubscriberListBroadcast {
|
|
17
19
|
constructor(body, kwargs = {}) {
|
|
18
20
|
if (!(body instanceof Object)) {
|
|
19
|
-
throw new
|
|
21
|
+
throw new InputValueError("broadcast body must be a json/dictionary");
|
|
20
22
|
}
|
|
21
23
|
this.body = body;
|
|
22
24
|
this.idempotency_key = kwargs?.idempotency_key;
|
|
23
25
|
this.brand_id = kwargs?.brand_id;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
add_attachment(file_path, kwargs = {}) {
|
|
29
|
+
const file_name = kwargs?.file_name;
|
|
30
|
+
const ignore_if_error = kwargs?.ignore_if_error ?? false;
|
|
31
|
+
|
|
32
|
+
if (!this.body?.["data"]) {
|
|
33
|
+
this.body["data"] = {};
|
|
34
|
+
}
|
|
35
|
+
// if body["data"] is not a dict, not raising error while adding attachment.
|
|
36
|
+
if (!(this.body["data"] instanceof Object)) {
|
|
37
|
+
console.log(
|
|
38
|
+
"WARNING: attachment cannot be added. please make sure body['data'] is a dictionary. " +
|
|
39
|
+
"SubscriberListBroadcast" +
|
|
40
|
+
JSON.stringify(this.as_json())
|
|
41
|
+
);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const attachment = get_attachment_json(
|
|
45
|
+
file_path,
|
|
46
|
+
file_name,
|
|
47
|
+
ignore_if_error
|
|
48
|
+
);
|
|
49
|
+
if (!attachment) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- add the attachment to body->data->$attachments
|
|
54
|
+
if (!this.body["data"]?.["$attachments"]) {
|
|
55
|
+
this.body["data"]["$attachments"] = [];
|
|
56
|
+
}
|
|
57
|
+
// -----
|
|
58
|
+
this.body["data"]["$attachments"].push(attachment);
|
|
59
|
+
}
|
|
60
|
+
|
|
26
61
|
get_final_json() {
|
|
27
62
|
this.body["$insert_id"] = uuid();
|
|
28
63
|
this.body["$time"] = epoch_milliseconds();
|
|
@@ -35,12 +70,24 @@ class SubscriberListBroadcast {
|
|
|
35
70
|
this.body = validate_list_broadcast_body_schema(this.body);
|
|
36
71
|
const apparent_size = get_apparent_list_broadcast_body_size(this.body);
|
|
37
72
|
if (apparent_size > SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
38
|
-
throw new
|
|
73
|
+
throw new InputValueError(
|
|
39
74
|
`SubscriberListBroadcast body too big - ${apparent_size} Bytes, must not cross ${SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
|
|
40
75
|
);
|
|
41
76
|
}
|
|
42
77
|
return [this.body, apparent_size];
|
|
43
78
|
}
|
|
79
|
+
|
|
80
|
+
as_json() {
|
|
81
|
+
const body_dict = { ...this.body };
|
|
82
|
+
if (this.idempotency_key) {
|
|
83
|
+
body_dict["$idempotency_key"] = this.idempotency_key;
|
|
84
|
+
}
|
|
85
|
+
if (this.brand_id) {
|
|
86
|
+
body_dict["brand_id"] = this.brand_id;
|
|
87
|
+
}
|
|
88
|
+
// -----
|
|
89
|
+
return body_dict;
|
|
90
|
+
}
|
|
44
91
|
}
|
|
45
92
|
|
|
46
93
|
class SubscriberListsApi {
|
|
@@ -243,7 +290,7 @@ class SubscriberListsApi {
|
|
|
243
290
|
|
|
244
291
|
async broadcast(broadcast_instance) {
|
|
245
292
|
if (!(broadcast_instance instanceof SubscriberListBroadcast)) {
|
|
246
|
-
throw new
|
|
293
|
+
throw new InputValueError(
|
|
247
294
|
"argument must be an instance of suprsend.SubscriberListBroadcast"
|
|
248
295
|
);
|
|
249
296
|
}
|
package/src/subscribers_bulk.js
CHANGED
|
@@ -9,6 +9,7 @@ import BulkResponse from "./bulk_response";
|
|
|
9
9
|
import { Subscriber } from "./subscriber";
|
|
10
10
|
import { cloneDeep } from "lodash";
|
|
11
11
|
import axios from "axios";
|
|
12
|
+
import { invalid_record_json, InputValueError } from "./utils";
|
|
12
13
|
|
|
13
14
|
export default class BulkSubscribersFactory {
|
|
14
15
|
constructor(config) {
|
|
@@ -75,7 +76,7 @@ class _BulkSubscribersChunk {
|
|
|
75
76
|
return false;
|
|
76
77
|
}
|
|
77
78
|
if (event_size > IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
|
|
78
|
-
throw new
|
|
79
|
+
throw new InputValueError(
|
|
79
80
|
`Event too big - ${event_size} Bytes, must not cross ${IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
|
|
80
81
|
);
|
|
81
82
|
}
|
|
@@ -128,7 +129,7 @@ class _BulkSubscribersChunk {
|
|
|
128
129
|
}
|
|
129
130
|
} catch (err) {
|
|
130
131
|
const error_status = err.status || 500;
|
|
131
|
-
|
|
132
|
+
this.response = {
|
|
132
133
|
status: "fail",
|
|
133
134
|
status_code: error_status,
|
|
134
135
|
message: err.message,
|
|
@@ -152,21 +153,29 @@ class BulkSubscribers {
|
|
|
152
153
|
this.__pending_records = [];
|
|
153
154
|
this.chunks = [];
|
|
154
155
|
this.response = new BulkResponse();
|
|
156
|
+
|
|
157
|
+
// invalid_record json: {"record": event-json, "error": error_str, "code": 500}
|
|
158
|
+
this.__invalid_records = [];
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
__validate_subscriber_events() {
|
|
158
|
-
if (!this.__subscribers) {
|
|
159
|
-
throw new SuprsendError("users list is empty in bulk request");
|
|
160
|
-
}
|
|
161
162
|
for (let sub of this.__subscribers) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
try {
|
|
164
|
+
const is_part_of_bulk = true;
|
|
165
|
+
const warnings_list = sub.validate_body(is_part_of_bulk);
|
|
166
|
+
if (warnings_list) {
|
|
167
|
+
this.response.warnings = [
|
|
168
|
+
...this.response.warnings,
|
|
169
|
+
...warnings_list,
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
const ev = sub.get_events();
|
|
173
|
+
const [ev_json, body_size] = sub.validate_event_size(ev);
|
|
174
|
+
this.__pending_records.push([ev_json, body_size]);
|
|
175
|
+
} catch (ex) {
|
|
176
|
+
const inv_rec = invalid_record_json(sub.as_json(), ex);
|
|
177
|
+
this.__invalid_records.push(inv_rec);
|
|
166
178
|
}
|
|
167
|
-
const ev = sub.get_events();
|
|
168
|
-
const [ev_json, body_size] = sub.validate_event_size(ev);
|
|
169
|
-
this.__pending_records.push([ev_json, body_size]);
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
|
|
@@ -187,19 +196,13 @@ class BulkSubscribers {
|
|
|
187
196
|
|
|
188
197
|
append(...subscribers) {
|
|
189
198
|
if (!subscribers) {
|
|
190
|
-
|
|
199
|
+
return;
|
|
191
200
|
}
|
|
192
201
|
for (let sub of subscribers) {
|
|
193
|
-
if (
|
|
194
|
-
|
|
202
|
+
if (sub && sub instanceof Subscriber) {
|
|
203
|
+
const sub_copy = cloneDeep(sub);
|
|
204
|
+
this.__subscribers.push(sub_copy);
|
|
195
205
|
}
|
|
196
|
-
if (!(sub instanceof Subscriber)) {
|
|
197
|
-
throw new SuprsendError(
|
|
198
|
-
"element must be an instance of suprsend.Subscriber"
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
const sub_copy = cloneDeep(sub);
|
|
202
|
-
this.__subscribers.push(sub_copy);
|
|
203
206
|
}
|
|
204
207
|
}
|
|
205
208
|
|
|
@@ -209,16 +212,33 @@ class BulkSubscribers {
|
|
|
209
212
|
|
|
210
213
|
async save() {
|
|
211
214
|
this.__validate_subscriber_events();
|
|
212
|
-
this.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
if (this.__invalid_records.length > 0) {
|
|
216
|
+
const ch_response = BulkResponse.invalid_records_chunk_response(
|
|
217
|
+
this.__invalid_records
|
|
218
|
+
);
|
|
219
|
+
this.response.merge_chunk_response(ch_response);
|
|
220
|
+
}
|
|
221
|
+
if (this.__pending_records.length) {
|
|
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
|
+
} else {
|
|
233
|
+
// if no records. i.e. invalid_records.length and pending_records.length both are 0
|
|
234
|
+
// then add empty success response
|
|
235
|
+
if (this.__invalid_records.length === 0) {
|
|
236
|
+
this.response.merge_chunk_response(
|
|
237
|
+
BulkResponse.empty_chunk_success_response()
|
|
238
|
+
);
|
|
216
239
|
}
|
|
217
|
-
// do api call
|
|
218
|
-
await ch.trigger();
|
|
219
|
-
// merge response
|
|
220
|
-
this.response.merge_chunk_response(ch.response);
|
|
221
240
|
}
|
|
241
|
+
|
|
222
242
|
return this.response;
|
|
223
243
|
}
|
|
224
244
|
}
|
package/src/utils.js
CHANGED
|
@@ -58,6 +58,13 @@ export class SuprsendApiError extends Error {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export class InputValueError extends Error {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "InputValueError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
export function is_string(value) {
|
|
62
69
|
return typeof value === "string";
|
|
63
70
|
}
|
|
@@ -93,7 +100,7 @@ export function validate_workflow_body_schema(body) {
|
|
|
93
100
|
body.data = {};
|
|
94
101
|
}
|
|
95
102
|
if (!(body.data instanceof Object)) {
|
|
96
|
-
throw new
|
|
103
|
+
throw new InputValueError("data must be a object");
|
|
97
104
|
}
|
|
98
105
|
const schema = workflow_schema;
|
|
99
106
|
var v = new Validator();
|
|
@@ -128,7 +135,7 @@ export function validate_list_broadcast_body_schema(body) {
|
|
|
128
135
|
body.data = {};
|
|
129
136
|
}
|
|
130
137
|
if (!(body.data instanceof Object)) {
|
|
131
|
-
throw new
|
|
138
|
+
throw new InputValueError("data must be a object");
|
|
132
139
|
}
|
|
133
140
|
const schema = list_broadcast_schema;
|
|
134
141
|
var v = new Validator();
|
|
@@ -227,3 +234,15 @@ export function get_apparent_list_broadcast_body_size(body) {
|
|
|
227
234
|
const body_size = JSON.stringify(body).length;
|
|
228
235
|
return body_size;
|
|
229
236
|
}
|
|
237
|
+
|
|
238
|
+
export function invalid_record_json(failed_record, err) {
|
|
239
|
+
let err_str;
|
|
240
|
+
if (err instanceof InputValueError) {
|
|
241
|
+
err_str = err.message;
|
|
242
|
+
} else {
|
|
243
|
+
// includes SuprsendValidationError,
|
|
244
|
+
// OR any other error
|
|
245
|
+
err_str = `${err.message}\n${err.stack}`;
|
|
246
|
+
}
|
|
247
|
+
return { record: failed_record, error: err_str, code: 500 };
|
|
248
|
+
}
|