@suprsend/node-sdk 0.0.6 → 0.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.
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "http://github.com/suprsend/suprsend-node-sdk/request_json/event.json",
4
+ "title": "track_event",
5
+ "description": "Json schema for Track event",
6
+ "$comment": "Json schema for Track event",
7
+ "type": "object",
8
+ "properties": {
9
+ "$insert_id": {
10
+ "type": "string",
11
+ "minLength": 36,
12
+ "description": "unique uuid generated per request"
13
+ },
14
+ "$time": {
15
+ "type": "integer",
16
+ "minimum": 1640995200000,
17
+ "description": "Timestamp: unix epoch in milliseconds"
18
+ },
19
+ "event": {
20
+ "type": "string",
21
+ "minLength": 2,
22
+ "description": "Event Name to track"
23
+ },
24
+ "env": {
25
+ "type": "string",
26
+ "minLength": 20,
27
+ "description": "Workspace key"
28
+ },
29
+ "distinct_id": {
30
+ "type": "string",
31
+ "minLength": 1,
32
+ "description": "distinct_id: Id which uniquely identify a user in your app"
33
+ },
34
+ "properties": {
35
+ "type": "object",
36
+ "description": "Properties related to event"
37
+ }
38
+ },
39
+ "required": [
40
+ "$insert_id",
41
+ "$time",
42
+ "event",
43
+ "env",
44
+ "distinct_id",
45
+ "properties"
46
+ ]
47
+ }
@@ -27,7 +27,11 @@
27
27
  "$ref": "#/definitions/non_empty_string",
28
28
  "description": "timestamp in ISO-8601 format. e.g 2021-08-27T20:14:51.643Z"
29
29
  },
30
- "priority_algorithm": { "type": "boolean" },
30
+ "delivery": {
31
+ "type": "object",
32
+ "$ref": "#/definitions/delivery_setting",
33
+ "description": "set parameters e.g smart (true/false), success metric, TTL, mandatory channels etc"
34
+ },
31
35
  "users": {
32
36
  "type": "array",
33
37
  "items": { "$ref": "#/definitions/user_setting" },
@@ -51,7 +55,7 @@
51
55
  "minLength": 8,
52
56
  "pattern": "^\\+[0-9\\s]+",
53
57
  "message": {
54
- "required": "Either a mobile-number or an array of mobile-numbers. e.g [\"41446681800\"]",
58
+ "required": "Either a mobile-number or an array of mobile-numbers. e.g [\"+41446681800\"]",
55
59
  "pattern": "number must start with + and must contain country code. e.g. +41446681800"
56
60
  }
57
61
  },
@@ -70,7 +74,11 @@
70
74
  "user_setting": {
71
75
  "type": "object",
72
76
  "properties": {
73
- "distinct_id": { "$ref": "#/definitions/non_empty_string" },
77
+ "distinct_id": {
78
+ "type": "string",
79
+ "minLength": 1,
80
+ "description": "distinct_id: Id which uniquely identify a user in your app"
81
+ },
74
82
  "$email": {
75
83
  "oneOf": [
76
84
  { "$ref": "#/definitions/email_pattern" },
@@ -170,6 +178,44 @@
170
178
  },
171
179
  "required": ["distinct_id"],
172
180
  "additionalProperties": false
181
+ },
182
+ "delivery_setting": {
183
+ "type": "object",
184
+ "properties": {
185
+ "smart": {
186
+ "type": "boolean",
187
+ "default": false,
188
+ "description": "If false, notifications are sent to all channels at once. If true, notifications are sent one-by-one until success metric is satisfied"
189
+ },
190
+ "success": {
191
+ "type": "string",
192
+ "default": "seen",
193
+ "description": "possible values: seen/interaction/<user_defined_event>."
194
+ },
195
+ "time_to_live": {
196
+ "type": "string",
197
+ "default": "1h",
198
+ "description": "Used if smart=true. format [XX]d[XX]h[XX]m[XX]s e.g 1d2h30m10s(for 1day 2hours 30minutes 10sec)"
199
+ },
200
+ "mandatory_channels": {
201
+ "type": "array",
202
+ "items": {
203
+ "type": "string",
204
+ "enum": [
205
+ "androidpush",
206
+ "iospush",
207
+ "webpush",
208
+ "email",
209
+ "sms",
210
+ "whatsapp"
211
+ ]
212
+ },
213
+ "minItems": 0,
214
+ "description": "e.g ['email', 'sms']. Used if smart=true, notification on these channels must be sent, independent of success metric outcome"
215
+ }
216
+ },
217
+ "required": [],
218
+ "additionalProperties": false
173
219
  }
174
220
  }
175
221
  }
package/dist/signature.js CHANGED
@@ -19,7 +19,7 @@ function get_request_signature(url, http_verb, content, headers, secret) {
19
19
  return "";
20
20
  }
21
21
 
22
- var content_md5 = _crypto["default"].createHash("md5").update(JSON.stringify(content)).digest("hex");
22
+ var content_md5 = _crypto["default"].createHash("md5").update(content).digest("hex");
23
23
 
24
24
  var request_uri = get_path(url);
25
25
  var sign_string = http_verb + "\n" + content_md5 + "\n" + headers["Content-Type"] + "\n" + headers["Date"] + "\n" + request_uri;
package/dist/utils.js CHANGED
@@ -6,9 +6,16 @@ Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
8
  exports.SuprsendError = void 0;
9
- exports._get_schema = _get_schema;
10
9
  exports.base64Encode = base64Encode;
10
+ exports.epoch_milliseconds = epoch_milliseconds;
11
+ exports.has_special_char = void 0;
12
+ exports.is_empty = is_empty;
13
+ exports.is_object = is_object;
14
+ exports.is_string = is_string;
11
15
  exports.resolveTilde = resolveTilde;
16
+ exports.uuid = uuid;
17
+
18
+ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
12
19
 
13
20
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
14
21
 
@@ -24,204 +31,12 @@ var _os = _interopRequireDefault(require("os"));
24
31
 
25
32
  var _fs = _interopRequireDefault(require("fs"));
26
33
 
27
- var _path = _interopRequireDefault(require("path"));
34
+ var _uuid = require("uuid");
28
35
 
29
36
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
30
37
 
31
38
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
32
39
 
33
- var schema = {
34
- $schema: "http://json-schema.org/draft-07/schema#",
35
- $id: "http://github.com/suprsend/suprsend-node-sdk/request_json/workflow.json",
36
- title: "workflow_request",
37
- description: "Json schema for workflow request",
38
- $comment: "Json schema for workflow request",
39
- type: "object",
40
- properties: {
41
- name: {
42
- $ref: "#/definitions/non_empty_string",
43
- description: "name of workflow"
44
- },
45
- template: {
46
- $ref: "#/definitions/non_empty_string",
47
- description: "slug of Template"
48
- },
49
- notification_category: {
50
- $ref: "#/definitions/non_empty_string",
51
- description: "slug of Notification category"
52
- },
53
- delay: {
54
- type: ["string", "integer"],
55
- minimum: 0,
56
- description: "If string: format [XX]d[XX]h[XX]m[XX]s e.g 1d2h30m10s(for 1day 2hours 30minutes 10sec). If integer: value in number of seconds"
57
- },
58
- trigger_at: {
59
- $ref: "#/definitions/non_empty_string",
60
- description: "timestamp in ISO-8601 format. e.g 2021-08-27T20:14:51.643Z"
61
- },
62
- priority_algorithm: {
63
- type: "boolean"
64
- },
65
- users: {
66
- type: "array",
67
- items: {
68
- $ref: "#/definitions/user_setting"
69
- },
70
- minItems: 1,
71
- maxItems: 100,
72
- description: "user object to run workflow for. At least 1 user is required"
73
- },
74
- data: {
75
- type: "object",
76
- description: "variables to be used in workflow. e.g replacing templates variables."
77
- }
78
- },
79
- required: ["name", "template", "notification_category", "users", "data"],
80
- definitions: {
81
- non_empty_string: {
82
- type: "string",
83
- minLength: 2
84
- },
85
- mobile_number_pattern: {
86
- type: "string",
87
- minLength: 8,
88
- pattern: "^\\+[0-9\\s]+",
89
- message: {
90
- required: 'Either a mobile-number or an array of mobile-numbers. e.g ["41446681800"]',
91
- pattern: "number must start with + and must contain country code. e.g. +41446681800"
92
- }
93
- },
94
- email_pattern: {
95
- type: "string",
96
- format: "email",
97
- pattern: "^\\S+@\\S+\\.\\S+$",
98
- description: "email of an user",
99
- minLength: 6,
100
- maxLength: 127,
101
- message: {
102
- required: "",
103
- pattern: "value in email format required. e.g. user@example.com"
104
- }
105
- },
106
- user_setting: {
107
- type: "object",
108
- properties: {
109
- distinct_id: {
110
- $ref: "#/definitions/non_empty_string"
111
- },
112
- $email: {
113
- oneOf: [{
114
- $ref: "#/definitions/email_pattern"
115
- }, {
116
- type: "array",
117
- uniqueItems: true,
118
- maxItems: 10,
119
- minItems: 1,
120
- items: {
121
- $ref: "#/definitions/email_pattern"
122
- }
123
- }]
124
- },
125
- $sms: {
126
- oneOf: [{
127
- $ref: "#/definitions/mobile_number_pattern"
128
- }, {
129
- type: "array",
130
- uniqueItems: true,
131
- maxItems: 10,
132
- minItems: 1,
133
- items: {
134
- $ref: "#/definitions/mobile_number_pattern"
135
- }
136
- }]
137
- },
138
- $androidpush: {
139
- oneOf: [{
140
- $ref: "#/definitions/non_empty_string"
141
- }, {
142
- type: "array",
143
- uniqueItems: true,
144
- maxItems: 10,
145
- minItems: 1,
146
- items: {
147
- $ref: "#/definitions/non_empty_string"
148
- }
149
- }]
150
- },
151
- $iospush: {
152
- oneOf: [{
153
- $ref: "#/definitions/non_empty_string"
154
- }, {
155
- type: "array",
156
- uniqueItems: true,
157
- maxItems: 10,
158
- minItems: 1,
159
- items: {
160
- $ref: "#/definitions/non_empty_string"
161
- }
162
- }]
163
- },
164
- $whatsapp: {
165
- oneOf: [{
166
- $ref: "#/definitions/mobile_number_pattern"
167
- }, {
168
- type: "array",
169
- uniqueItems: true,
170
- maxItems: 10,
171
- minItems: 1,
172
- items: {
173
- $ref: "#/definitions/mobile_number_pattern"
174
- }
175
- }]
176
- },
177
- $webpush: {
178
- oneOf: [{
179
- type: "object",
180
- minProperties: 1
181
- }, {
182
- type: "array",
183
- uniqueItems: true,
184
- maxItems: 10,
185
- minItems: 1,
186
- items: {
187
- type: "object",
188
- minProperties: 1
189
- }
190
- }]
191
- },
192
- $inbox: {
193
- oneOf: [{
194
- $ref: "#/definitions/non_empty_string"
195
- }, {
196
- type: "array",
197
- uniqueItems: true,
198
- maxItems: 10,
199
- minItems: 1,
200
- items: {
201
- $ref: "#/definitions/non_empty_string"
202
- }
203
- }]
204
- },
205
- $messenger: {
206
- oneOf: [{
207
- $ref: "#/definitions/non_empty_string"
208
- }, {
209
- type: "array",
210
- uniqueItems: true,
211
- maxItems: 10,
212
- minItems: 1,
213
- items: {
214
- $ref: "#/definitions/non_empty_string"
215
- }
216
- }]
217
- }
218
- },
219
- required: ["distinct_id"],
220
- additionalProperties: false
221
- }
222
- }
223
- };
224
-
225
40
  function base64Encode(file) {
226
41
  var body = _fs["default"].readFileSync(file);
227
42
 
@@ -240,35 +55,6 @@ function resolveTilde(filePath) {
240
55
  return filePath;
241
56
  }
242
57
 
243
- var __JSON_SCHEMAS = {};
244
-
245
- function _get_schema(schema_name) {
246
- var schema_body = __JSON_SCHEMAS[schema_name];
247
-
248
- if (!schema_body) {
249
- schema_body = schema;
250
- __JSON_SCHEMAS[schema_name] = schema_body;
251
- }
252
-
253
- return schema_body;
254
- }
255
-
256
- function _load_json_schema(schema_name) {
257
- var file_path = _path["default"].join(__dirname, "request_json/".concat(schema_name, ".json"));
258
-
259
- var json_schema;
260
-
261
- try {
262
- var schema_string = _fs["default"].readFileSync(file_path, "utf8");
263
-
264
- json_schema = JSON.parse(schema_string);
265
- } catch (e) {
266
- throw new SuprsendError("Missing Schema");
267
- }
268
-
269
- return json_schema;
270
- }
271
-
272
58
  var SuprsendError = /*#__PURE__*/function (_Error) {
273
59
  (0, _inherits2["default"])(SuprsendError, _Error);
274
60
 
@@ -286,4 +72,36 @@ var SuprsendError = /*#__PURE__*/function (_Error) {
286
72
  return SuprsendError;
287
73
  }( /*#__PURE__*/(0, _wrapNativeSuper2["default"])(Error));
288
74
 
289
- exports.SuprsendError = SuprsendError;
75
+ exports.SuprsendError = SuprsendError;
76
+
77
+ function is_string(value) {
78
+ return typeof value === "string";
79
+ }
80
+
81
+ function is_object(value) {
82
+ return (0, _typeof2["default"])(value) === "object" && !Array.isArray(value) && value !== null;
83
+ }
84
+
85
+ function is_empty(value) {
86
+ if (is_object(value)) {
87
+ return Object.keys(value) <= 0;
88
+ } else if (Array.isArray(value)) {
89
+ return value.length <= 0;
90
+ }
91
+ }
92
+
93
+ var has_special_char = function has_special_char(str) {
94
+ var _str$toLowerCase;
95
+
96
+ return str.startsWith("$") || (str === null || str === void 0 ? void 0 : (_str$toLowerCase = str.toLowerCase()) === null || _str$toLowerCase === void 0 ? void 0 : _str$toLowerCase.startsWith("ss_"));
97
+ };
98
+
99
+ exports.has_special_char = has_special_char;
100
+
101
+ function uuid() {
102
+ return (0, _uuid.v4)();
103
+ }
104
+
105
+ function epoch_milliseconds() {
106
+ return Math.round(Date.now());
107
+ }
package/dist/workflow.js CHANGED
@@ -23,6 +23,8 @@ var _utils = require("./utils");
23
23
 
24
24
  var _axios = _interopRequireDefault(require("axios"));
25
25
 
26
+ var workflow_schema = require("./request_json/workflow.json");
27
+
26
28
  var Workflow = /*#__PURE__*/function () {
27
29
  function Workflow(ss_instance, data) {
28
30
  (0, _classCallCheck2["default"])(this, Workflow);
@@ -69,7 +71,7 @@ var Workflow = /*#__PURE__*/function () {
69
71
  content_text = JSON.stringify(this.data);
70
72
 
71
73
  if (this.ss_instance.auth_enabled) {
72
- signature = (0, _signature["default"])(this.url, "POST", this.data, headers, this.ss_instance.env_secret);
74
+ signature = (0, _signature["default"])(this.url, "POST", content_text, headers, this.ss_instance.env_secret);
73
75
  headers["Authorization"] = "".concat(this.ss_instance.env_key, ":").concat(signature);
74
76
  }
75
77
 
@@ -123,7 +125,7 @@ var Workflow = /*#__PURE__*/function () {
123
125
  throw new _utils.SuprsendError("data must be a object");
124
126
  }
125
127
 
126
- var schema = (0, _utils._get_schema)("workflow");
128
+ var schema = workflow_schema;
127
129
  var v = new _jsonschema.Validator();
128
130
  var validated_data = v.validate(this.data, schema);
129
131
 
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@suprsend/node-sdk",
3
- "version": "0.0.6",
3
+ "version": "0.1.0",
4
4
  "description": "Suprsend Node SDK to trigger workflow from backend",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
- "build": "rm -rf dist && babel ./src --out-dir dist && mkdir ./dist/request_json && cp ./src/request_json/workflow.json ./dist/request_json/",
7
+ "build": "rm -rf dist && babel ./src --out-dir dist && mkdir ./dist/request_json && cp -R ./src/request_json/ ./dist/request_json/",
8
8
  "publish_sdk": "npm run build && npm publish"
9
9
  },
10
10
  "keywords": [
11
11
  "suprsend-node-sdk",
12
12
  "workflow"
13
13
  ],
14
- "author": "Sivaram Katta",
14
+ "author": "SuprSend Developers",
15
15
  "license": "MIT",
16
16
  "repository": {
17
17
  "type": "git",
@@ -24,7 +24,8 @@
24
24
  "@babel/runtime": "^7.16.3",
25
25
  "axios": "^0.24.0",
26
26
  "jsonschema": "^1.4.0",
27
- "mime-types": "^2.1.34"
27
+ "mime-types": "^2.1.34",
28
+ "uuid": "^8.3.2"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@babel/cli": "^7.16.0",
package/src/event.js ADDED
@@ -0,0 +1,148 @@
1
+ import {
2
+ is_object,
3
+ is_string,
4
+ SuprsendError,
5
+ has_special_char,
6
+ uuid,
7
+ epoch_milliseconds,
8
+ } from "./utils";
9
+ import get_request_signature from "./signature";
10
+ import { Validator } from "jsonschema";
11
+ import axios from "axios";
12
+
13
+ const event_schema = require("./request_json/event.json");
14
+
15
+ const RESERVED_EVENT_NAMES = [
16
+ "$identify",
17
+ "$notification_delivered",
18
+ "$notification_dismiss",
19
+ "$notification_clicked",
20
+ "$app_launched",
21
+ "$user_login",
22
+ "$user_logout",
23
+ ];
24
+
25
+ class EventCollector {
26
+ constructor(config) {
27
+ this.config = config;
28
+ this.__url = this.__get_url();
29
+ this.__headers = this.__common_headers();
30
+ this.__supr_props = this.__super_properties();
31
+ }
32
+
33
+ __get_url() {
34
+ let url_template = "event/";
35
+ if (this.config.include_signature_param) {
36
+ if (this.config.auth_enabled) {
37
+ url_template = url_template + "?verify=true";
38
+ } else {
39
+ url_template = url_template + "?verify=false";
40
+ }
41
+ }
42
+ return `${this.config.base_url}${url_template}`;
43
+ }
44
+
45
+ __common_headers() {
46
+ return {
47
+ "Content-Type": "application/json; charset=utf-8",
48
+ "User-Agent": this.config.user_agent,
49
+ };
50
+ }
51
+ __dynamic_headers() {
52
+ return {
53
+ Date: new Date().toUTCString(),
54
+ };
55
+ }
56
+
57
+ __super_properties() {
58
+ return {
59
+ $ss_sdk_version: this.config.user_agent,
60
+ };
61
+ }
62
+
63
+ __check_event_prefix(event_name) {
64
+ if (!RESERVED_EVENT_NAMES.includes(event_name)) {
65
+ if (has_special_char(event_name)) {
66
+ throw new SuprsendError(
67
+ "event_names starting with [$,ss_] are reserved"
68
+ );
69
+ }
70
+ }
71
+ }
72
+
73
+ __validate_event_name(event_name) {
74
+ if (!is_string(event_name)) {
75
+ throw new SuprsendError("event_name must be a string");
76
+ }
77
+ event_name = event_name.trim();
78
+ this.__check_event_prefix(event_name);
79
+ return event_name;
80
+ }
81
+
82
+ validate_track_event_schema(data) {
83
+ if (!data["properties"]) {
84
+ data["properties"] = {};
85
+ }
86
+ const schema = event_schema;
87
+ var v = new Validator();
88
+ const validated_data = v.validate(data, schema);
89
+ if (validated_data.valid) {
90
+ return data;
91
+ } else {
92
+ const error_obj = validated_data.errors[0];
93
+ const error_msg = `${error_obj.property} ${error_obj.message}`;
94
+ throw new SuprsendError(error_msg);
95
+ }
96
+ }
97
+
98
+ collect(distinct_id, event_name, properties = {}) {
99
+ event_name = this.__validate_event_name(event_name);
100
+ if (!is_object(properties)) {
101
+ throw new SuprsendError("properties must be a dictionary");
102
+ }
103
+ properties = { ...properties, ...this.__supr_props };
104
+ let event = {
105
+ $insert_id: uuid(),
106
+ $time: epoch_milliseconds(),
107
+ event: event_name,
108
+ env: this.config.env_key,
109
+ distinct_id: distinct_id,
110
+ properties: properties,
111
+ };
112
+ event = this.validate_track_event_schema(event);
113
+ return this.send(event);
114
+ }
115
+
116
+ async send(event) {
117
+ const headers = { ...this.__headers, ...this.__dynamic_headers() };
118
+ const content_text = JSON.stringify(event);
119
+ if (this.config.auth_enabled) {
120
+ const signature = get_request_signature(
121
+ this.__url,
122
+ "POST",
123
+ content_text,
124
+ headers,
125
+ this.config.env_secret
126
+ );
127
+ headers["Authorization"] = `${this.config.env_key}:${signature}`;
128
+ }
129
+ try {
130
+ const response = await axios.post(this.__url, content_text, {
131
+ headers,
132
+ });
133
+ return {
134
+ status_code: response.status,
135
+ success: true,
136
+ message: response.statusText,
137
+ };
138
+ } catch (err) {
139
+ return {
140
+ status_code: 400,
141
+ success: false,
142
+ message: err.message,
143
+ };
144
+ }
145
+ }
146
+ }
147
+
148
+ export default EventCollector;