@suprsend/node-sdk 0.1.1 → 1.1.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.
@@ -7,22 +7,71 @@ import {
7
7
  is_string,
8
8
  } from "./utils";
9
9
 
10
+ // ---------- Identity keys
11
+ const IDENT_KEY_EMAIL = "$email";
12
+ const IDENT_KEY_SMS = "$sms";
13
+ const IDENT_KEY_ANDROIDPUSH = "$androidpush";
14
+ const IDENT_KEY_IOSPUSH = "$iospush";
15
+ const IDENT_KEY_WHATSAPP = "$whatsapp";
16
+ const IDENT_KEY_WEBPUSH = "$webpush";
17
+ const IDENT_KEY_SLACK = "$slack";
18
+
19
+ const IDENT_KEYS_ALL = [
20
+ IDENT_KEY_EMAIL,
21
+ IDENT_KEY_SMS,
22
+ IDENT_KEY_ANDROIDPUSH,
23
+ IDENT_KEY_IOSPUSH,
24
+ IDENT_KEY_WHATSAPP,
25
+ IDENT_KEY_WEBPUSH,
26
+ IDENT_KEY_SLACK,
27
+ ];
28
+
29
+ const KEY_PUSHVENDOR = "$pushvendor";
30
+
31
+ const OTHER_RESERVED_KEYS = [
32
+ "$messenger",
33
+ "$inbox",
34
+ KEY_PUSHVENDOR,
35
+ "$device_id",
36
+ "$insert_id",
37
+ "$time",
38
+ "$set",
39
+ "$set_once",
40
+ "$add",
41
+ "$append",
42
+ "$remove",
43
+ "$unset",
44
+ "$identify",
45
+ "$anon_id",
46
+ "$identified_id",
47
+ "$notification_delivered",
48
+ "$notification_dismiss",
49
+ "$notification_clicked",
50
+ ];
51
+
52
+ const SUPER_PROPERTY_KEYS = [
53
+ "$app_version_string",
54
+ "$app_build_number",
55
+ "$brand",
56
+ "$carrier",
57
+ "$manufacturer",
58
+ "$model",
59
+ "$os",
60
+ "$ss_sdk_version",
61
+ "$insert_id",
62
+ "$time",
63
+ ];
64
+
65
+ const ALL_RESERVED_KEYS = [
66
+ ...SUPER_PROPERTY_KEYS,
67
+ ...OTHER_RESERVED_KEYS,
68
+ ...IDENT_KEYS_ALL,
69
+ ];
70
+
10
71
  const EMAIL_REGEX = /\S+@\S+\.\S+/;
11
72
  const MOBILE_REGEX = /^\+[0-9\s]+/;
12
- const KEY_PUSHVENDOR = "$pushvendor";
13
- const CHANNEL_MAP = {
14
- EMAIL: "$email",
15
- SMS: "$sms",
16
- WHATSAPP: "$whatsapp",
17
- ANDROID_PUSH: "$androidpush",
18
- IOS_PUSH: "$iospush",
19
- WEB_PUSH: "$webpush",
20
- };
21
- const ANDROID_PUSH_VENDORS = ["fcm", "xiaomi", "oppo"];
22
- const IOS_PUSH_VENDORS = ["apns"];
23
- const WEB_PUSH_VENDORS = ["vapid"];
24
-
25
- export default class _IdentityEventInternalHelper {
73
+
74
+ export default class _SubscriberInternalHelper {
26
75
  constructor(distinct_id, workspace_key) {
27
76
  this.distinct_id = distinct_id;
28
77
  this.workspace_key = workspace_key;
@@ -69,7 +118,11 @@ export default class _IdentityEventInternalHelper {
69
118
  }
70
119
 
71
120
  __form_event() {
72
- if (!is_empty(this.__dict_append) || !is_empty(this.__dict_remove)) {
121
+ if (
122
+ !is_empty(this.__dict_append) ||
123
+ !is_empty(this.__dict_remove) ||
124
+ !is_empty(this.__list_unset)
125
+ ) {
73
126
  let event = {
74
127
  $insert_id: uuid(),
75
128
  $time: epoch_milliseconds(),
@@ -84,9 +137,14 @@ export default class _IdentityEventInternalHelper {
84
137
  event["$remove"] = this.__dict_remove;
85
138
  this.__remove_count += 1;
86
139
  }
140
+ if (!is_empty(this.__list_unset)) {
141
+ event["$unset"] = this.__list_unset;
142
+ this.__unset_count += 1;
143
+ }
87
144
  return event;
145
+ } else {
146
+ return;
88
147
  }
89
- return;
90
148
  }
91
149
 
92
150
  __validate_key_basic(key, caller) {
@@ -105,7 +163,7 @@ export default class _IdentityEventInternalHelper {
105
163
  }
106
164
 
107
165
  __validate_key_prefix(key, caller) {
108
- if (!this.__is_identity_key(key)) {
166
+ if (!ALL_RESERVED_KEYS.includes(key)) {
109
167
  if (has_special_char(key)) {
110
168
  this.__info.push(
111
169
  `[${caller}] skipping key: ${key}. key starting with [$,ss_] are reserved`
@@ -117,68 +175,79 @@ export default class _IdentityEventInternalHelper {
117
175
  }
118
176
 
119
177
  __is_identity_key(key) {
120
- return Object.values(CHANNEL_MAP).includes(key);
178
+ return IDENT_KEYS_ALL.includes(key);
121
179
  }
122
180
 
123
181
  _append_kv(key, value, args = {}, caller = "append") {
124
- const [validated_key, is_valid] = this.__validate_key_basic(key, caller);
125
- if (!is_valid) {
182
+ const [validated_key, is_k_valid] = this.__validate_key_basic(key, caller);
183
+ if (!is_k_valid) {
126
184
  return;
127
185
  }
128
186
  if (this.__is_identity_key(validated_key)) {
129
187
  this.__add_identity(validated_key, value, args, caller);
130
188
  } else {
131
- const is_valid = this.__validate_key_prefix(validated_key, caller);
132
- if (is_valid) {
189
+ const is_k_valid = this.__validate_key_prefix(validated_key, caller);
190
+ if (is_k_valid) {
133
191
  this.__dict_append[validated_key] = value;
134
192
  }
135
193
  }
136
194
  }
137
195
 
138
196
  _remove_kv(key, value, args = {}, caller = "remove") {
139
- const [validated_key, is_valid] = this.__validate_key_basic(key, caller);
140
- if (!is_valid) {
197
+ const [validated_key, is_k_valid] = this.__validate_key_basic(key, caller);
198
+ if (!is_k_valid) {
141
199
  return;
142
200
  }
143
201
  if (this.__is_identity_key(validated_key)) {
144
202
  this.__remove_identity(validated_key, value, args, caller);
145
203
  } else {
146
- const is_valid = this.__validate_key_prefix(validated_key, caller);
147
- if (is_valid) {
204
+ const is_k_valid = this.__validate_key_prefix(validated_key, caller);
205
+ if (is_k_valid) {
148
206
  this.__dict_remove[validated_key] = value;
149
207
  }
150
208
  }
151
209
  }
152
210
 
211
+ _unset_k(key, caller = "unset") {
212
+ const [validated_key, is_k_valid] = this.__validate_key_basic(key, caller);
213
+ if (!is_k_valid) {
214
+ return;
215
+ }
216
+ this.__list_unset.push(validated_key);
217
+ }
218
+
153
219
  __add_identity(key, value, args, caller) {
154
220
  switch (key) {
155
- case CHANNEL_MAP.EMAIL:
221
+ case IDENT_KEY_EMAIL:
156
222
  this._add_email(value, caller);
157
223
  break;
158
- case CHANNEL_MAP.SMS:
224
+ case IDENT_KEY_SMS:
159
225
  this._add_sms(value, caller);
160
226
  break;
161
- case CHANNEL_MAP.WHATSAPP:
227
+ case IDENT_KEY_WHATSAPP:
162
228
  this._add_whatsapp(value, caller);
163
229
  break;
164
- case CHANNEL_MAP.ANDROID_PUSH:
230
+ case IDENT_KEY_ANDROIDPUSH:
165
231
  this._add_androidpush(value, args[KEY_PUSHVENDOR], caller);
166
232
  if (this.__dict_append[KEY_PUSHVENDOR]) {
167
233
  args[KEY_PUSHVENDOR] = this.__dict_append[KEY_PUSHVENDOR];
168
234
  }
169
235
  break;
170
- case CHANNEL_MAP.IOS_PUSH:
236
+ case IDENT_KEY_IOSPUSH:
171
237
  this._add_iospush(value, args[KEY_PUSHVENDOR], caller);
172
238
  if (this.__dict_append[KEY_PUSHVENDOR]) {
173
239
  args[KEY_PUSHVENDOR] = this.__dict_append[KEY_PUSHVENDOR];
174
240
  }
175
241
  break;
176
- case CHANNEL_MAP.WEB_PUSH:
242
+ case IDENT_KEY_WEBPUSH:
177
243
  this._add_webpush(value, args[KEY_PUSHVENDOR], caller);
178
244
  if (this.__dict_append[KEY_PUSHVENDOR]) {
179
245
  args[KEY_PUSHVENDOR] = this.__dict_append[KEY_PUSHVENDOR];
180
246
  }
181
247
  break;
248
+ case IDENT_KEY_SLACK:
249
+ this._add_slack(value, caller);
250
+ break;
182
251
  default:
183
252
  break;
184
253
  }
@@ -186,33 +255,36 @@ export default class _IdentityEventInternalHelper {
186
255
 
187
256
  __remove_identity(key, value, args, caller) {
188
257
  switch (key) {
189
- case CHANNEL_MAP.EMAIL:
258
+ case IDENT_KEY_EMAIL:
190
259
  this._remove_email(value, caller);
191
260
  break;
192
- case CHANNEL_MAP.SMS:
261
+ case IDENT_KEY_SMS:
193
262
  this._remove_sms(value, caller);
194
263
  break;
195
- case CHANNEL_MAP.WHATSAPP:
264
+ case IDENT_KEY_WHATSAPP:
196
265
  this._remove_whatsapp(value, caller);
197
266
  break;
198
- case CHANNEL_MAP.ANDROID_PUSH:
267
+ case IDENT_KEY_ANDROIDPUSH:
199
268
  this._remove_androidpush(value, args[KEY_PUSHVENDOR], caller);
200
269
  if (this.__dict_remove[KEY_PUSHVENDOR]) {
201
270
  args[KEY_PUSHVENDOR] = this.__dict_remove[KEY_PUSHVENDOR];
202
271
  }
203
272
  break;
204
- case CHANNEL_MAP.IOS_PUSH:
273
+ case IDENT_KEY_IOSPUSH:
205
274
  this._remove_iospush(value, args[KEY_PUSHVENDOR], caller);
206
275
  if (this.__dict_remove[KEY_PUSHVENDOR]) {
207
276
  args[KEY_PUSHVENDOR] = this.__dict_remove[KEY_PUSHVENDOR];
208
277
  }
209
278
  break;
210
- case CHANNEL_MAP.WEB_PUSH:
279
+ case IDENT_KEY_WEBPUSH:
211
280
  this._remove_webpush(value, args[KEY_PUSHVENDOR], caller);
212
281
  if (this.__dict_remove[KEY_PUSHVENDOR]) {
213
282
  args[KEY_PUSHVENDOR] = this.__dict_remove[KEY_PUSHVENDOR];
214
283
  }
215
284
  break;
285
+ case IDENT_KEY_SLACK:
286
+ this._remove_slack(val, caller);
287
+ break;
216
288
  default:
217
289
  break;
218
290
  }
@@ -260,7 +332,7 @@ export default class _IdentityEventInternalHelper {
260
332
  if (!is_valid) {
261
333
  return;
262
334
  }
263
- this.__dict_append[CHANNEL_MAP.EMAIL] = value;
335
+ this.__dict_append[IDENT_KEY_EMAIL] = value;
264
336
  }
265
337
 
266
338
  _remove_email(email, caller) {
@@ -268,7 +340,7 @@ export default class _IdentityEventInternalHelper {
268
340
  if (!is_valid) {
269
341
  return;
270
342
  }
271
- this.__dict_remove[CHANNEL_MAP.EMAIL] = value;
343
+ this.__dict_remove[IDENT_KEY_EMAIL] = value;
272
344
  }
273
345
 
274
346
  // mobile methods
@@ -299,7 +371,7 @@ export default class _IdentityEventInternalHelper {
299
371
  if (!is_valid) {
300
372
  return;
301
373
  }
302
- this.__dict_append[CHANNEL_MAP.SMS] = value;
374
+ this.__dict_append[IDENT_KEY_SMS] = value;
303
375
  }
304
376
 
305
377
  _remove_sms(mobile_no, caller) {
@@ -307,7 +379,7 @@ export default class _IdentityEventInternalHelper {
307
379
  if (!is_valid) {
308
380
  return;
309
381
  }
310
- this.__dict_remove[CHANNEL_MAP.SMS] = value;
382
+ this.__dict_remove[IDENT_KEY_SMS] = value;
311
383
  }
312
384
 
313
385
  _add_whatsapp(mobile_no, caller) {
@@ -315,7 +387,7 @@ export default class _IdentityEventInternalHelper {
315
387
  if (!is_valid) {
316
388
  return;
317
389
  }
318
- this.__dict_append[CHANNEL_MAP.WHATSAPP] = value;
390
+ this.__dict_append[IDENT_KEY_WHATSAPP] = value;
319
391
  }
320
392
 
321
393
  _remove_whatsapp(mobile_no, caller) {
@@ -323,7 +395,7 @@ export default class _IdentityEventInternalHelper {
323
395
  if (!is_valid) {
324
396
  return;
325
397
  }
326
- this.__dict_remove[CHANNEL_MAP.WHATSAPP] = value;
398
+ this.__dict_remove[IDENT_KEY_WHATSAPP] = value;
327
399
  }
328
400
 
329
401
  // android push methods
@@ -335,7 +407,7 @@ export default class _IdentityEventInternalHelper {
335
407
  if (!provider) {
336
408
  provider = "fcm";
337
409
  }
338
- if (!ANDROID_PUSH_VENDORS.includes(provider)) {
410
+ if (!["fcm", "xiaomi", "oppo"].includes(provider)) {
339
411
  this.__errors.push(
340
412
  `[${caller}] unsupported androidpush provider ${provider}`
341
413
  );
@@ -353,7 +425,7 @@ export default class _IdentityEventInternalHelper {
353
425
  if (!is_valid) {
354
426
  return;
355
427
  }
356
- this.__dict_append[CHANNEL_MAP.ANDROID_PUSH] = value;
428
+ this.__dict_append[IDENT_KEY_ANDROIDPUSH] = value;
357
429
  this.__dict_append[KEY_PUSHVENDOR] = vendor;
358
430
  }
359
431
 
@@ -367,7 +439,7 @@ export default class _IdentityEventInternalHelper {
367
439
  if (!is_valid) {
368
440
  return;
369
441
  }
370
- this.__dict_remove[CHANNEL_MAP.ANDROID_PUSH] = value;
442
+ this.__dict_remove[IDENT_KEY_ANDROIDPUSH] = value;
371
443
  this.__dict_remove[KEY_PUSHVENDOR] = vendor;
372
444
  }
373
445
 
@@ -380,7 +452,7 @@ export default class _IdentityEventInternalHelper {
380
452
  if (!provider) {
381
453
  provider = "apns";
382
454
  }
383
- if (!IOS_PUSH_VENDORS.includes(provider)) {
455
+ if (!["apns"].includes(provider)) {
384
456
  this.__errors.push(
385
457
  `[${caller}] unsupported iospush provider ${provider}`
386
458
  );
@@ -398,7 +470,7 @@ export default class _IdentityEventInternalHelper {
398
470
  if (!is_valid) {
399
471
  return;
400
472
  }
401
- this.__dict_append[CHANNEL_MAP.IOS_PUSH] = value;
473
+ this.__dict_append[IDENT_KEY_IOSPUSH] = value;
402
474
  this.__dict_append[KEY_PUSHVENDOR] = vendor;
403
475
  }
404
476
 
@@ -411,7 +483,7 @@ export default class _IdentityEventInternalHelper {
411
483
  if (!is_valid) {
412
484
  return;
413
485
  }
414
- this.__dict_remove[CHANNEL_MAP.IOS_PUSH] = value;
486
+ this.__dict_remove[IDENT_KEY_IOSPUSH] = value;
415
487
  this.__dict_remove[KEY_PUSHVENDOR] = vendor;
416
488
  }
417
489
 
@@ -426,7 +498,7 @@ export default class _IdentityEventInternalHelper {
426
498
  if (!provider) {
427
499
  provider = "vapid";
428
500
  }
429
- if (!WEB_PUSH_VENDORS.includes(provider)) {
501
+ if (!["vapid"].includes(provider)) {
430
502
  this.__errors.push(
431
503
  `[${caller}] unsupported webpush provider ${provider}`
432
504
  );
@@ -444,7 +516,7 @@ export default class _IdentityEventInternalHelper {
444
516
  if (!is_valid) {
445
517
  return;
446
518
  }
447
- this.__dict_append[CHANNEL_MAP.WEB_PUSH] = value;
519
+ this.__dict_append[IDENT_KEY_WEBPUSH] = value;
448
520
  this.__dict_append[KEY_PUSHVENDOR] = vendor;
449
521
  }
450
522
 
@@ -457,7 +529,78 @@ export default class _IdentityEventInternalHelper {
457
529
  if (!is_valid) {
458
530
  return;
459
531
  }
460
- this.__dict_remove[CHANNEL_MAP.WEB_PUSH] = value;
532
+ this.__dict_remove[IDENT_KEY_WEBPUSH] = value;
461
533
  this.__dict_remove[KEY_PUSHVENDOR] = vendor;
462
534
  }
535
+
536
+ __validate_slack_userid(userid, caller) {
537
+ let [validated_userid, is_valid] = this.__check_ident_val_string(
538
+ userid,
539
+ caller
540
+ );
541
+ if (!is_valid) {
542
+ return [validated_userid, false];
543
+ }
544
+ validated_userid = validated_userid.toUpperCase();
545
+ if (
546
+ !validated_userid.startsWith("U") &&
547
+ !validated_userid.startsWith("W")
548
+ ) {
549
+ this.__errors.push(
550
+ `[${caller}] invalid value ${validated_userid}. Slack user/member_id starts with a U or W`
551
+ );
552
+ return [validated_userid, false];
553
+ }
554
+ return [validated_userid, true];
555
+ }
556
+
557
+ __check_slack_dict(value, caller) {
558
+ const msg =
559
+ "value must be a valid dict/json with one of these keys: [email, user_id]";
560
+ if (!(value && value instanceof Object)) {
561
+ this.__errors.push(`[${caller}] ${msg}`);
562
+ return [value, false];
563
+ }
564
+ let user_id = value.user_id;
565
+ let email = value.email;
566
+ if (user_id && user_id.trim()) {
567
+ user_id = user_id.trim();
568
+ let [validated_user_id, is_valid] = this.__validate_slack_userid(
569
+ user_id,
570
+ caller
571
+ );
572
+ if (!is_valid) {
573
+ return [value, false];
574
+ } else {
575
+ return [{ user_id: validated_user_id }, true];
576
+ }
577
+ } else if (email && email.trim()) {
578
+ email = email.trim();
579
+ let [validated_email, is_valid] = this.__validate_email(email, caller);
580
+ if (!is_valid) {
581
+ return [value, false];
582
+ } else {
583
+ return [{ email: validated_email }, true];
584
+ }
585
+ } else {
586
+ this.__errors.push(`[${caller}] ${msg}`);
587
+ return [value, false];
588
+ }
589
+ }
590
+
591
+ _add_slack(value, caller) {
592
+ const [validated_value, is_valid] = this.__check_slack_dict(value, caller);
593
+ if (!is_valid) {
594
+ return;
595
+ }
596
+ this.__dict_append[IDENT_KEY_SLACK] = validated_value;
597
+ }
598
+
599
+ _remove_slack(value, caller) {
600
+ const [validated_value, is_valid] = this.__check_slack_dict(value, caller);
601
+ if (!is_valid) {
602
+ return;
603
+ }
604
+ this.__dict_remove[IDENT_KEY_SLACK] = validated_value;
605
+ }
463
606
  }
@@ -0,0 +1,237 @@
1
+ import {
2
+ IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES,
3
+ IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
4
+ BODY_MAX_APPARENT_SIZE_IN_BYTES,
5
+ BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
6
+ MAX_IDENTITY_EVENTS_IN_BULK_API,
7
+ } from "./constants";
8
+ import get_request_signature from "./signature";
9
+ import BulkResponse from "./bulk_response";
10
+ import { Subscriber } from "./subscriber";
11
+ import { cloneDeep } from "lodash";
12
+ import axios from "axios";
13
+
14
+ export default class BulkSubscribersFactory {
15
+ constructor(config) {
16
+ this.config = config;
17
+ }
18
+
19
+ new_instance() {
20
+ return new BulkSubscribers(this.config);
21
+ }
22
+ }
23
+
24
+ class _BulkSubscribersChunk {
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_IDENTITY_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 > IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
88
+ throw new SuprsendError(
89
+ `Event too big - ${event_size} Bytes, must not cross ${IDENTITY_SINGLE_EVENT_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
+
96
+ this.__add_event_to_chunk(event, event_size);
97
+ return true;
98
+ }
99
+
100
+ async trigger() {
101
+ const headers = { ...this.__headers, ...this.__dynamic_headers() };
102
+ const content_text = JSON.stringify(this.__chunk);
103
+ // Based on whether signature is required or not, add Authorization header
104
+ if (this.config.auth_enabled) {
105
+ const signature = get_request_signature(
106
+ this.__url,
107
+ "POST",
108
+ content_text,
109
+ headers,
110
+ this.config.workspace_secret
111
+ );
112
+ headers["Authorization"] = `${this.config.workspace_key}:${signature}`;
113
+ }
114
+ try {
115
+ const response = await axios.post(this.__url, content_text, { headers });
116
+ const ok_response = Math.floor(response.status / 100) == 2;
117
+ if (ok_response) {
118
+ this.response = {
119
+ status: "success",
120
+ status_code: response.status,
121
+ total: this.__chunk.length,
122
+ success: this.__chunk.length,
123
+ failure: 0,
124
+ failed_records: [],
125
+ };
126
+ } else {
127
+ this.response = {
128
+ status: "fail",
129
+ status_code: response.status,
130
+ total: this.__chunk.length,
131
+ success: 0,
132
+ failure: this.__chunk.length,
133
+ failed_records: this.__chunk.map((item) => ({
134
+ record: item,
135
+ error: response.statusText,
136
+ code: response.status,
137
+ })),
138
+ };
139
+ }
140
+ } catch (err) {
141
+ const error_status = err.status || 500;
142
+ return {
143
+ status: "fail",
144
+ status_code: error_status,
145
+ message: err.message,
146
+ total: this.__chunk.length,
147
+ success: 0,
148
+ failure: this.__chunk.length,
149
+ failed_records: this.__chunk.map((item) => ({
150
+ record: item,
151
+ error: err.message,
152
+ code: error_status,
153
+ })),
154
+ };
155
+ }
156
+ }
157
+ }
158
+
159
+ class BulkSubscribers {
160
+ constructor(config) {
161
+ this.config = config;
162
+ this.__subscribers = [];
163
+ this.__pending_records = [];
164
+ this.chunks = [];
165
+ this.response = new BulkResponse();
166
+ }
167
+
168
+ __validate_subscriber_events() {
169
+ if (!this.__subscribers) {
170
+ throw new SuprsendError("users list is empty in bulk request");
171
+ }
172
+ for (let sub of this.__subscribers) {
173
+ const is_part_of_bulk = true;
174
+ const warnings_list = sub.validate_body(is_part_of_bulk);
175
+ if (warnings_list) {
176
+ this.response.warnings = [...this.response.warnings, ...warnings_list];
177
+ }
178
+ const ev_arr = sub.events();
179
+ for (let ev of ev_arr) {
180
+ const [ev_json, body_size] = sub.validate_event_size(ev);
181
+ this.__pending_records.push([ev_json, body_size]);
182
+ }
183
+ }
184
+ }
185
+
186
+ __chunkify(start_idx = 0) {
187
+ const curr_chunk = new _BulkSubscribersChunk(this.config);
188
+ this.chunks.push(curr_chunk);
189
+ const entries = this.__pending_records.slice(start_idx).entries();
190
+ for (const [rel_idx, rec] of entries) {
191
+ const is_added = curr_chunk.try_to_add_into_chunk(rec[0], rec[1]);
192
+ if (!is_added) {
193
+ // create chunks from remaining records
194
+ this.__chunkify(start_idx + rel_idx);
195
+ // Don't forget to break. As current loop must not continue further
196
+ break;
197
+ }
198
+ }
199
+ }
200
+
201
+ append(...subscribers) {
202
+ if (!subscribers) {
203
+ throw SuprsendError("users list empty. must pass one or more users");
204
+ }
205
+ for (let sub of subscribers) {
206
+ if (!sub) {
207
+ throw new SuprsendError("null/empty element found in bulk instance");
208
+ }
209
+ if (!(sub instanceof Subscriber)) {
210
+ throw new SuprsendError(
211
+ "element must be an instance of suprsend.Subscriber"
212
+ );
213
+ }
214
+ const sub_copy = cloneDeep(sub);
215
+ this.__subscribers.push(sub_copy);
216
+ }
217
+ }
218
+
219
+ trigger() {
220
+ this.save();
221
+ }
222
+
223
+ async save() {
224
+ this.__validate_subscriber_events();
225
+ this.__chunkify();
226
+ for (const [c_idx, ch] of this.chunks.entries()) {
227
+ if (this.config.req_log_level > 0) {
228
+ console.log(`DEBUG: triggering api call for chunk: ${c_idx}`);
229
+ }
230
+ // do api call
231
+ await ch.trigger();
232
+ // merge response
233
+ this.response.merge_chunk_response(ch.response);
234
+ }
235
+ return this.response;
236
+ }
237
+ }