@suprsend/node-sdk 1.9.1 → 1.11.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.
@@ -0,0 +1,360 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "http://github.com/suprsend/suprsend-node-sdk/request_json/workflow_trigger.json",
4
+ "title": "workflow_trigger",
5
+ "description": "Json schema for workflow trigger request",
6
+ "$comment": "Json schema for workflow trigger request",
7
+ "type": "object",
8
+ "properties": {
9
+ "$idempotency_key": {
10
+ "type": ["string", "null"],
11
+ "maxLength": 255,
12
+ "description": "unique id provided by client for request"
13
+ },
14
+ "tenant_id": {
15
+ "type": ["string", "null"],
16
+ "maxLength": 64,
17
+ "description": "tenant id for workflow to be run in context of a tenant"
18
+ },
19
+ "workflow": {
20
+ "$ref": "#/definitions/non_empty_string",
21
+ "description": "workflow slug"
22
+ },
23
+ "actor": {
24
+ "oneOf": [
25
+ { "$ref": "#/definitions/non_empty_string" },
26
+ { "$ref": "#/definitions/user_setting" }
27
+ ],
28
+ "description": "actor id/object of workflow"
29
+ },
30
+ "recipients": {
31
+ "type": "array",
32
+ "items": {
33
+ "oneOf": [
34
+ { "$ref": "#/definitions/non_empty_string" },
35
+ { "$ref": "#/definitions/user_setting" }
36
+ ]
37
+ },
38
+ "minItems": 1,
39
+ "maxItems": 100,
40
+ "description": "list of recipient id/object to run workflow for. At least 1 user is required"
41
+ },
42
+ "data": {
43
+ "type": "object",
44
+ "description": "variables to be used in workflow. e.g replacing templates variables."
45
+ },
46
+ "metadata": {
47
+ "type": "object",
48
+ "description": "metadata of request"
49
+ },
50
+ "cancellation_key": {
51
+ "type": ["string", "null"],
52
+ "maxLength": 255,
53
+ "description": "cancellation key for workflow"
54
+ }
55
+ },
56
+ "required": ["workflow", "recipients", "data"],
57
+ "additionalProperties": false,
58
+ "definitions": {
59
+ "non_empty_string": {
60
+ "type": "string",
61
+ "minLength": 1
62
+ },
63
+ "mobile_number_pattern": {
64
+ "type": "string",
65
+ "minLength": 8,
66
+ "pattern": "^\\+[0-9\\s]+",
67
+ "message": {
68
+ "required": "Either a mobile-number or an array of mobile-numbers. e.g [\"+41446681800\"]",
69
+ "pattern": "number must start with + and must contain country code. e.g. +41446681800"
70
+ }
71
+ },
72
+ "email_pattern": {
73
+ "type": "string",
74
+ "format": "email",
75
+ "pattern": "^\\S+@\\S+\\.\\S+$",
76
+ "description": "email of an user",
77
+ "minLength": 6,
78
+ "maxLength": 127,
79
+ "message": {
80
+ "required": "",
81
+ "pattern": "value in email format required. e.g. user@example.com"
82
+ }
83
+ },
84
+ "slack_setting": {
85
+ "type": "object",
86
+ "properties": {
87
+ "access_token": {
88
+ "type": "string",
89
+ "pattern": "^xox",
90
+ "description": "Bot User OAuth Access Token with prefix xoxb-"
91
+ },
92
+ "channel_id": {
93
+ "type": "string",
94
+ "pattern": "^[cC][a-zA-Z0-9]+$",
95
+ "description": "slack channel id. the bot must be part of it",
96
+ "message": {
97
+ "required": "",
98
+ "pattern": "Slack Channel id format: CXXXXXXXXX"
99
+ }
100
+ },
101
+ "user_id": {
102
+ "type": "string",
103
+ "pattern": "^[uUwW][a-zA-Z0-9]+$",
104
+ "description": "slack member id of user",
105
+ "message": {
106
+ "required": "",
107
+ "pattern": "Slack Member id format: [U|W]XXXXXXXXX"
108
+ }
109
+ },
110
+ "email": {
111
+ "$ref": "#/definitions/email_pattern"
112
+ },
113
+ "incoming_webhook": {
114
+ "type": "object",
115
+ "properties": {
116
+ "url": {
117
+ "type": "string",
118
+ "format": "uri",
119
+ "pattern": "^https://hooks.slack.com/",
120
+ "description": "incoming webhook url. e.g https://hooks.slack.com/TXXXXX/BXXXXX/XXXXXXXXXX"
121
+ }
122
+ }
123
+ }
124
+ },
125
+ "additionalProperties": false
126
+ },
127
+ "ms_teams_setting": {
128
+ "type": "object",
129
+ "properties": {
130
+ "tenant_id": {
131
+ "type": "string",
132
+ "description": "ms teams tenant id"
133
+ },
134
+ "service_url": {
135
+ "type": "string",
136
+ "format": "uri",
137
+ "pattern": "^https://smba.trafficmanager.net/",
138
+ "description": "service webhook url. e.g https://smba.trafficmanager.net/XXXXXXXXXX"
139
+ },
140
+ "conversation_id": {
141
+ "type": "string",
142
+ "description": "ms team conversation id"
143
+ },
144
+ "user_id": {
145
+ "type": "string",
146
+ "description": "ms team user id"
147
+ },
148
+ "incoming_webhook": {
149
+ "type": "object",
150
+ "properties": {
151
+ "url": {
152
+ "type": "string",
153
+ "format": "uri",
154
+ "description": "incoming webhook url. e.g https://XXXXX.webhook.office.com/webhookb2/XXXXXXXXXX@XXXXXXXXXX/IncomingWebhook/XXXXXXXXXX/XXXXXXXXXX"
155
+ }
156
+ }
157
+ }
158
+ },
159
+ "additionalProperties": false
160
+ },
161
+ "androidpush_settings": {
162
+ "type": "object",
163
+ "properties": {
164
+ "token": {
165
+ "type": "string",
166
+ "minLength": 1,
167
+ "description": "androidpush token"
168
+ },
169
+ "provider": {
170
+ "type": "string",
171
+ "enum": ["fcm", "xiaomi", "oppo"],
172
+ "description": "androidpush token provider(fcm/xiaomi)"
173
+ },
174
+ "device_id": { "type": ["string", "null"] }
175
+ },
176
+ "required": ["token", "provider"],
177
+ "additionalProperties": false
178
+ },
179
+ "iospush_settings": {
180
+ "type": "object",
181
+ "properties": {
182
+ "token": {
183
+ "type": "string",
184
+ "minLength": 1,
185
+ "description": "iospush token"
186
+ },
187
+ "provider": {
188
+ "type": "string",
189
+ "enum": ["apns"],
190
+ "description": "iospush token provider(apns)"
191
+ },
192
+ "device_id": { "type": ["string", "null"] }
193
+ },
194
+ "required": ["token", "provider"],
195
+ "additionalProperties": false
196
+ },
197
+ "user_setting": {
198
+ "type": "object",
199
+ "properties": {
200
+ "is_transient": {
201
+ "type": ["boolean", "null"],
202
+ "description": "indicates whether this is a transient user. Profiles are not created for such users."
203
+ },
204
+ "distinct_id": {
205
+ "type": ["string", "null"],
206
+ "description": "distinct_id: Id which uniquely identifies a user in your app"
207
+ },
208
+ "$preferred_language": {
209
+ "type": ["string", "null"],
210
+ "description": "preferred_language: 2-letter language code in ISO 639-1 Alpha-2 code format. e.g en (for English)"
211
+ },
212
+ "$timezone": {
213
+ "type": ["string", "null"],
214
+ "description": "timezone: in IANA timezone format e.g 'America/Los_Angeles'"
215
+ },
216
+ "$channels": {
217
+ "type": "array",
218
+ "items": {
219
+ "type": "string",
220
+ "enum": [
221
+ "androidpush",
222
+ "iospush",
223
+ "webpush",
224
+ "email",
225
+ "sms",
226
+ "whatsapp",
227
+ "slack",
228
+ "inbox",
229
+ "messenger",
230
+ "ms_teams"
231
+ ]
232
+ },
233
+ "minItems": 0,
234
+ "description": "user preferred channels. notification will be tried only on specified channels e.g ['email', 'sms']"
235
+ },
236
+ "$email": {
237
+ "oneOf": [
238
+ { "$ref": "#/definitions/email_pattern" },
239
+ {
240
+ "type": "array",
241
+ "uniqueItems": false,
242
+ "maxItems": 10,
243
+ "minItems": 1,
244
+ "items": { "$ref": "#/definitions/email_pattern" }
245
+ }
246
+ ]
247
+ },
248
+ "$sms": {
249
+ "oneOf": [
250
+ { "$ref": "#/definitions/mobile_number_pattern" },
251
+ {
252
+ "type": "array",
253
+ "uniqueItems": false,
254
+ "maxItems": 10,
255
+ "minItems": 1,
256
+ "items": { "$ref": "#/definitions/mobile_number_pattern" }
257
+ }
258
+ ]
259
+ },
260
+ "$androidpush": {
261
+ "oneOf": [
262
+ { "$ref": "#/definitions/androidpush_settings" },
263
+ {
264
+ "type": "array",
265
+ "uniqueItems": false,
266
+ "maxItems": 10,
267
+ "minItems": 1,
268
+ "items": { "$ref": "#/definitions/androidpush_settings" }
269
+ }
270
+ ]
271
+ },
272
+ "$iospush": {
273
+ "oneOf": [
274
+ { "$ref": "#/definitions/iospush_settings" },
275
+ {
276
+ "type": "array",
277
+ "uniqueItems": false,
278
+ "maxItems": 10,
279
+ "minItems": 1,
280
+ "items": { "$ref": "#/definitions/iospush_settings" }
281
+ }
282
+ ]
283
+ },
284
+ "$whatsapp": {
285
+ "oneOf": [
286
+ { "$ref": "#/definitions/mobile_number_pattern" },
287
+ {
288
+ "type": "array",
289
+ "uniqueItems": false,
290
+ "maxItems": 10,
291
+ "minItems": 1,
292
+ "items": { "$ref": "#/definitions/mobile_number_pattern" }
293
+ }
294
+ ]
295
+ },
296
+ "$webpush": {
297
+ "oneOf": [
298
+ { "type": "object", "minProperties": 1 },
299
+ {
300
+ "type": "array",
301
+ "uniqueItems": false,
302
+ "maxItems": 10,
303
+ "minItems": 1,
304
+ "items": { "type": "object", "minProperties": 1 }
305
+ }
306
+ ]
307
+ },
308
+ "$slack": {
309
+ "oneOf": [
310
+ { "$ref": "#/definitions/slack_setting" },
311
+ {
312
+ "type": "array",
313
+ "uniqueItems": false,
314
+ "maxItems": 10,
315
+ "minItems": 1,
316
+ "items": { "$ref": "#/definitions/slack_setting" }
317
+ }
318
+ ]
319
+ },
320
+ "$ms_teams": {
321
+ "oneOf": [
322
+ { "$ref": "#/definitions/ms_teams_setting" },
323
+ {
324
+ "type": "array",
325
+ "uniqueItems": false,
326
+ "maxItems": 10,
327
+ "minItems": 1,
328
+ "items": { "$ref": "#/definitions/ms_teams_setting" }
329
+ }
330
+ ]
331
+ },
332
+ "$inbox": {
333
+ "oneOf": [
334
+ { "$ref": "#/definitions/non_empty_string" },
335
+ {
336
+ "type": "array",
337
+ "uniqueItems": false,
338
+ "maxItems": 10,
339
+ "minItems": 1,
340
+ "items": { "$ref": "#/definitions/non_empty_string" }
341
+ }
342
+ ]
343
+ },
344
+ "$messenger": {
345
+ "oneOf": [
346
+ { "$ref": "#/definitions/non_empty_string" },
347
+ {
348
+ "type": "array",
349
+ "uniqueItems": false,
350
+ "maxItems": 10,
351
+ "minItems": 1,
352
+ "items": { "$ref": "#/definitions/non_empty_string" }
353
+ }
354
+ ]
355
+ }
356
+ },
357
+ "required": []
358
+ }
359
+ }
360
+ }
package/src/subscriber.js CHANGED
@@ -327,6 +327,12 @@ export class Subscriber {
327
327
  this._collect_event();
328
328
  }
329
329
 
330
+ set_timezone(timezone) {
331
+ const caller = "set_timezone";
332
+ this._helper._set_timezone(timezone, caller);
333
+ this._collect_event();
334
+ }
335
+
330
336
  add_email(email) {
331
337
  const caller = "add_email";
332
338
  this._helper._add_email(email, caller);
@@ -31,6 +31,7 @@ const IDENT_KEYS_ALL = [
31
31
 
32
32
  const KEY_PUSHVENDOR = "$pushvendor";
33
33
  const KEY_PREFERRED_LANGUAGE = "$preferred_language";
34
+ const KEY_TIMEZONE = "$timezone";
34
35
 
35
36
  const OTHER_RESERVED_KEYS = [
36
37
  "$messenger",
@@ -49,6 +50,7 @@ const OTHER_RESERVED_KEYS = [
49
50
  "$anon_id",
50
51
  "$identified_id",
51
52
  KEY_PREFERRED_LANGUAGE,
53
+ KEY_TIMEZONE,
52
54
  "$notification_delivered",
53
55
  "$notification_dismiss",
54
56
  "$notification_clicked",
@@ -255,6 +257,10 @@ export default class _SubscriberInternalHelper {
255
257
  this.__dict_set[KEY_PREFERRED_LANGUAGE] = lang_code;
256
258
  }
257
259
 
260
+ _set_timezone(timezone, caller) {
261
+ this.__dict_set[KEY_TIMEZONE] = timezone;
262
+ }
263
+
258
264
  __add_identity(key, value, args, caller) {
259
265
  const new_caller = `${caller}:${key}`;
260
266
  switch (key) {
@@ -320,7 +326,7 @@ export default class _SubscriberInternalHelper {
320
326
  }
321
327
 
322
328
  __check_ident_val_string(value, caller) {
323
- const message = "value must a string with proper value";
329
+ const message = "value must be a string with proper value";
324
330
  if (!is_string(value)) {
325
331
  this.__errors.push(`[${caller}] ${message}`);
326
332
  return [value, false];
package/src/utils.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  import { cloneDeep } from "lodash";
12
12
 
13
13
  const workflow_schema = require("./request_json/workflow.json");
14
+ const workflow_trigger_schema = require("./request_json/workflow_trigger.json");
14
15
  const event_schema = require("./request_json/event.json");
15
16
  const list_broadcast_schema = require("./request_json/list_broadcast.json");
16
17
 
@@ -114,6 +115,25 @@ export function validate_workflow_body_schema(body) {
114
115
  }
115
116
  }
116
117
 
118
+ export function validate_workflow_trigger_body_schema(body) {
119
+ if (!body?.data) {
120
+ body.data = {};
121
+ }
122
+ if (!(body.data instanceof Object)) {
123
+ throw new InputValueError("data must be a object");
124
+ }
125
+ const schema = workflow_trigger_schema;
126
+ var v = new Validator();
127
+ const validated_data = v.validate(body, schema);
128
+ if (validated_data.valid) {
129
+ return body;
130
+ } else {
131
+ const error_obj = validated_data.errors[0];
132
+ const error_msg = `${error_obj.property} ${error_obj.message}`;
133
+ throw new SuprsendError(error_msg);
134
+ }
135
+ }
136
+
117
137
  export function validate_track_event_schema(body) {
118
138
  if (!body?.properties) {
119
139
  body.properties = {};
package/src/workflow.js CHANGED
@@ -33,7 +33,7 @@ export default class Workflow {
33
33
  if (!(this.body["data"] instanceof Object)) {
34
34
  console.log(
35
35
  `WARNING: attachment cannot be added. please make sure body['data'] is a dictionary. Workflow ${JSON.stringify(
36
- this.as_json()
36
+ JSON.stringify(this.as_json())
37
37
  )}`
38
38
  );
39
39
  return;
@@ -0,0 +1,71 @@
1
+ import axios from "axios";
2
+ import get_request_signature from "./signature";
3
+ import { BulkWorkflowTrigger } from "./workflow_trigger_bulk";
4
+ import WorkflowTriggerRequest from "./workflow_request";
5
+
6
+ export default class WorkflowsApi {
7
+ constructor(config) {
8
+ this.config = config;
9
+ this.metadata = { "User-Agent": this.config.user_agent };
10
+ }
11
+
12
+ _get_headers() {
13
+ return {
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ Date: new Date().toUTCString(),
16
+ "User-Agent": this.config.user_agent,
17
+ };
18
+ }
19
+
20
+ async trigger(workflow) {
21
+ const is_part_of_bulk = false;
22
+ const [workflow_body, body_size] = workflow.get_final_json(
23
+ this.config,
24
+ is_part_of_bulk
25
+ );
26
+ try {
27
+ const headers = this._get_headers();
28
+ const content_text = JSON.stringify(workflow_body);
29
+ const url = `${this.config.base_url}trigger/`;
30
+ const signature = get_request_signature(
31
+ url,
32
+ "POST",
33
+ content_text,
34
+ headers,
35
+ this.config.workspace_secret
36
+ );
37
+ headers["Authorization"] = `${this.config.workspace_key}:${signature}`;
38
+ const resp = await axios.post(url, content_text, {
39
+ headers,
40
+ transformResponse: [(data) => data], // dont assume type of response
41
+ });
42
+ const ok_response = Math.floor(resp.status / 100) === 2;
43
+ return {
44
+ success: ok_response,
45
+ status: ok_response ? "success" : "fail",
46
+ status_code: resp.status,
47
+ message: resp.data,
48
+ };
49
+ } catch (err) {
50
+ if (err?.response) {
51
+ return {
52
+ success: false,
53
+ status: "fail",
54
+ status_code: err?.response?.status || 500,
55
+ message: err?.response?.data,
56
+ };
57
+ } else {
58
+ return {
59
+ success: false,
60
+ status: "fail",
61
+ status_code: 500,
62
+ message: err.message,
63
+ };
64
+ }
65
+ }
66
+ }
67
+
68
+ bulk_trigger_instance() {
69
+ return new BulkWorkflowTrigger(this.config);
70
+ }
71
+ }
@@ -0,0 +1,92 @@
1
+ import {
2
+ InputValueError,
3
+ get_apparent_workflow_body_size,
4
+ validate_workflow_trigger_body_schema,
5
+ } from "./utils";
6
+ import get_attachment_json from "./attachment";
7
+ import {
8
+ SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES,
9
+ SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
10
+ } from "./constants";
11
+
12
+ export default class WorkflowTriggerRequest {
13
+ constructor(body, kwargs = {}) {
14
+ if (!(typeof body === "object" && !Array.isArray(body))) {
15
+ throw new InputValueError(
16
+ "WorkflowTriggerRequest body must be a JSON object/dictionary"
17
+ );
18
+ }
19
+ this.body = body;
20
+ this.idempotency_key = kwargs?.idempotency_key;
21
+ this.tenant_id = kwargs?.tenant_id;
22
+ this.cancellation_key = kwargs?.cancellation_key;
23
+ }
24
+
25
+ add_attachment(file_path = "", kwargs = {}) {
26
+ const file_name = kwargs?.file_name;
27
+ const ignore_if_error = kwargs?.ignore_if_error ?? false;
28
+ if (!this.body.data) {
29
+ this.body.data = {};
30
+ }
31
+ // If body["data"] is not a dictionary, don't raise an error while adding attachment.
32
+ if (typeof this.body.data !== "object") {
33
+ console.warn(
34
+ "WARNING: attachment cannot be added. Please make sure body['data'] is a dictionary. WorkflowTriggerRequest ",
35
+ JSON.stringify(this.as_json())
36
+ );
37
+ return;
38
+ }
39
+ const attachment = get_attachment_json(
40
+ file_path,
41
+ file_name,
42
+ ignore_if_error
43
+ );
44
+ if (!attachment) {
45
+ return;
46
+ }
47
+ if (!this.body.data.$attachments) {
48
+ this.body.data.$attachments = [];
49
+ }
50
+ this.body.data.$attachments.push(attachment);
51
+ }
52
+
53
+ get_final_json(config, is_part_of_bulk = false) {
54
+ if (this.idempotency_key) {
55
+ this.body["$idempotency_key"] = this.idempotency_key;
56
+ }
57
+ if (this.tenant_id) {
58
+ this.body["tenant_id"] = this.tenant_id;
59
+ }
60
+ if (this.cancellation_key) {
61
+ this.body.cancellation_key = this.cancellation_key;
62
+ }
63
+
64
+ this.body = validate_workflow_trigger_body_schema(this.body);
65
+
66
+ const apparent_size = get_apparent_workflow_body_size(
67
+ this.body,
68
+ is_part_of_bulk
69
+ );
70
+ if (apparent_size > SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
71
+ throw new InputValueError(
72
+ `workflow body too big - ${apparent_size} Bytes, must not exceed ${SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
73
+ );
74
+ }
75
+
76
+ return [this.body, apparent_size];
77
+ }
78
+
79
+ as_json() {
80
+ const body_dict = { ...this.body };
81
+ if (this.idempotency_key) {
82
+ body_dict.$idempotency_key = this.idempotency_key;
83
+ }
84
+ if (this.tenant_id) {
85
+ body_dict.tenant_id = this.tenant_id;
86
+ }
87
+ if (this.cancellation_key) {
88
+ body_dict.cancellation_key = this.cancellation_key;
89
+ }
90
+ return body_dict;
91
+ }
92
+ }