@suprsend/web-sdk 1.5.1 → 2.0.1

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.
@@ -1,505 +0,0 @@
1
- import create_signature from "./encryption";
2
- import config from "./config";
3
- import utils from "./utils";
4
-
5
- export const PreferenceOptions = { OPT_IN: "opt_in", OPT_OUT: "opt_out" };
6
- export const ChannelLevelPreferenceOptions = {
7
- ALL: "all",
8
- REQUIRED: "required",
9
- };
10
-
11
- class Preferences {
12
- constructor(instance, emitter) {
13
- this.ss_instance = instance;
14
- this._preference_data;
15
- this._preference_args; // used internally after over_all_channel_preferences update
16
- this._emitter = emitter;
17
-
18
- this._debounced_update_category_preferences = utils.debounce_by_type(
19
- this._update_category_preferences,
20
- config.preference_debounce
21
- );
22
- this._debounced_update_channel_preferences = utils.debounce_by_type(
23
- this._update_channel_preferences,
24
- config.preference_debounce
25
- );
26
- }
27
-
28
- _validate_query_params(query_params = {}) {
29
- let validated_params = {};
30
- for (let key in query_params) {
31
- if (query_params[key]) {
32
- validated_params[key] = query_params[key];
33
- }
34
- }
35
- return validated_params;
36
- }
37
-
38
- async _get_request(route = "", query_params = {}) {
39
- const preference_base_url = `/v1/subscriber/${this.ss_instance.distinct_id}`;
40
- const validated_query_params = this._validate_query_params(query_params);
41
- const query_params_string = new URLSearchParams(
42
- validated_query_params
43
- ).toString();
44
-
45
- const full_url_path = query_params_string
46
- ? `${preference_base_url}/${route}/?${query_params_string}`
47
- : `${preference_base_url}/${route}`;
48
-
49
- const requested_date = new Date().toGMTString();
50
- const signature = await create_signature(
51
- "",
52
- requested_date,
53
- "GET",
54
- full_url_path
55
- );
56
- const authorization = signature
57
- ? `${config.env_key}:${signature}`
58
- : config.env_key;
59
-
60
- try {
61
- const resp = await fetch(`${config.api_url}${full_url_path}`, {
62
- headers: {
63
- "Content-Type": "application/json",
64
- Authorization: authorization,
65
- "x-amz-date": requested_date,
66
- },
67
- });
68
- if (resp.ok) {
69
- const respData = resp.json();
70
- return respData;
71
- }
72
- return {
73
- error: true,
74
- api_error: true,
75
- status_code: resp.status,
76
- message: resp.statusText,
77
- error_obj: null,
78
- };
79
- } catch (e) {
80
- return {
81
- error: true,
82
- api_error: false,
83
- status_code: null,
84
- message: e.message,
85
- error_obj: e,
86
- };
87
- }
88
- }
89
-
90
- async _update_request(body, route, query_params) {
91
- const preference_base_url = `/v1/subscriber/${this.ss_instance.distinct_id}`;
92
- const validated_query_params = this._validate_query_params(query_params);
93
- const query_params_string = new URLSearchParams(
94
- validated_query_params
95
- ).toString();
96
-
97
- const full_url_path = query_params_string
98
- ? `${preference_base_url}/${route}/?${query_params_string}`
99
- : `${preference_base_url}/${route}`;
100
-
101
- const requested_date = new Date().toGMTString();
102
- const bodyString = JSON.stringify(body);
103
- const signature = await create_signature(
104
- bodyString,
105
- requested_date,
106
- "POST",
107
- full_url_path
108
- );
109
- const authorization = signature
110
- ? `${config.env_key}:${signature}`
111
- : config.env_key;
112
-
113
- try {
114
- const resp = await fetch(`${config.api_url}${full_url_path}`, {
115
- method: "POST",
116
- body: bodyString,
117
- headers: {
118
- "Content-Type": "application/json",
119
- Authorization: authorization,
120
- "x-amz-date": requested_date,
121
- },
122
- });
123
- if (resp.ok) {
124
- const respData = resp.json();
125
- return respData;
126
- }
127
- return {
128
- error: true,
129
- api_error: true,
130
- status_code: resp.status,
131
- message: resp.statusText,
132
- error_obj: null,
133
- };
134
- } catch (e) {
135
- return {
136
- error: true,
137
- api_error: false,
138
- status_code: null,
139
- message: e.message,
140
- error_obj: e,
141
- };
142
- }
143
- }
144
-
145
- _update_category_preferences = async (
146
- category = "",
147
- body = {},
148
- subcategory,
149
- args = {}
150
- ) => {
151
- let url_path = `category/${category}`;
152
- const response = await this._update_request(body, url_path, args);
153
- if (response?.error) {
154
- this._emitter.emit("preferences_error", response);
155
- } else {
156
- Object.assign(subcategory, response);
157
- this._emitter.emit("preferences_updated", this.data);
158
- }
159
- return response;
160
- };
161
-
162
- _update_channel_preferences = async (body = {}) => {
163
- let url_path = "channel_preference";
164
- const response = await this._update_request(body, url_path);
165
- if (response?.error) {
166
- this._emitter.emit("preferences_error", response);
167
- } else {
168
- await this.get_preferences(this._preference_args);
169
- this._emitter.emit("preferences_updated", this.data);
170
- }
171
- return response;
172
- };
173
-
174
- set data(value) {
175
- this._preference_data = value;
176
- }
177
-
178
- get data() {
179
- return this._preference_data;
180
- }
181
-
182
- async get_preferences(args = {}) {
183
- let url_path = "full_preference";
184
- this._preference_args = args;
185
- let query_params = {
186
- tenant_id: args?.tenant_id,
187
- show_opt_out_channels: args?.show_opt_out_channels,
188
- };
189
-
190
- const response = await this._get_request(url_path, query_params);
191
- if (!response?.error) {
192
- this.data = response;
193
- }
194
- return response;
195
- }
196
-
197
- async get_categories(args = {}) {
198
- let url_path = "category";
199
- const query_params = {
200
- tenant_id: args?.tenant_id,
201
- show_opt_out_channels: args?.show_opt_out_channels,
202
- limit: args?.limit,
203
- offset: args?.offset,
204
- };
205
-
206
- const response = await this._get_request(url_path, query_params);
207
- return response;
208
- }
209
-
210
- async get_category(category = "", args = {}) {
211
- if (!category) {
212
- return {
213
- error: true,
214
- message: "Category parameter is missing",
215
- };
216
- }
217
-
218
- let url_path = `category/${category}`;
219
- let query_params = {
220
- tenant_id: args?.tenant_id,
221
- show_opt_out_channels: args?.show_opt_out_channels,
222
- };
223
-
224
- const response = await this._get_request(url_path, query_params);
225
- return response;
226
- }
227
-
228
- async get_overall_channel_preferences() {
229
- let url_path = `channel_preference`;
230
- const response = await this._get_request(url_path);
231
- return response;
232
- }
233
-
234
- update_category_preference(category = "", preference = "", args = {}) {
235
- if (
236
- !category ||
237
- ![PreferenceOptions.OPT_IN, PreferenceOptions.OPT_OUT].includes(
238
- preference
239
- )
240
- ) {
241
- return {
242
- error: true,
243
- message: !category
244
- ? "Category parameter is missing"
245
- : "Preference parameter is invalid",
246
- };
247
- }
248
-
249
- if (!this.data) {
250
- return {
251
- error: true,
252
- message: "Call get_preferences method before performing action",
253
- };
254
- }
255
-
256
- let category_data;
257
- let data_updated = false;
258
- let show_opt_out_channels = args?.show_opt_out_channels;
259
-
260
- // optimistic update in local store
261
- for (let section of this.data.sections) {
262
- let abort = false;
263
- for (let subcategory of section.subcategories) {
264
- if (subcategory.category === category) {
265
- category_data = subcategory;
266
- if (subcategory.is_editable) {
267
- if (subcategory.preference !== preference) {
268
- subcategory.preference = preference;
269
- data_updated = true;
270
- abort = true;
271
- break;
272
- } else {
273
- // console.log(`category is already ${status}ed`);
274
- }
275
- } else {
276
- return {
277
- error: true,
278
- message: "Category preference is not editable",
279
- };
280
- }
281
- }
282
- }
283
- if (abort) break;
284
- }
285
-
286
- if (!category_data) {
287
- return {
288
- error: true,
289
- message: "Category is not found",
290
- };
291
- }
292
-
293
- if (!data_updated) {
294
- return this.data;
295
- }
296
-
297
- const opt_out_channels = [];
298
- category_data.channels.forEach((channel) => {
299
- if (channel.preference === PreferenceOptions.OPT_OUT) {
300
- opt_out_channels.push(channel.channel);
301
- }
302
- });
303
-
304
- const request_payload = {
305
- preference: category_data.preference,
306
- opt_out_channels:
307
- show_opt_out_channels && preference === PreferenceOptions.OPT_IN
308
- ? null
309
- : opt_out_channels,
310
- };
311
-
312
- this._debounced_update_category_preferences(
313
- category,
314
- category,
315
- request_payload,
316
- category_data,
317
- {
318
- tenant_id: args?.tenant_id,
319
- show_opt_out_channels: args?.show_opt_out_channels,
320
- }
321
- );
322
-
323
- return this.data;
324
- }
325
-
326
- update_channel_preference_in_category(
327
- channel = "",
328
- preference = "",
329
- category = "",
330
- args = {}
331
- ) {
332
- if (!channel || !category) {
333
- return {
334
- error: true,
335
- message: !channel
336
- ? "Channel parameter is missing"
337
- : "Category parameter is missing",
338
- };
339
- } else if (
340
- ![PreferenceOptions.OPT_IN, PreferenceOptions.OPT_OUT].includes(
341
- preference
342
- )
343
- ) {
344
- return {
345
- error: true,
346
- message: "Preference parameter is invalid",
347
- };
348
- }
349
-
350
- if (!this.data) {
351
- return {
352
- error: true,
353
- message: "Call get_preferences method before performing action",
354
- };
355
- }
356
-
357
- let category_data;
358
- let selected_channel_data;
359
- let data_updated = false;
360
-
361
- // optimistic update in local store
362
- for (let section of this.data.sections) {
363
- let abort = false;
364
- for (let subcategory of section.subcategories) {
365
- if (subcategory.category === category) {
366
- category_data = subcategory;
367
- for (let channel_data of subcategory.channels) {
368
- if (channel_data.channel === channel) {
369
- selected_channel_data = channel_data;
370
- if (channel_data.is_editable) {
371
- if (channel_data.preference !== preference) {
372
- channel_data.preference = preference;
373
- if (preference === PreferenceOptions.OPT_IN) {
374
- subcategory.preference = PreferenceOptions.OPT_IN;
375
- }
376
- data_updated = true;
377
- abort = true;
378
- break;
379
- } else {
380
- // console.log(`channel is already ${preference}`);
381
- }
382
- } else {
383
- return {
384
- error: true,
385
- message: "Channel preference is not editable",
386
- };
387
- }
388
- }
389
- }
390
- }
391
- if (abort) break;
392
- }
393
- if (abort) break;
394
- }
395
-
396
- if (!category_data) {
397
- return {
398
- error: true,
399
- message: "Category not found",
400
- };
401
- }
402
-
403
- if (!selected_channel_data) {
404
- return {
405
- error: true,
406
- message: "Category's Channel not found",
407
- };
408
- }
409
-
410
- if (!data_updated) {
411
- return this.data;
412
- }
413
-
414
- const opt_out_channels = [];
415
- category_data.channels.forEach((channel) => {
416
- if (channel.preference === PreferenceOptions.OPT_OUT) {
417
- opt_out_channels.push(channel.channel);
418
- }
419
- });
420
-
421
- const category_preference =
422
- args?.show_opt_out_channels &&
423
- category_data.preference === PreferenceOptions.OPT_OUT &&
424
- preference === PreferenceOptions.OPT_IN
425
- ? PreferenceOptions.OPT_IN
426
- : category_data.preference;
427
-
428
- const request_payload = {
429
- preference: category_preference,
430
- opt_out_channels,
431
- };
432
-
433
- this._debounced_update_category_preferences(
434
- category,
435
- category,
436
- request_payload,
437
- category_data,
438
- {
439
- tenant_id: args?.tenant_id,
440
- show_opt_out_channels: args?.show_opt_out_channels,
441
- }
442
- );
443
-
444
- return this.data;
445
- }
446
-
447
- update_overall_channel_preference(channel = "", preference = "") {
448
- if (
449
- !channel ||
450
- ![
451
- ChannelLevelPreferenceOptions.ALL,
452
- ChannelLevelPreferenceOptions.REQUIRED,
453
- ].includes(preference)
454
- ) {
455
- return {
456
- error: true,
457
- message: !channel
458
- ? "Channel parameter is missing"
459
- : "Preference parameter is invalid",
460
- };
461
- }
462
-
463
- if (!this.data) {
464
- return {
465
- error: true,
466
- message: "Call get_preferences method before performing action",
467
- };
468
- }
469
-
470
- let channel_data;
471
- let data_updated = false;
472
- const preference_restricted =
473
- preference === ChannelLevelPreferenceOptions.REQUIRED;
474
-
475
- for (let channel_item of this.data.channel_preferences) {
476
- if (channel_item.channel === channel) {
477
- channel_data = channel_item;
478
- if (channel_item.is_restricted !== preference_restricted) {
479
- channel_item.is_restricted = preference_restricted;
480
- data_updated = true;
481
- break;
482
- }
483
- }
484
- }
485
-
486
- if (!channel_data) {
487
- return {
488
- error: true,
489
- message: "Channel data not found",
490
- };
491
- }
492
-
493
- if (!data_updated) {
494
- return this.data;
495
- }
496
-
497
- this._debounced_update_channel_preferences(channel_data.channel, {
498
- channel_preferences: [channel_data],
499
- });
500
-
501
- return this.data;
502
- }
503
- }
504
-
505
- export default Preferences;
package/src/user.js DELETED
@@ -1,196 +0,0 @@
1
- import utils from "./utils";
2
- import config from "./config";
3
- import { regex, constants } from "./constants";
4
- import Preferences from "./preferences";
5
-
6
- class User {
7
- constructor(instance, emitter) {
8
- this.instance = instance;
9
- this.preferences = new Preferences(this.instance, emitter);
10
- }
11
-
12
- _call_indentity(properties) {
13
- utils.batch_or_call({
14
- env: config.env_key,
15
- distinct_id: this.instance.distinct_id,
16
- $insert_id: utils.uuid(),
17
- $time: utils.epoch_milliseconds(),
18
- ...properties,
19
- });
20
- }
21
-
22
- _call_flush_immediately(properties) {
23
- utils
24
- .api(constants.api_events_route, {
25
- env: config.env_key,
26
- distinct_id: this.instance.distinct_id,
27
- $insert_id: utils.uuid(),
28
- $time: utils.epoch_milliseconds(),
29
- ...properties,
30
- })
31
- .then((res) => {
32
- if (!res.ok) {
33
- throw new Error("Error in Fetch");
34
- }
35
- })
36
- .catch(() => {
37
- this._call_indentity(properties);
38
- });
39
- }
40
-
41
- _allow_special_char_events = (key) => {
42
- return (
43
- key === "$email" ||
44
- key === "$whatsapp" ||
45
- key === "$sms" ||
46
- key?.["$webpush"]
47
- );
48
- };
49
-
50
- _convert_to_number(obj) {
51
- if (!utils.is_empty(obj)) {
52
- for (let key in obj) {
53
- const number = Number(obj[key]);
54
- if (!number) {
55
- delete obj[key];
56
- } else {
57
- obj[key] = number;
58
- }
59
- }
60
- }
61
- return obj;
62
- }
63
-
64
- _validate_email_and_send(key, email) {
65
- if (regex.email.test(email)) {
66
- this.append(key, email);
67
- } else {
68
- console.log("SuprSend: Provide valid Email ID");
69
- }
70
- }
71
-
72
- _validate_mobile_and_send(key, mobile) {
73
- try {
74
- if (regex.mobile.test(mobile)) {
75
- this.append(key, mobile);
76
- } else {
77
- console.log(
78
- "SuprSend: Provide valid Mobile number as per E.164 standard"
79
- );
80
- }
81
- } catch (err) {
82
- console.log(
83
- "SuprSend: Provide valid Mobile number as per E.164 standard"
84
- );
85
- }
86
- }
87
-
88
- set(key, value) {
89
- const data = utils.format_props({ key, value });
90
- if (!utils.is_empty(data)) {
91
- this._call_indentity({ $set: data });
92
- }
93
- }
94
-
95
- set_once(key, value) {
96
- const data = utils.format_props({ key, value });
97
- if (!utils.is_empty(data)) {
98
- this._call_indentity({ $set_once: data });
99
- }
100
- }
101
-
102
- increment(key, value = 1) {
103
- const data = utils.format_props({ key, value });
104
- const formatted_data = this._convert_to_number(data);
105
- if (!utils.is_empty(formatted_data)) {
106
- this._call_indentity({ $add: formatted_data });
107
- }
108
- }
109
-
110
- append(key, value) {
111
- const allow_special_tags = this._allow_special_char_events(key);
112
- const data = utils.format_props({ key, value, allow_special_tags });
113
- if (!utils.is_empty(data)) {
114
- this._call_indentity({ $append: data });
115
- }
116
- }
117
-
118
- remove(key, value, config = {}) {
119
- const allow_special_tags = this._allow_special_char_events(key);
120
- const data = utils.format_props({ key, value, allow_special_tags });
121
- if (!utils.is_empty(data)) {
122
- if (config?.flush_immediately) {
123
- this._call_flush_immediately({ $remove: data });
124
- } else {
125
- this._call_indentity({ $remove: data });
126
- }
127
- }
128
- }
129
-
130
- unset(key) {
131
- let formatted_data;
132
- if (typeof key === "string") {
133
- if (!utils.has_special_char(key)) {
134
- formatted_data = [key];
135
- } else {
136
- console.log("SuprSend: key cannot start with $ or ss_");
137
- }
138
- } else if (Array.isArray(key)) {
139
- formatted_data = [];
140
- key.forEach((item) => {
141
- if (!utils.has_special_char(String(item))) {
142
- formatted_data.push(String(item));
143
- }
144
- });
145
- }
146
- if (!utils.is_empty(formatted_data)) {
147
- this._call_indentity({ $unset: formatted_data });
148
- }
149
- }
150
-
151
- add_email(email = "") {
152
- this._validate_email_and_send("$email", email);
153
- }
154
-
155
- remove_email(email = "") {
156
- this.remove("$email", email);
157
- }
158
-
159
- add_sms(mobile = "") {
160
- this._validate_mobile_and_send("$sms", mobile);
161
- }
162
-
163
- remove_sms(mobile = "") {
164
- this.remove("$sms", mobile);
165
- }
166
-
167
- add_whatsapp(mobile = "") {
168
- this._validate_mobile_and_send("$whatsapp", mobile);
169
- }
170
-
171
- remove_whatsapp(mobile = "") {
172
- this.remove("$whatsapp", mobile);
173
- }
174
-
175
- add_webpush(push = "") {
176
- this.append({
177
- $webpush: push,
178
- $device_id: this.instance?.env_properties?.$device_id,
179
- $pushvendor: "vapid",
180
- });
181
- }
182
-
183
- remove_webpush(push = "") {
184
- this.remove(
185
- {
186
- $webpush: push,
187
- $device_id: this.instance?.env_properties?.$device_id,
188
- $pushvendor: "vapid",
189
- },
190
- null,
191
- { flush_immediately: true }
192
- );
193
- }
194
- }
195
-
196
- export default User;