@suprsend/node-sdk 0.1.1 → 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/src/index.js CHANGED
@@ -1,36 +1,62 @@
1
- import config from "./config";
2
- import Workflow from "./workflow";
3
- import path from "path";
4
- import mime from "mime-types";
5
- import { base64Encode, resolveTilde, SuprsendError } from "./utils";
6
- import UserIdentityFactory from "./identity";
7
- import EventCollector from "./event";
1
+ import { SuprsendError, SuprsendConfigError } from "./utils";
2
+ import get_attachment_json_for_file from "./attachment";
3
+ import Workflow, { _WorkflowTrigger } from "./workflow";
4
+ import { BulkWorkflowsFactory } from "./workflows_bulk";
5
+ import Event, { EventCollector } from "./event";
6
+ import { BulkEventsFactory } from "./events_bulk";
7
+ import SubscriberFactory from "./subscriber";
8
+ import BulkSubscribersFactory from "./subscribers_bulk";
9
+ import { DEFAULT_UAT_URL, DEFAULT_URL } from "./constants";
8
10
 
9
11
  const package_json = require("../package.json");
10
12
 
11
13
  class Suprsend {
12
- constructor(workspace_env, workspace_secret, config = {}) {
13
- this.env_key = workspace_env;
14
- this.env_secret = workspace_secret;
14
+ constructor(workspace_key, workspace_secret, config = {}) {
15
+ this.workspace_key = workspace_key;
16
+ this.workspace_secret = workspace_secret;
15
17
  this.config = config;
18
+
16
19
  this.base_url = this._get_url(config.base_url);
17
20
  this.auth_enabled = config.auth_enabled !== false;
18
21
  this.include_signature_param = config.include_signature_param !== false;
19
22
  this.user_agent = `suprsend/${
20
23
  package_json.version
21
24
  };node/${process.version.slice(1)}`;
22
- this.user = new UserIdentityFactory(this);
25
+
26
+ this._workflow_trigger = new _WorkflowTrigger(this);
23
27
  this._eventcollector = new EventCollector(this);
28
+
29
+ this._bulk_workflows = new BulkWorkflowsFactory(this);
30
+ this._bulk_events = new BulkEventsFactory(this);
31
+ this._bulk_users = new BulkSubscribersFactory(this);
32
+
33
+ this._user = new SubscriberFactory(this);
24
34
  this._validate();
25
35
  }
26
36
 
37
+ get bulk_workflows() {
38
+ return this._bulk_workflows;
39
+ }
40
+
41
+ get bulk_events() {
42
+ return this._bulk_events;
43
+ }
44
+
45
+ get bulk_users() {
46
+ return this._bulk_users;
47
+ }
48
+
49
+ get user() {
50
+ return this._user;
51
+ }
52
+
27
53
  _validate() {
28
- if (!this.env_key) {
29
- throw new SuprsendError("Missing Mandatory WORKSPACE_ENVIRONEMENT");
30
- } else if (!this.env_secret) {
31
- throw new SuprsendError("Missing Mandatory WORKSPACE_SECRET");
54
+ if (!this.workspace_key) {
55
+ throw new SuprsendConfigError("Missing workspace_key");
56
+ } else if (!this.workspace_secret) {
57
+ throw new SuprsendConfigError("Missing workspace_secret");
32
58
  } else if (!this.base_url) {
33
- throw new SuprsendError("Missing Mandatory base url");
59
+ throw new SuprsendConfigError("Missing base_url");
34
60
  }
35
61
  }
36
62
 
@@ -40,9 +66,9 @@ class Suprsend {
40
66
  }
41
67
  if (!base_url) {
42
68
  if (this.config.is_staging) {
43
- base_url = config.staging;
69
+ base_url = DEFAULT_UAT_URL;
44
70
  } else {
45
- base_url = config.prod;
71
+ base_url = DEFAULT_URL;
46
72
  }
47
73
  }
48
74
  base_url = base_url.trim();
@@ -59,7 +85,7 @@ class Suprsend {
59
85
  if (!body.data instanceof Object) {
60
86
  throw new SuprsendError("data must be an object");
61
87
  }
62
- const attachment = this._get_attachment_json_for_file(file_path);
88
+ const attachment = get_attachment_json_for_file(file_path);
63
89
  if (!body.data["$attachments"]) {
64
90
  body["data"]["$attachments"] = [];
65
91
  }
@@ -67,24 +93,32 @@ class Suprsend {
67
93
  return body;
68
94
  }
69
95
 
70
- _get_attachment_json_for_file(file_path) {
71
- const abs_path = path.resolve(resolveTilde(file_path));
72
- return {
73
- filename: path.basename(abs_path),
74
- contentType: mime.lookup(abs_path),
75
- data: base64Encode(abs_path),
76
- };
96
+ trigger_workflow(data) {
97
+ let wf_ins;
98
+ if (data instanceof Workflow) {
99
+ wf_ins = data;
100
+ } else {
101
+ wf_ins = new Workflow(data);
102
+ }
103
+ return this._workflow_trigger.trigger(wf_ins);
77
104
  }
78
105
 
79
- trigger_workflow(data) {
80
- const wf = new Workflow(this, data);
81
- wf.validate_data();
82
- return wf.execute_workflow();
106
+ track(distinct_id, event_name, properties = {}, idempotency_key) {
107
+ const event = new Event(
108
+ distinct_id,
109
+ event_name,
110
+ properties,
111
+ idempotency_key
112
+ );
113
+ return this._eventcollector.collect(event);
83
114
  }
84
115
 
85
- track(distinct_id, event_name, properties = {}) {
86
- return this._eventcollector.collect(distinct_id, event_name, properties);
116
+ track_event(event) {
117
+ if (!(event instanceof Event)) {
118
+ throw new SuprsendError("argument must be an instance of suprsend.Event");
119
+ }
120
+ return this._eventcollector.collect(event);
87
121
  }
88
122
  }
89
123
 
90
- export default Suprsend;
124
+ export { Suprsend, Event, Workflow };
@@ -6,6 +6,11 @@
6
6
  "$comment": "Json schema for Track event",
7
7
  "type": "object",
8
8
  "properties": {
9
+ "$idempotency_key": {
10
+ "type": ["string", "null"],
11
+ "maxLength": 64,
12
+ "description": "unique id provided by client for request"
13
+ },
9
14
  "$insert_id": {
10
15
  "type": "string",
11
16
  "minLength": 36,
@@ -6,6 +6,11 @@
6
6
  "$comment": "Json schema for workflow request",
7
7
  "type": "object",
8
8
  "properties": {
9
+ "$idempotency_key": {
10
+ "type": ["string", "null"],
11
+ "maxLength": 64,
12
+ "description": "unique id provided by client for request"
13
+ },
9
14
  "name": {
10
15
  "$ref": "#/definitions/non_empty_string",
11
16
  "description": "name of workflow"
@@ -71,6 +76,58 @@
71
76
  "pattern": "value in email format required. e.g. user@example.com"
72
77
  }
73
78
  },
79
+ "slack_setting": {
80
+ "type": "object",
81
+ "properties": {
82
+ "user_id": {
83
+ "type": "string",
84
+ "pattern": "^[uUwW][a-zA-Z0-9]+$",
85
+ "description": "slack member id of user",
86
+ "message": {
87
+ "required": "",
88
+ "pattern": "Slack Member id format: [U|W]XXXXXXXXX"
89
+ }
90
+ },
91
+ "email": { "$ref": "#/definitions/email_pattern" }
92
+ },
93
+ "additionalProperties": false
94
+ },
95
+ "androidpush_settings": {
96
+ "type": "object",
97
+ "properties": {
98
+ "token": {
99
+ "type": "string",
100
+ "minLength": 1,
101
+ "description": "androidpush token"
102
+ },
103
+ "provider": {
104
+ "type": "string",
105
+ "enum": ["fcm", "xiaomi", "oppo"],
106
+ "description": "androidpush token provider(fcm/xiaomi)"
107
+ },
108
+ "device_id": { "type": ["string", "null"] }
109
+ },
110
+ "required": ["token", "provider"],
111
+ "additionalProperties": false
112
+ },
113
+ "iospush_settings": {
114
+ "type": "object",
115
+ "properties": {
116
+ "token": {
117
+ "type": "string",
118
+ "minLength": 1,
119
+ "description": "iospush token"
120
+ },
121
+ "provider": {
122
+ "type": "string",
123
+ "enum": ["apns"],
124
+ "description": "iospush token provider(apns)"
125
+ },
126
+ "device_id": { "type": ["string", "null"] }
127
+ },
128
+ "required": ["token", "provider"],
129
+ "additionalProperties": false
130
+ },
74
131
  "user_setting": {
75
132
  "type": "object",
76
133
  "properties": {
@@ -79,12 +136,31 @@
79
136
  "minLength": 1,
80
137
  "description": "distinct_id: Id which uniquely identify a user in your app"
81
138
  },
139
+ "$channels": {
140
+ "type": "array",
141
+ "items": {
142
+ "type": "string",
143
+ "enum": [
144
+ "androidpush",
145
+ "iospush",
146
+ "webpush",
147
+ "email",
148
+ "sms",
149
+ "whatsapp",
150
+ "slack",
151
+ "inbox",
152
+ "messenger"
153
+ ]
154
+ },
155
+ "minItems": 0,
156
+ "description": "user preferred channels. notification will be tried only on specified channels e.g ['email', 'sms']"
157
+ },
82
158
  "$email": {
83
159
  "oneOf": [
84
160
  { "$ref": "#/definitions/email_pattern" },
85
161
  {
86
162
  "type": "array",
87
- "uniqueItems": true,
163
+ "uniqueItems": false,
88
164
  "maxItems": 10,
89
165
  "minItems": 1,
90
166
  "items": { "$ref": "#/definitions/email_pattern" }
@@ -96,7 +172,7 @@
96
172
  { "$ref": "#/definitions/mobile_number_pattern" },
97
173
  {
98
174
  "type": "array",
99
- "uniqueItems": true,
175
+ "uniqueItems": false,
100
176
  "maxItems": 10,
101
177
  "minItems": 1,
102
178
  "items": { "$ref": "#/definitions/mobile_number_pattern" }
@@ -105,10 +181,18 @@
105
181
  },
106
182
  "$androidpush": {
107
183
  "oneOf": [
184
+ { "$ref": "#/definitions/androidpush_settings" },
185
+ {
186
+ "type": "array",
187
+ "uniqueItems": false,
188
+ "maxItems": 10,
189
+ "minItems": 1,
190
+ "items": { "$ref": "#/definitions/androidpush_settings" }
191
+ },
108
192
  { "$ref": "#/definitions/non_empty_string" },
109
193
  {
110
194
  "type": "array",
111
- "uniqueItems": true,
195
+ "uniqueItems": false,
112
196
  "maxItems": 10,
113
197
  "minItems": 1,
114
198
  "items": { "$ref": "#/definitions/non_empty_string" }
@@ -117,10 +201,18 @@
117
201
  },
118
202
  "$iospush": {
119
203
  "oneOf": [
204
+ { "$ref": "#/definitions/iospush_settings" },
205
+ {
206
+ "type": "array",
207
+ "uniqueItems": false,
208
+ "maxItems": 10,
209
+ "minItems": 1,
210
+ "items": { "$ref": "#/definitions/iospush_settings" }
211
+ },
120
212
  { "$ref": "#/definitions/non_empty_string" },
121
213
  {
122
214
  "type": "array",
123
- "uniqueItems": true,
215
+ "uniqueItems": false,
124
216
  "maxItems": 10,
125
217
  "minItems": 1,
126
218
  "items": { "$ref": "#/definitions/non_empty_string" }
@@ -132,7 +224,7 @@
132
224
  { "$ref": "#/definitions/mobile_number_pattern" },
133
225
  {
134
226
  "type": "array",
135
- "uniqueItems": true,
227
+ "uniqueItems": false,
136
228
  "maxItems": 10,
137
229
  "minItems": 1,
138
230
  "items": { "$ref": "#/definitions/mobile_number_pattern" }
@@ -144,19 +236,31 @@
144
236
  { "type": "object", "minProperties": 1 },
145
237
  {
146
238
  "type": "array",
147
- "uniqueItems": true,
239
+ "uniqueItems": false,
148
240
  "maxItems": 10,
149
241
  "minItems": 1,
150
242
  "items": { "type": "object", "minProperties": 1 }
151
243
  }
152
244
  ]
153
245
  },
246
+ "$slack": {
247
+ "oneOf": [
248
+ { "$ref": "#/definitions/slack_setting" },
249
+ {
250
+ "type": "array",
251
+ "uniqueItems": false,
252
+ "maxItems": 10,
253
+ "minItems": 1,
254
+ "items": { "$ref": "#/definitions/slack_setting" }
255
+ }
256
+ ]
257
+ },
154
258
  "$inbox": {
155
259
  "oneOf": [
156
260
  { "$ref": "#/definitions/non_empty_string" },
157
261
  {
158
262
  "type": "array",
159
- "uniqueItems": true,
263
+ "uniqueItems": false,
160
264
  "maxItems": 10,
161
265
  "minItems": 1,
162
266
  "items": { "$ref": "#/definitions/non_empty_string" }
@@ -168,7 +272,7 @@
168
272
  { "$ref": "#/definitions/non_empty_string" },
169
273
  {
170
274
  "type": "array",
171
- "uniqueItems": true,
275
+ "uniqueItems": false,
172
276
  "maxItems": 10,
173
277
  "minItems": 1,
174
278
  "items": { "$ref": "#/definitions/non_empty_string" }
@@ -207,7 +311,10 @@
207
311
  "webpush",
208
312
  "email",
209
313
  "sms",
210
- "whatsapp"
314
+ "whatsapp",
315
+ "slack",
316
+ "inbox",
317
+ "messenger"
211
318
  ]
212
319
  },
213
320
  "minItems": 0,
@@ -5,17 +5,27 @@ import {
5
5
  uuid,
6
6
  is_empty,
7
7
  is_string,
8
+ get_apparent_identity_event_size,
8
9
  } from "./utils";
9
10
  import get_request_signature from "./signature";
10
11
  import axios from "axios";
11
- import _IdentityEventInternalHelper from "./identity_helper";
12
+ import _IdentityEventInternalHelper from "./subscriber_helper";
13
+ import {
14
+ IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES,
15
+ IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
16
+ } from "./constants";
17
+ import _SubscriberInternalHelper from "./subscriber_helper";
12
18
 
13
- export default class UserIdentityFactory {
19
+ export default class SubscriberFactory {
14
20
  constructor(config) {
15
21
  this.config = config;
16
22
  }
17
23
 
18
24
  new_user(distinct_id) {
25
+ return this.get_instance(distinct_id);
26
+ }
27
+
28
+ get_instance(distinct_id) {
19
29
  if (!is_string(distinct_id)) {
20
30
  throw new SuprsendError(
21
31
  "distinct_id must be a string. an Id which uniquely identify a user in your app"
@@ -25,11 +35,11 @@ export default class UserIdentityFactory {
25
35
  if (!distinct_id) {
26
36
  throw new SuprsendError("distinct_id must be passed");
27
37
  }
28
- return new UserIdentity(this.config, distinct_id);
38
+ return new Subscriber(this.config, distinct_id);
29
39
  }
30
40
  }
31
41
 
32
- class UserIdentity {
42
+ export class Subscriber {
33
43
  constructor(config, distinct_id) {
34
44
  this.config = config;
35
45
  this.distinct_id = distinct_id;
@@ -40,10 +50,11 @@ class UserIdentity {
40
50
  this.__info = [];
41
51
  this._append_count = 0;
42
52
  this._remove_count = 0;
53
+ this._unset_count = 0;
43
54
  this._events = [];
44
- this._helper = new _IdentityEventInternalHelper(
55
+ this._helper = new _SubscriberInternalHelper(
45
56
  distinct_id,
46
- config.env_key
57
+ config.workspace_key
47
58
  );
48
59
  }
49
60
 
@@ -73,47 +84,69 @@ class UserIdentity {
73
84
  };
74
85
  }
75
86
 
76
- __get_events() {
77
- let all_events = this._events;
87
+ events() {
88
+ let all_events = [...this._events];
78
89
  for (let e of all_events) {
79
90
  e["properties"] = this.__supr_props;
80
91
  }
81
92
 
82
- if (this._append_count > 0) {
93
+ if (all_events.length === 0 || this._append_count > 0) {
83
94
  const user_identify_event = {
84
95
  $insert_id: uuid(),
85
96
  $time: epoch_milliseconds(),
86
- env: this.config.env_key,
97
+ env: this.config.workspace_key,
87
98
  event: "$identify",
88
99
  properties: {
89
- $anon_id: this.distinct_id,
90
100
  $identified_id: this.distinct_id,
91
- ...this.__super_properties,
101
+ ...this.__supr_props,
92
102
  },
93
103
  };
94
- all_events.push(user_identify_event);
104
+ all_events = [user_identify_event, ...all_events];
95
105
  }
96
106
  return all_events;
97
107
  }
98
108
 
99
- __validate_body() {
109
+ validate_event_size(event_dict) {
110
+ const apparent_size = get_apparent_identity_event_size(event_dict);
111
+ if (apparent_size > IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
112
+ throw new SuprsendError(
113
+ `User Event size too big - ${apparent_size} Bytes, must not cross ${IDENTITY_SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
114
+ );
115
+ }
116
+ return [event_dict, apparent_size];
117
+ }
118
+
119
+ validate_body(is_part_of_bulk = false) {
120
+ const warnings_list = [];
100
121
  if (!is_empty(this.__info)) {
101
- console.log("WARNING: " + this.__info.join("\n"));
122
+ const msg = `[distinct_id: ${this.distinct_id}]${this.__info.join("\n")}`;
123
+ warnings_list.push(msg);
124
+ console.log(`WARNING: ${msg}`);
102
125
  }
103
126
  if (!is_empty(this.__errors)) {
104
- throw new SuprsendError("ERROR: " + this.__errors.join("\n"));
105
- }
106
- if (is_empty(this._events)) {
107
- throw new SuprsendError(
108
- "ERROR: no user properties have been edited. Use user.append/remove/unset method to update user properties"
109
- );
127
+ const msg = `[distinct_id: ${this.distinct_id}] ${this.__errors.join(
128
+ "\n"
129
+ )}`;
130
+ warnings_list.push(msg);
131
+ const err_msg = `ERROR: ${msg}`;
132
+ if (is_part_of_bulk) {
133
+ console.log(err_msg);
134
+ } else {
135
+ throw new SuprsendError(err_msg);
136
+ }
110
137
  }
138
+ return warnings_list;
111
139
  }
112
140
 
113
141
  async save() {
114
- this.__validate_body();
142
+ const is_part_of_bulk = false;
143
+ this.validate_body(is_part_of_bulk);
115
144
  const headers = this.__get_headers();
116
- const events = this.__get_events();
145
+ const events = this.events();
146
+ for (let ev of events) {
147
+ const [validated_ev, size] = this.validate_event_size(ev);
148
+ }
149
+
117
150
  const content_text = JSON.stringify(events);
118
151
  if (this.config.auth_enabled) {
119
152
  const signature = get_request_signature(
@@ -121,23 +154,33 @@ class UserIdentity {
121
154
  "POST",
122
155
  content_text,
123
156
  headers,
124
- this.config.env_secret
157
+ this.config.workspace_secret
125
158
  );
126
- headers["Authorization"] = `${this.config.env_key}:${signature}`;
159
+ headers["Authorization"] = `${this.config.workspace_key}:${signature}`;
127
160
  }
128
161
  try {
129
- const response = await axios.post(this.__url, content_text, {
130
- headers,
131
- });
132
- return {
133
- status_code: response.status,
134
- success: true,
135
- message: response.statusText,
136
- };
162
+ const response = await axios.post(this.__url, content_text, { headers });
163
+ const ok_response = Math.floor(response.status / 100) == 2;
164
+ if (ok_response) {
165
+ return {
166
+ success: true,
167
+ status: "success",
168
+ status_code: response.status,
169
+ message: response.statusText,
170
+ };
171
+ } else {
172
+ return {
173
+ success: false,
174
+ status: "fail",
175
+ status_code: response.status,
176
+ message: response.statusText,
177
+ };
178
+ }
137
179
  } catch (err) {
138
180
  return {
139
- status_code: 400,
140
181
  success: false,
182
+ status: "fail",
183
+ status_code: err.status || 500,
141
184
  message: err.message,
142
185
  };
143
186
  }
@@ -207,6 +250,23 @@ class UserIdentity {
207
250
  }
208
251
  }
209
252
 
253
+ unset(key) {
254
+ const caller = "unset";
255
+ if (!is_string(key) && !Array.isArray(key)) {
256
+ this.__errors.push(`[${caller}] key must be either string or array`);
257
+ return;
258
+ }
259
+ if (is_string(key)) {
260
+ this._helper._unset_k(key, caller);
261
+ this._collect_event();
262
+ } else {
263
+ for (let item of key) {
264
+ this._helper._unset_k(item, caller);
265
+ }
266
+ this._collect_event();
267
+ }
268
+ }
269
+
210
270
  add_email(email) {
211
271
  const caller = "add_email";
212
272
  this._helper._add_email(email, caller);
@@ -278,4 +338,28 @@ class UserIdentity {
278
338
  this._helper._remove_webpush(push_token, provider, caller);
279
339
  this._collect_event();
280
340
  }
341
+
342
+ add_slack_email(value) {
343
+ const caller = "add_slack_email";
344
+ this._helper._add_slack({ email: value }, caller);
345
+ this._collect_event();
346
+ }
347
+
348
+ remove_slack_email(value) {
349
+ const caller = "remove_slack_email";
350
+ this._helper._remove_slack({ email: value }, caller);
351
+ this._collect_event();
352
+ }
353
+
354
+ add_slack_userid(value) {
355
+ const caller = "add_slack_userid";
356
+ this._helper._add_slack({ user_id: value }, caller);
357
+ this._collect_event();
358
+ }
359
+
360
+ remove_slack_userid(value) {
361
+ const caller = "remove_slack_userid";
362
+ this._helper._remove_slack({ user_id: value }, caller);
363
+ this._collect_event();
364
+ }
281
365
  }