@suprsend/node-sdk 0.0.5 → 0.1.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.
@@ -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,7 +31,7 @@ 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
 
@@ -48,35 +55,6 @@ function resolveTilde(filePath) {
48
55
  return filePath;
49
56
  }
50
57
 
51
- var __JSON_SCHEMAS = {};
52
-
53
- function _get_schema(schema_name) {
54
- var schema_body = __JSON_SCHEMAS[schema_name];
55
-
56
- if (!schema_body) {
57
- schema_body = _load_json_schema(schema_name);
58
- __JSON_SCHEMAS[schema_name] = schema_body;
59
- }
60
-
61
- return schema_body;
62
- }
63
-
64
- function _load_json_schema(schema_name) {
65
- var file_path = _path["default"].join(__dirname, "request_json/".concat(schema_name, ".json"));
66
-
67
- var json_schema;
68
-
69
- try {
70
- var schema_string = _fs["default"].readFileSync(file_path, "utf8");
71
-
72
- json_schema = JSON.parse(schema_string);
73
- } catch (e) {
74
- throw new SuprsendError("Missing Schema");
75
- }
76
-
77
- return json_schema;
78
- }
79
-
80
58
  var SuprsendError = /*#__PURE__*/function (_Error) {
81
59
  (0, _inherits2["default"])(SuprsendError, _Error);
82
60
 
@@ -94,4 +72,36 @@ var SuprsendError = /*#__PURE__*/function (_Error) {
94
72
  return SuprsendError;
95
73
  }( /*#__PURE__*/(0, _wrapNativeSuper2["default"])(Error));
96
74
 
97
- 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,19 @@
1
1
  {
2
2
  "name": "@suprsend/node-sdk",
3
- "version": "0.0.5",
3
+ "version": "0.1.1",
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
- "workflow"
12
+ "node",
13
+ "sdk",
14
+ "notifications"
13
15
  ],
14
- "author": "Sivaram Katta",
16
+ "author": "SuprSend Developers",
15
17
  "license": "MIT",
16
18
  "repository": {
17
19
  "type": "git",
@@ -22,13 +24,14 @@
22
24
  },
23
25
  "dependencies": {
24
26
  "@babel/runtime": "^7.16.3",
25
- "axios": "^0.24.0",
27
+ "axios": "^0.27.2",
26
28
  "jsonschema": "^1.4.0",
27
- "mime-types": "^2.1.34"
29
+ "mime-types": "^2.1.34",
30
+ "uuid": "^8.3.2"
28
31
  },
29
32
  "devDependencies": {
30
33
  "@babel/cli": "^7.16.0",
31
- "@babel/core": "^7.16.0",
34
+ "@babel/core": "^7.18.6",
32
35
  "@babel/plugin-transform-runtime": "^7.16.4",
33
36
  "@babel/preset-env": "^7.16.4",
34
37
  "babel-plugin-add-module-exports": "^1.0.4"
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;