@churnsignal/churnsignal-sdk-web 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.
- package/README.md +63 -0
- package/dist/api-client.d.ts +28 -0
- package/dist/churnsignal-sdk-web.cjs.development.js +249 -0
- package/dist/churnsignal-sdk-web.cjs.development.js.map +1 -0
- package/dist/churnsignal-sdk-web.cjs.production.min.js +2 -0
- package/dist/churnsignal-sdk-web.cjs.production.min.js.map +1 -0
- package/dist/churnsignal-sdk-web.esm.js +243 -0
- package/dist/churnsignal-sdk-web.esm.js.map +1 -0
- package/dist/core/destroy.d.ts +1 -0
- package/dist/core/identify.d.ts +3 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/initialise.d.ts +11 -0
- package/dist/core/track.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -0
- package/dist/managers/index.d.ts +1 -0
- package/dist/managers/sdk-instance-manager.d.ts +26 -0
- package/dist/utils/request-transformations.d.ts +18 -0
- package/dist/utils/url-validator.d.ts +2 -0
- package/package.json +55 -0
- package/src/api-client.ts +118 -0
- package/src/core/destroy.ts +5 -0
- package/src/core/identify.ts +5 -0
- package/src/core/index.ts +3 -0
- package/src/core/initialise.test.ts +33 -0
- package/src/core/initialise.ts +26 -0
- package/src/core/track.ts +15 -0
- package/src/index.ts +1 -0
- package/src/managers/index.ts +1 -0
- package/src/managers/sdk-instance-manager.ts +71 -0
- package/src/utils/request-transformations.ts +44 -0
- package/src/utils/url-validator.test.ts +26 -0
- package/src/utils/url-validator.ts +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# RAVEN SDK WEB
|
|
2
|
+
This SDK allows developers to seamlessly integrate Raven into their Web applications
|
|
3
|
+
|
|
4
|
+
## Commands
|
|
5
|
+
|
|
6
|
+
### Install dependencies
|
|
7
|
+
|
|
8
|
+
To install the dependencies run:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
To do a one-off build
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This builds the library to `/dist` folder
|
|
21
|
+
|
|
22
|
+
To run in watch mode run
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm start
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`.
|
|
29
|
+
|
|
30
|
+
To run tests run
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Checking if it's working
|
|
37
|
+
|
|
38
|
+
To use this package in another app you will need to link it
|
|
39
|
+
To do so, go into the SDK folder and run
|
|
40
|
+
`npm link`
|
|
41
|
+
then inside of the folder of the app you want to check it in run
|
|
42
|
+
`npm link @churnsignal/churnsignal-sdk-web`
|
|
43
|
+
|
|
44
|
+
### Bundle Analysis
|
|
45
|
+
|
|
46
|
+
[`size-limit`](https://github.com/ai/size-limit) is set up to calculate the real cost of your library with `npm run size` and visualize the bundle with `npm run analyze`.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Module Formats
|
|
50
|
+
|
|
51
|
+
CJS, ESModules, and UMD module formats are supported.
|
|
52
|
+
|
|
53
|
+
The appropriate paths are configured in `package.json` and `dist/index.js` accordingly.
|
|
54
|
+
|
|
55
|
+
## Named Exports
|
|
56
|
+
|
|
57
|
+
Per Palmer Group guidelines, [always use named exports.](https://github.com/palmerhq/typescript#exports) Code split inside your React app instead of your React library.
|
|
58
|
+
|
|
59
|
+
## Including Styles
|
|
60
|
+
|
|
61
|
+
There are many ways to ship styles, including with CSS-in-JS. TSDX has no opinion on this, configure how you like.
|
|
62
|
+
|
|
63
|
+
For vanilla CSS, you can include it at the root directory and add it to the `files` section in your `package.json`, so that it can be imported separately by your users and run through their bundler's loader.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare type ApiClientProps = {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
config: {
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
tenantId?: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare type EventProperties = Record<string, any>;
|
|
10
|
+
declare type User = {
|
|
11
|
+
email: string;
|
|
12
|
+
};
|
|
13
|
+
declare class ApiClient {
|
|
14
|
+
private apiKey;
|
|
15
|
+
private baseUrl?;
|
|
16
|
+
private workspaceId;
|
|
17
|
+
private tenantId?;
|
|
18
|
+
private user?;
|
|
19
|
+
constructor({ apiKey, baseUrl, config }: ApiClientProps);
|
|
20
|
+
fetchWithConfig: (endpoint: string, options?: RequestInit) => Promise<Response>;
|
|
21
|
+
getApiKey(): string;
|
|
22
|
+
getBaseUrl(): string | undefined;
|
|
23
|
+
getWorkspaceId(): string;
|
|
24
|
+
getUser(): User | undefined;
|
|
25
|
+
identify(user: User): void;
|
|
26
|
+
track(eventName: string, eventProperties?: EventProperties): void;
|
|
27
|
+
}
|
|
28
|
+
export default ApiClient;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var urlRegex = /^https?:\/\//;
|
|
6
|
+
var isValidUrl = function isValidUrl(url) {
|
|
7
|
+
return urlRegex.test(url);
|
|
8
|
+
};
|
|
9
|
+
var sanitiseUrl = function sanitiseUrl(url) {
|
|
10
|
+
// Trim whitespace
|
|
11
|
+
var sanitisedUrl = url.trim();
|
|
12
|
+
// Validate scheme
|
|
13
|
+
if (!isValidUrl(url)) {
|
|
14
|
+
throw new Error("URL must start with 'http://' or 'https://'");
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
var urlObject = new URL(sanitisedUrl);
|
|
18
|
+
urlObject.hostname = urlObject.hostname.toLowerCase();
|
|
19
|
+
// Remove trailing slash
|
|
20
|
+
if (urlObject.pathname !== '/') {
|
|
21
|
+
urlObject.pathname = urlObject.pathname.replace(/\/+$/, '') || '';
|
|
22
|
+
}
|
|
23
|
+
// Reconstruct the URL from its components
|
|
24
|
+
// Note: The URL interface automatically handles encoding of the pathname
|
|
25
|
+
return urlObject.toString().replace(/\/+$/, '');
|
|
26
|
+
} catch (e) {
|
|
27
|
+
throw new Error('Invalid URL provided');
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function _extends() {
|
|
32
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
33
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
34
|
+
var source = arguments[i];
|
|
35
|
+
for (var key in source) {
|
|
36
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
37
|
+
target[key] = source[key];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return target;
|
|
42
|
+
};
|
|
43
|
+
return _extends.apply(this, arguments);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var transformTrackRequestData = function transformTrackRequestData(_ref) {
|
|
47
|
+
var eventName = _ref.eventName,
|
|
48
|
+
eventProperties = _ref.eventProperties,
|
|
49
|
+
userProperties = _ref.userProperties,
|
|
50
|
+
configProperties = _ref.configProperties;
|
|
51
|
+
var transformedEvent = {
|
|
52
|
+
name: eventName,
|
|
53
|
+
timestamp: new Date().getTime().toString(),
|
|
54
|
+
userEmail: userProperties == null ? void 0 : userProperties.email,
|
|
55
|
+
tenantId: configProperties.tenantId,
|
|
56
|
+
workspaceId: configProperties.workspaceId,
|
|
57
|
+
type: 'event',
|
|
58
|
+
data: eventProperties
|
|
59
|
+
};
|
|
60
|
+
return JSON.stringify(transformedEvent);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
var sdkConfig = {
|
|
64
|
+
defaultHeaders: {
|
|
65
|
+
'Content-Type': 'application/json'
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var API_PREFIX = 'sdk-prod';
|
|
69
|
+
var TRACK_ENDPOINT = API_PREFIX + "/track";
|
|
70
|
+
// const IDENTIFY_USER_ENDPOINT = `${API_PREFIX}/identify`;
|
|
71
|
+
var ApiClient = /*#__PURE__*/function () {
|
|
72
|
+
function ApiClient(_ref) {
|
|
73
|
+
var _this = this;
|
|
74
|
+
var apiKey = _ref.apiKey,
|
|
75
|
+
baseUrl = _ref.baseUrl,
|
|
76
|
+
config = _ref.config;
|
|
77
|
+
this.workspaceId = '';
|
|
78
|
+
// Wrapper function for fetch
|
|
79
|
+
this.fetchWithConfig = function (endpoint, options) {
|
|
80
|
+
if (options === void 0) {
|
|
81
|
+
options = {
|
|
82
|
+
method: 'GET'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Construct the full URL
|
|
86
|
+
var url = _this.getBaseUrl() + "/" + endpoint;
|
|
87
|
+
// Merge the default headers with any headers provided in the options
|
|
88
|
+
var headers = _extends({}, sdkConfig.defaultHeaders, {
|
|
89
|
+
'x-api-key': _this.getApiKey()
|
|
90
|
+
}, options.headers || {});
|
|
91
|
+
// Merge the rest of the options with the headers
|
|
92
|
+
var config = _extends({}, options, {
|
|
93
|
+
headers: headers
|
|
94
|
+
});
|
|
95
|
+
// Execute the fetch call with the merged configuration
|
|
96
|
+
return fetch(url, config);
|
|
97
|
+
};
|
|
98
|
+
this.apiKey = apiKey;
|
|
99
|
+
this.baseUrl = baseUrl;
|
|
100
|
+
this.workspaceId = config.workspaceId;
|
|
101
|
+
this.tenantId = config.tenantId;
|
|
102
|
+
}
|
|
103
|
+
var _proto = ApiClient.prototype;
|
|
104
|
+
_proto.getApiKey = function getApiKey() {
|
|
105
|
+
return this.apiKey;
|
|
106
|
+
};
|
|
107
|
+
_proto.getBaseUrl = function getBaseUrl() {
|
|
108
|
+
return this.baseUrl;
|
|
109
|
+
};
|
|
110
|
+
_proto.getWorkspaceId = function getWorkspaceId() {
|
|
111
|
+
return this.workspaceId;
|
|
112
|
+
};
|
|
113
|
+
_proto.getUser = function getUser() {
|
|
114
|
+
return this.user;
|
|
115
|
+
};
|
|
116
|
+
_proto.identify = function identify(user) {
|
|
117
|
+
this.user = user;
|
|
118
|
+
// this.fetchWithConfig(IDENTIFY_USER_ENDPOINT, {
|
|
119
|
+
// method: 'POST',
|
|
120
|
+
// body: transformIdentifyRequestData({
|
|
121
|
+
// ...user,
|
|
122
|
+
// }),
|
|
123
|
+
// });
|
|
124
|
+
};
|
|
125
|
+
_proto.track = function track(eventName, eventProperties) {
|
|
126
|
+
if (!this.user) {
|
|
127
|
+
throw new Error('No identified users to track');
|
|
128
|
+
}
|
|
129
|
+
this.fetchWithConfig(TRACK_ENDPOINT, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
body: transformTrackRequestData({
|
|
132
|
+
eventName: eventName,
|
|
133
|
+
eventProperties: eventProperties,
|
|
134
|
+
configProperties: {
|
|
135
|
+
workspaceId: this.getWorkspaceId(),
|
|
136
|
+
tenantId: this.tenantId
|
|
137
|
+
},
|
|
138
|
+
userProperties: {
|
|
139
|
+
email: this.user.email
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
return ApiClient;
|
|
145
|
+
}();
|
|
146
|
+
|
|
147
|
+
var SDKInstanceManager = /*#__PURE__*/function () {
|
|
148
|
+
function SDKInstanceManager() {
|
|
149
|
+
this.isInitialised = false;
|
|
150
|
+
this.config = {
|
|
151
|
+
workspaceId: ''
|
|
152
|
+
};
|
|
153
|
+
if (!SDKInstanceManager.instance) {
|
|
154
|
+
this.config = {
|
|
155
|
+
workspaceId: ''
|
|
156
|
+
};
|
|
157
|
+
this.isInitialised = false;
|
|
158
|
+
SDKInstanceManager.instance = this;
|
|
159
|
+
}
|
|
160
|
+
return SDKInstanceManager.instance;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* @throws Error in case validation of parameters fails
|
|
164
|
+
* @param apiKey - required string value representing the API key
|
|
165
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
166
|
+
* @param config - required object value
|
|
167
|
+
* @returns void
|
|
168
|
+
*/
|
|
169
|
+
var _proto = SDKInstanceManager.prototype;
|
|
170
|
+
_proto.initialise = function initialise(apiKey, baseUrl, config) {
|
|
171
|
+
if (this.getIsInitialised()) {
|
|
172
|
+
console.info('SDK is already initialised with API key');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
176
|
+
throw new Error('SDK needs a valid API key to be inialised');
|
|
177
|
+
}
|
|
178
|
+
if (!baseUrl || typeof baseUrl !== 'string' || !isValidUrl(baseUrl)) {
|
|
179
|
+
throw new Error('SDK needs a valid base URL to be initialised');
|
|
180
|
+
}
|
|
181
|
+
if (!config.workspaceId) {
|
|
182
|
+
throw new Error('Workspace ID must be provided');
|
|
183
|
+
}
|
|
184
|
+
this.config = config;
|
|
185
|
+
var sanitisedUrl = sanitiseUrl(baseUrl);
|
|
186
|
+
this.apiClient = new ApiClient({
|
|
187
|
+
apiKey: apiKey,
|
|
188
|
+
baseUrl: sanitisedUrl,
|
|
189
|
+
config: config
|
|
190
|
+
});
|
|
191
|
+
this.isInitialised = true;
|
|
192
|
+
};
|
|
193
|
+
_proto.destroy = function destroy() {
|
|
194
|
+
this.config = {
|
|
195
|
+
workspaceId: ''
|
|
196
|
+
};
|
|
197
|
+
this.isInitialised = false;
|
|
198
|
+
};
|
|
199
|
+
_proto.getIsInitialised = function getIsInitialised() {
|
|
200
|
+
return this.isInitialised;
|
|
201
|
+
};
|
|
202
|
+
_proto.getApiClient = function getApiClient() {
|
|
203
|
+
return this.apiClient;
|
|
204
|
+
};
|
|
205
|
+
_proto.getConfig = function getConfig() {
|
|
206
|
+
return this.config;
|
|
207
|
+
};
|
|
208
|
+
return SDKInstanceManager;
|
|
209
|
+
}();
|
|
210
|
+
var instance = /*#__PURE__*/new SDKInstanceManager();
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param apiKey - required string value representing the API key
|
|
214
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
215
|
+
* @param options - optional object value
|
|
216
|
+
* @returns boolean indicating whether the initialisation of the sdk was successful or not
|
|
217
|
+
*/
|
|
218
|
+
var initialise = function initialise(apiKey, baseUrl, config) {
|
|
219
|
+
try {
|
|
220
|
+
instance.initialise(apiKey, baseUrl, config);
|
|
221
|
+
return true;
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.info(e == null ? void 0 : e.message);
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
var identify = function identify(user) {
|
|
229
|
+
var _sdkInstanceManager$g;
|
|
230
|
+
(_sdkInstanceManager$g = instance.getApiClient()) == null || _sdkInstanceManager$g.identify(user);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
var track = function track(eventName, eventProperties) {
|
|
234
|
+
var _sdkInstanceManager$g;
|
|
235
|
+
if (!instance.getIsInitialised()) {
|
|
236
|
+
console.error('SDK must be initialised first');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (!eventName || typeof eventName !== 'string') {
|
|
240
|
+
console.error('Event name must be provided');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
(_sdkInstanceManager$g = instance.getApiClient()) == null || _sdkInstanceManager$g.track(eventName, eventProperties);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
exports.identify = identify;
|
|
247
|
+
exports.initialise = initialise;
|
|
248
|
+
exports.track = track;
|
|
249
|
+
//# sourceMappingURL=churnsignal-sdk-web.cjs.development.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churnsignal-sdk-web.cjs.development.js","sources":["../src/utils/url-validator.ts","../src/utils/request-transformations.ts","../src/api-client.ts","../src/managers/sdk-instance-manager.ts","../src/core/initialise.ts","../src/core/identify.ts","../src/core/track.ts"],"sourcesContent":["const urlRegex = /^https?:\\/\\//;\n\nexport const isValidUrl = (url: string) => urlRegex.test(url);\n\nexport const sanitiseUrl = (url: string) => {\n // Trim whitespace\n const sanitisedUrl = url.trim();\n\n // Validate scheme\n if (!isValidUrl(url)) {\n throw new Error(\"URL must start with 'http://' or 'https://'\");\n }\n\n try {\n let urlObject = new URL(sanitisedUrl);\n\n urlObject.hostname = urlObject.hostname.toLowerCase();\n\n // Remove trailing slash\n if (urlObject.pathname !== '/') {\n urlObject.pathname = urlObject.pathname.replace(/\\/+$/, '') || '';\n }\n\n // Reconstruct the URL from its components\n // Note: The URL interface automatically handles encoding of the pathname\n return urlObject.toString().replace(/\\/+$/, '');\n } catch (e) {\n throw new Error('Invalid URL provided');\n }\n};\n","import { EventProperties } from '../api-client';\n\ntype TrackRequest = {\n eventName: string;\n eventProperties?: EventProperties;\n userProperties?: {\n email: string;\n };\n configProperties: {\n workspaceId: string;\n tenantId?: string\n };\n};\n\nexport const transformTrackRequestData = ({\n eventName,\n eventProperties,\n userProperties,\n configProperties,\n}: TrackRequest) => {\n const transformedEvent = {\n name: eventName,\n timestamp: new Date().getTime().toString(),\n userEmail: userProperties?.email,\n tenantId: configProperties.tenantId, // TODO this will be handled on the API side, for now pass it in SDK setup\n workspaceId: configProperties.workspaceId,\n type: 'event',\n data: eventProperties,\n };\n return JSON.stringify(transformedEvent);\n};\n\ntype IdentifyRequest = {\n email: string\n}\n\nexport const transformIdentifyRequestData = ({\n email\n}: IdentifyRequest) => {\n const transformedIdentify = {\n userEmail: email\n }\n return JSON.stringify(transformedIdentify);\n}","import {\n transformTrackRequestData,\n // transformIdentifyRequestData,\n} from './utils/request-transformations';\n\ntype ApiClientProps = {\n apiKey: string;\n baseUrl?: string;\n config: {\n workspaceId: string;\n tenantId?: string;\n };\n};\n\nexport type EventProperties = Record<string, any>;\n\ntype User = {\n email: string;\n};\n\nconst sdkConfig = {\n defaultHeaders: {\n 'Content-Type': 'application/json',\n },\n};\n\nconst API_PREFIX = 'sdk-prod';\n\nconst TRACK_ENDPOINT = `${API_PREFIX}/track`;\n// const IDENTIFY_USER_ENDPOINT = `${API_PREFIX}/identify`;\n\nclass ApiClient {\n private apiKey: string;\n private baseUrl?: string;\n private workspaceId: string = '';\n private tenantId?: string;\n private user?: User;\n\n constructor({ apiKey, baseUrl, config }: ApiClientProps) {\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n this.workspaceId = config.workspaceId;\n this.tenantId = config.tenantId;\n }\n\n // Wrapper function for fetch\n fetchWithConfig = (\n endpoint: string,\n options: RequestInit = { method: 'GET' }\n ) => {\n // Construct the full URL\n const url = `${this.getBaseUrl()}/${endpoint}`;\n\n // Merge the default headers with any headers provided in the options\n const headers = {\n ...sdkConfig.defaultHeaders,\n 'x-api-key': this.getApiKey(),\n ...(options.headers || {}),\n };\n\n // Merge the rest of the options with the headers\n const config = {\n ...options,\n headers,\n };\n\n // Execute the fetch call with the merged configuration\n return fetch(url, config);\n };\n\n getApiKey() {\n return this.apiKey;\n }\n\n getBaseUrl() {\n return this.baseUrl;\n }\n\n getWorkspaceId() {\n return this.workspaceId;\n }\n\n getUser() {\n return this.user;\n }\n\n identify(user: User) {\n this.user = user;\n // this.fetchWithConfig(IDENTIFY_USER_ENDPOINT, {\n // method: 'POST',\n // body: transformIdentifyRequestData({\n // ...user,\n // }),\n // });\n }\n\n track(eventName: string, eventProperties?: EventProperties) {\n if (!this.user) {\n throw new Error('No identified users to track');\n }\n this.fetchWithConfig(TRACK_ENDPOINT, {\n method: 'POST',\n body: transformTrackRequestData({\n eventName,\n eventProperties,\n configProperties: {\n workspaceId: this.getWorkspaceId(),\n tenantId: this.tenantId,\n },\n userProperties: {\n email: this.user.email,\n },\n }),\n });\n }\n}\n\nexport default ApiClient;\n","import { isValidUrl, sanitiseUrl } from '../utils/url-validator';\nimport ApiClient from './../api-client';\n\ntype Config = {\n workspaceId: string;\n tenantId?: string;\n};\n\nclass SDKInstanceManager {\n private static instance: SDKInstanceManager;\n private isInitialised: boolean = false;\n private config: Config = { workspaceId: '' };\n private apiClient?: ApiClient;\n\n constructor() {\n if (!SDKInstanceManager.instance) {\n this.config = { workspaceId: '' };\n this.isInitialised = false;\n SDKInstanceManager.instance = this;\n }\n\n return SDKInstanceManager.instance;\n }\n\n /**\n * @throws Error in case validation of parameters fails\n * @param apiKey - required string value representing the API key\n * @param baseUrl - required string value representing the API endpoint\n * @param config - required object value\n * @returns void\n */\n initialise(apiKey: string, baseUrl: string, config: Config) {\n if (this.getIsInitialised()) {\n console.info('SDK is already initialised with API key');\n return;\n }\n if (!apiKey || typeof apiKey !== 'string') {\n throw new Error('SDK needs a valid API key to be inialised');\n }\n if (!baseUrl || typeof baseUrl !== 'string' || !isValidUrl(baseUrl)) {\n throw new Error('SDK needs a valid base URL to be initialised');\n }\n if (!config.workspaceId) {\n throw new Error('Workspace ID must be provided');\n }\n this.config = config;\n let sanitisedUrl = sanitiseUrl(baseUrl);\n this.apiClient = new ApiClient({ apiKey, baseUrl: sanitisedUrl, config });\n this.isInitialised = true;\n }\n\n destroy() {\n this.config = { workspaceId: '' };\n this.isInitialised = false;\n }\n\n getIsInitialised() {\n return this.isInitialised;\n }\n\n getApiClient() {\n return this.apiClient;\n }\n\n getConfig() {\n return this.config;\n }\n}\n\nconst instance = new SDKInstanceManager();\nexport { instance as default };\n","import sdkInstanceManager from './../managers/sdk-instance-manager';\n\nexport type InitConfig = {\n workspaceId: string\n tenantId: string; // TODO to be removed should be done on the API side\n};\n\n/**\n * @param apiKey - required string value representing the API key\n * @param baseUrl - required string value representing the API endpoint\n * @param options - optional object value\n * @returns boolean indicating whether the initialisation of the sdk was successful or not\n */\nexport const initialise = (\n apiKey: string,\n baseUrl: string,\n config: InitConfig\n) => {\n try {\n sdkInstanceManager.initialise(apiKey, baseUrl, config);\n return true;\n } catch (e) {\n console.info((e as any)?.message);\n return false;\n }\n};\n","import sdkInstanceManager from '../managers/sdk-instance-manager';\n\nexport const identify = (user: { email: string }) => {\n sdkInstanceManager.getApiClient()?.identify(user);\n};\n","import sdkInstanceManager from '../managers/sdk-instance-manager';\n\ntype Properties = Record<string, any>;\n\nexport const track = (eventName: string, eventProperties?: Properties) => {\n if (!sdkInstanceManager.getIsInitialised()) {\n console.error('SDK must be initialised first');\n return;\n }\n if (!eventName || typeof eventName !== 'string') {\n console.error('Event name must be provided');\n return;\n }\n sdkInstanceManager.getApiClient()?.track(eventName, eventProperties);\n};\n"],"names":["urlRegex","isValidUrl","url","test","sanitiseUrl","sanitisedUrl","trim","Error","urlObject","URL","hostname","toLowerCase","pathname","replace","toString","e","transformTrackRequestData","_ref","eventName","eventProperties","userProperties","configProperties","transformedEvent","name","timestamp","Date","getTime","userEmail","email","tenantId","workspaceId","type","data","JSON","stringify","sdkConfig","defaultHeaders","API_PREFIX","TRACK_ENDPOINT","ApiClient","apiKey","baseUrl","config","endpoint","options","method","_this","getBaseUrl","headers","_extends","getApiKey","fetch","_proto","prototype","getWorkspaceId","getUser","user","identify","track","fetchWithConfig","body","SDKInstanceManager","instance","isInitialised","initialise","getIsInitialised","console","info","apiClient","destroy","getApiClient","getConfig","sdkInstanceManager","message","_sdkInstanceManager$g","error"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=/^https?:\/\//,e=function(e){return t.test(e)};function i(){return(i=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t}).apply(this,arguments)}var n=function(t){var e=t.eventProperties,i=t.userProperties,n=t.configProperties,r={name:t.eventName,timestamp:(new Date).getTime().toString(),userEmail:null==i?void 0:i.email,tenantId:n.tenantId,workspaceId:n.workspaceId,type:"event",data:e};return JSON.stringify(r)},r={"Content-Type":"application/json"},s=function(){function t(t){var e=this,n=t.apiKey,s=t.baseUrl,o=t.config;this.workspaceId="",this.fetchWithConfig=function(t,n){void 0===n&&(n={method:"GET"});var s=e.getBaseUrl()+"/"+t,o=i({},r,{"x-api-key":e.getApiKey()},n.headers||{}),a=i({},n,{headers:o});return fetch(s,a)},this.apiKey=n,this.baseUrl=s,this.workspaceId=o.workspaceId,this.tenantId=o.tenantId}var e=t.prototype;return e.getApiKey=function(){return this.apiKey},e.getBaseUrl=function(){return this.baseUrl},e.getWorkspaceId=function(){return this.workspaceId},e.getUser=function(){return this.user},e.identify=function(t){this.user=t},e.track=function(t,e){if(!this.user)throw new Error("No identified users to track");this.fetchWithConfig("sdk-prod/track",{method:"POST",body:n({eventName:t,eventProperties:e,configProperties:{workspaceId:this.getWorkspaceId(),tenantId:this.tenantId},userProperties:{email:this.user.email}})})},t}(),o=new(function(){function t(){return this.isInitialised=!1,this.config={workspaceId:""},t.instance||(this.config={workspaceId:""},this.isInitialised=!1,t.instance=this),t.instance}var i=t.prototype;return i.initialise=function(t,i,n){if(this.getIsInitialised())console.info("SDK is already initialised with API key");else{if(!t||"string"!=typeof t)throw new Error("SDK needs a valid API key to be inialised");if(!i||"string"!=typeof i||!e(i))throw new Error("SDK needs a valid base URL to be initialised");if(!n.workspaceId)throw new Error("Workspace ID must be provided");this.config=n;var r=function(t){var i=t.trim();if(!e(t))throw new Error("URL must start with 'http://' or 'https://'");try{var n=new URL(i);return n.hostname=n.hostname.toLowerCase(),"/"!==n.pathname&&(n.pathname=n.pathname.replace(/\/+$/,"")||""),n.toString().replace(/\/+$/,"")}catch(t){throw new Error("Invalid URL provided")}}(i);this.apiClient=new s({apiKey:t,baseUrl:r,config:n}),this.isInitialised=!0}},i.destroy=function(){this.config={workspaceId:""},this.isInitialised=!1},i.getIsInitialised=function(){return this.isInitialised},i.getApiClient=function(){return this.apiClient},i.getConfig=function(){return this.config},t}());exports.identify=function(t){var e;null==(e=o.getApiClient())||e.identify(t)},exports.initialise=function(t,e,i){try{return o.initialise(t,e,i),!0}catch(t){return console.info(null==t?void 0:t.message),!1}},exports.track=function(t,e){var i;o.getIsInitialised()?t&&"string"==typeof t?null==(i=o.getApiClient())||i.track(t,e):console.error("Event name must be provided"):console.error("SDK must be initialised first")};
|
|
2
|
+
//# sourceMappingURL=churnsignal-sdk-web.cjs.production.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churnsignal-sdk-web.cjs.production.min.js","sources":["../src/utils/url-validator.ts","../src/utils/request-transformations.ts","../src/api-client.ts","../src/managers/sdk-instance-manager.ts","../src/core/identify.ts","../src/core/initialise.ts","../src/core/track.ts"],"sourcesContent":["const urlRegex = /^https?:\\/\\//;\n\nexport const isValidUrl = (url: string) => urlRegex.test(url);\n\nexport const sanitiseUrl = (url: string) => {\n // Trim whitespace\n const sanitisedUrl = url.trim();\n\n // Validate scheme\n if (!isValidUrl(url)) {\n throw new Error(\"URL must start with 'http://' or 'https://'\");\n }\n\n try {\n let urlObject = new URL(sanitisedUrl);\n\n urlObject.hostname = urlObject.hostname.toLowerCase();\n\n // Remove trailing slash\n if (urlObject.pathname !== '/') {\n urlObject.pathname = urlObject.pathname.replace(/\\/+$/, '') || '';\n }\n\n // Reconstruct the URL from its components\n // Note: The URL interface automatically handles encoding of the pathname\n return urlObject.toString().replace(/\\/+$/, '');\n } catch (e) {\n throw new Error('Invalid URL provided');\n }\n};\n","import { EventProperties } from '../api-client';\n\ntype TrackRequest = {\n eventName: string;\n eventProperties?: EventProperties;\n userProperties?: {\n email: string;\n };\n configProperties: {\n workspaceId: string;\n tenantId?: string\n };\n};\n\nexport const transformTrackRequestData = ({\n eventName,\n eventProperties,\n userProperties,\n configProperties,\n}: TrackRequest) => {\n const transformedEvent = {\n name: eventName,\n timestamp: new Date().getTime().toString(),\n userEmail: userProperties?.email,\n tenantId: configProperties.tenantId, // TODO this will be handled on the API side, for now pass it in SDK setup\n workspaceId: configProperties.workspaceId,\n type: 'event',\n data: eventProperties,\n };\n return JSON.stringify(transformedEvent);\n};\n\ntype IdentifyRequest = {\n email: string\n}\n\nexport const transformIdentifyRequestData = ({\n email\n}: IdentifyRequest) => {\n const transformedIdentify = {\n userEmail: email\n }\n return JSON.stringify(transformedIdentify);\n}","import {\n transformTrackRequestData,\n // transformIdentifyRequestData,\n} from './utils/request-transformations';\n\ntype ApiClientProps = {\n apiKey: string;\n baseUrl?: string;\n config: {\n workspaceId: string;\n tenantId?: string;\n };\n};\n\nexport type EventProperties = Record<string, any>;\n\ntype User = {\n email: string;\n};\n\nconst sdkConfig = {\n defaultHeaders: {\n 'Content-Type': 'application/json',\n },\n};\n\nconst API_PREFIX = 'sdk-prod';\n\nconst TRACK_ENDPOINT = `${API_PREFIX}/track`;\n// const IDENTIFY_USER_ENDPOINT = `${API_PREFIX}/identify`;\n\nclass ApiClient {\n private apiKey: string;\n private baseUrl?: string;\n private workspaceId: string = '';\n private tenantId?: string;\n private user?: User;\n\n constructor({ apiKey, baseUrl, config }: ApiClientProps) {\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n this.workspaceId = config.workspaceId;\n this.tenantId = config.tenantId;\n }\n\n // Wrapper function for fetch\n fetchWithConfig = (\n endpoint: string,\n options: RequestInit = { method: 'GET' }\n ) => {\n // Construct the full URL\n const url = `${this.getBaseUrl()}/${endpoint}`;\n\n // Merge the default headers with any headers provided in the options\n const headers = {\n ...sdkConfig.defaultHeaders,\n 'x-api-key': this.getApiKey(),\n ...(options.headers || {}),\n };\n\n // Merge the rest of the options with the headers\n const config = {\n ...options,\n headers,\n };\n\n // Execute the fetch call with the merged configuration\n return fetch(url, config);\n };\n\n getApiKey() {\n return this.apiKey;\n }\n\n getBaseUrl() {\n return this.baseUrl;\n }\n\n getWorkspaceId() {\n return this.workspaceId;\n }\n\n getUser() {\n return this.user;\n }\n\n identify(user: User) {\n this.user = user;\n // this.fetchWithConfig(IDENTIFY_USER_ENDPOINT, {\n // method: 'POST',\n // body: transformIdentifyRequestData({\n // ...user,\n // }),\n // });\n }\n\n track(eventName: string, eventProperties?: EventProperties) {\n if (!this.user) {\n throw new Error('No identified users to track');\n }\n this.fetchWithConfig(TRACK_ENDPOINT, {\n method: 'POST',\n body: transformTrackRequestData({\n eventName,\n eventProperties,\n configProperties: {\n workspaceId: this.getWorkspaceId(),\n tenantId: this.tenantId,\n },\n userProperties: {\n email: this.user.email,\n },\n }),\n });\n }\n}\n\nexport default ApiClient;\n","import { isValidUrl, sanitiseUrl } from '../utils/url-validator';\nimport ApiClient from './../api-client';\n\ntype Config = {\n workspaceId: string;\n tenantId?: string;\n};\n\nclass SDKInstanceManager {\n private static instance: SDKInstanceManager;\n private isInitialised: boolean = false;\n private config: Config = { workspaceId: '' };\n private apiClient?: ApiClient;\n\n constructor() {\n if (!SDKInstanceManager.instance) {\n this.config = { workspaceId: '' };\n this.isInitialised = false;\n SDKInstanceManager.instance = this;\n }\n\n return SDKInstanceManager.instance;\n }\n\n /**\n * @throws Error in case validation of parameters fails\n * @param apiKey - required string value representing the API key\n * @param baseUrl - required string value representing the API endpoint\n * @param config - required object value\n * @returns void\n */\n initialise(apiKey: string, baseUrl: string, config: Config) {\n if (this.getIsInitialised()) {\n console.info('SDK is already initialised with API key');\n return;\n }\n if (!apiKey || typeof apiKey !== 'string') {\n throw new Error('SDK needs a valid API key to be inialised');\n }\n if (!baseUrl || typeof baseUrl !== 'string' || !isValidUrl(baseUrl)) {\n throw new Error('SDK needs a valid base URL to be initialised');\n }\n if (!config.workspaceId) {\n throw new Error('Workspace ID must be provided');\n }\n this.config = config;\n let sanitisedUrl = sanitiseUrl(baseUrl);\n this.apiClient = new ApiClient({ apiKey, baseUrl: sanitisedUrl, config });\n this.isInitialised = true;\n }\n\n destroy() {\n this.config = { workspaceId: '' };\n this.isInitialised = false;\n }\n\n getIsInitialised() {\n return this.isInitialised;\n }\n\n getApiClient() {\n return this.apiClient;\n }\n\n getConfig() {\n return this.config;\n }\n}\n\nconst instance = new SDKInstanceManager();\nexport { instance as default };\n","import sdkInstanceManager from '../managers/sdk-instance-manager';\n\nexport const identify = (user: { email: string }) => {\n sdkInstanceManager.getApiClient()?.identify(user);\n};\n","import sdkInstanceManager from './../managers/sdk-instance-manager';\n\nexport type InitConfig = {\n workspaceId: string\n tenantId: string; // TODO to be removed should be done on the API side\n};\n\n/**\n * @param apiKey - required string value representing the API key\n * @param baseUrl - required string value representing the API endpoint\n * @param options - optional object value\n * @returns boolean indicating whether the initialisation of the sdk was successful or not\n */\nexport const initialise = (\n apiKey: string,\n baseUrl: string,\n config: InitConfig\n) => {\n try {\n sdkInstanceManager.initialise(apiKey, baseUrl, config);\n return true;\n } catch (e) {\n console.info((e as any)?.message);\n return false;\n }\n};\n","import sdkInstanceManager from '../managers/sdk-instance-manager';\n\ntype Properties = Record<string, any>;\n\nexport const track = (eventName: string, eventProperties?: Properties) => {\n if (!sdkInstanceManager.getIsInitialised()) {\n console.error('SDK must be initialised first');\n return;\n }\n if (!eventName || typeof eventName !== 'string') {\n console.error('Event name must be provided');\n return;\n }\n sdkInstanceManager.getApiClient()?.track(eventName, eventProperties);\n};\n"],"names":["urlRegex","isValidUrl","url","test","transformTrackRequestData","_ref","eventProperties","userProperties","configProperties","transformedEvent","name","eventName","timestamp","Date","getTime","toString","userEmail","email","tenantId","workspaceId","type","data","JSON","stringify","sdkConfig","Content-Type","ApiClient","apiKey","baseUrl","config","this","endpoint","options","method","_this","getBaseUrl","headers","_extends","x-api-key","getApiKey","fetch","_proto","prototype","getWorkspaceId","getUser","user","identify","track","Error","fetchWithConfig","API_PREFIX","body","instance","SDKInstanceManager","isInitialised","initialise","getIsInitialised","console","info","sanitisedUrl","trim","urlObject","URL","hostname","toLowerCase","pathname","replace","e","sanitiseUrl","apiClient","destroy","getApiClient","getConfig","_sdkInstanceManager$g","sdkInstanceManager","message","error"],"mappings":"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"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
var urlRegex = /^https?:\/\//;
|
|
2
|
+
var isValidUrl = function isValidUrl(url) {
|
|
3
|
+
return urlRegex.test(url);
|
|
4
|
+
};
|
|
5
|
+
var sanitiseUrl = function sanitiseUrl(url) {
|
|
6
|
+
// Trim whitespace
|
|
7
|
+
var sanitisedUrl = url.trim();
|
|
8
|
+
// Validate scheme
|
|
9
|
+
if (!isValidUrl(url)) {
|
|
10
|
+
throw new Error("URL must start with 'http://' or 'https://'");
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
var urlObject = new URL(sanitisedUrl);
|
|
14
|
+
urlObject.hostname = urlObject.hostname.toLowerCase();
|
|
15
|
+
// Remove trailing slash
|
|
16
|
+
if (urlObject.pathname !== '/') {
|
|
17
|
+
urlObject.pathname = urlObject.pathname.replace(/\/+$/, '') || '';
|
|
18
|
+
}
|
|
19
|
+
// Reconstruct the URL from its components
|
|
20
|
+
// Note: The URL interface automatically handles encoding of the pathname
|
|
21
|
+
return urlObject.toString().replace(/\/+$/, '');
|
|
22
|
+
} catch (e) {
|
|
23
|
+
throw new Error('Invalid URL provided');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function _extends() {
|
|
28
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
29
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
30
|
+
var source = arguments[i];
|
|
31
|
+
for (var key in source) {
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
33
|
+
target[key] = source[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return target;
|
|
38
|
+
};
|
|
39
|
+
return _extends.apply(this, arguments);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var transformTrackRequestData = function transformTrackRequestData(_ref) {
|
|
43
|
+
var eventName = _ref.eventName,
|
|
44
|
+
eventProperties = _ref.eventProperties,
|
|
45
|
+
userProperties = _ref.userProperties,
|
|
46
|
+
configProperties = _ref.configProperties;
|
|
47
|
+
var transformedEvent = {
|
|
48
|
+
name: eventName,
|
|
49
|
+
timestamp: new Date().getTime().toString(),
|
|
50
|
+
userEmail: userProperties == null ? void 0 : userProperties.email,
|
|
51
|
+
tenantId: configProperties.tenantId,
|
|
52
|
+
workspaceId: configProperties.workspaceId,
|
|
53
|
+
type: 'event',
|
|
54
|
+
data: eventProperties
|
|
55
|
+
};
|
|
56
|
+
return JSON.stringify(transformedEvent);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
var sdkConfig = {
|
|
60
|
+
defaultHeaders: {
|
|
61
|
+
'Content-Type': 'application/json'
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var API_PREFIX = 'sdk-prod';
|
|
65
|
+
var TRACK_ENDPOINT = API_PREFIX + "/track";
|
|
66
|
+
// const IDENTIFY_USER_ENDPOINT = `${API_PREFIX}/identify`;
|
|
67
|
+
var ApiClient = /*#__PURE__*/function () {
|
|
68
|
+
function ApiClient(_ref) {
|
|
69
|
+
var _this = this;
|
|
70
|
+
var apiKey = _ref.apiKey,
|
|
71
|
+
baseUrl = _ref.baseUrl,
|
|
72
|
+
config = _ref.config;
|
|
73
|
+
this.workspaceId = '';
|
|
74
|
+
// Wrapper function for fetch
|
|
75
|
+
this.fetchWithConfig = function (endpoint, options) {
|
|
76
|
+
if (options === void 0) {
|
|
77
|
+
options = {
|
|
78
|
+
method: 'GET'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Construct the full URL
|
|
82
|
+
var url = _this.getBaseUrl() + "/" + endpoint;
|
|
83
|
+
// Merge the default headers with any headers provided in the options
|
|
84
|
+
var headers = _extends({}, sdkConfig.defaultHeaders, {
|
|
85
|
+
'x-api-key': _this.getApiKey()
|
|
86
|
+
}, options.headers || {});
|
|
87
|
+
// Merge the rest of the options with the headers
|
|
88
|
+
var config = _extends({}, options, {
|
|
89
|
+
headers: headers
|
|
90
|
+
});
|
|
91
|
+
// Execute the fetch call with the merged configuration
|
|
92
|
+
return fetch(url, config);
|
|
93
|
+
};
|
|
94
|
+
this.apiKey = apiKey;
|
|
95
|
+
this.baseUrl = baseUrl;
|
|
96
|
+
this.workspaceId = config.workspaceId;
|
|
97
|
+
this.tenantId = config.tenantId;
|
|
98
|
+
}
|
|
99
|
+
var _proto = ApiClient.prototype;
|
|
100
|
+
_proto.getApiKey = function getApiKey() {
|
|
101
|
+
return this.apiKey;
|
|
102
|
+
};
|
|
103
|
+
_proto.getBaseUrl = function getBaseUrl() {
|
|
104
|
+
return this.baseUrl;
|
|
105
|
+
};
|
|
106
|
+
_proto.getWorkspaceId = function getWorkspaceId() {
|
|
107
|
+
return this.workspaceId;
|
|
108
|
+
};
|
|
109
|
+
_proto.getUser = function getUser() {
|
|
110
|
+
return this.user;
|
|
111
|
+
};
|
|
112
|
+
_proto.identify = function identify(user) {
|
|
113
|
+
this.user = user;
|
|
114
|
+
// this.fetchWithConfig(IDENTIFY_USER_ENDPOINT, {
|
|
115
|
+
// method: 'POST',
|
|
116
|
+
// body: transformIdentifyRequestData({
|
|
117
|
+
// ...user,
|
|
118
|
+
// }),
|
|
119
|
+
// });
|
|
120
|
+
};
|
|
121
|
+
_proto.track = function track(eventName, eventProperties) {
|
|
122
|
+
if (!this.user) {
|
|
123
|
+
throw new Error('No identified users to track');
|
|
124
|
+
}
|
|
125
|
+
this.fetchWithConfig(TRACK_ENDPOINT, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
body: transformTrackRequestData({
|
|
128
|
+
eventName: eventName,
|
|
129
|
+
eventProperties: eventProperties,
|
|
130
|
+
configProperties: {
|
|
131
|
+
workspaceId: this.getWorkspaceId(),
|
|
132
|
+
tenantId: this.tenantId
|
|
133
|
+
},
|
|
134
|
+
userProperties: {
|
|
135
|
+
email: this.user.email
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
return ApiClient;
|
|
141
|
+
}();
|
|
142
|
+
|
|
143
|
+
var SDKInstanceManager = /*#__PURE__*/function () {
|
|
144
|
+
function SDKInstanceManager() {
|
|
145
|
+
this.isInitialised = false;
|
|
146
|
+
this.config = {
|
|
147
|
+
workspaceId: ''
|
|
148
|
+
};
|
|
149
|
+
if (!SDKInstanceManager.instance) {
|
|
150
|
+
this.config = {
|
|
151
|
+
workspaceId: ''
|
|
152
|
+
};
|
|
153
|
+
this.isInitialised = false;
|
|
154
|
+
SDKInstanceManager.instance = this;
|
|
155
|
+
}
|
|
156
|
+
return SDKInstanceManager.instance;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* @throws Error in case validation of parameters fails
|
|
160
|
+
* @param apiKey - required string value representing the API key
|
|
161
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
162
|
+
* @param config - required object value
|
|
163
|
+
* @returns void
|
|
164
|
+
*/
|
|
165
|
+
var _proto = SDKInstanceManager.prototype;
|
|
166
|
+
_proto.initialise = function initialise(apiKey, baseUrl, config) {
|
|
167
|
+
if (this.getIsInitialised()) {
|
|
168
|
+
console.info('SDK is already initialised with API key');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
172
|
+
throw new Error('SDK needs a valid API key to be inialised');
|
|
173
|
+
}
|
|
174
|
+
if (!baseUrl || typeof baseUrl !== 'string' || !isValidUrl(baseUrl)) {
|
|
175
|
+
throw new Error('SDK needs a valid base URL to be initialised');
|
|
176
|
+
}
|
|
177
|
+
if (!config.workspaceId) {
|
|
178
|
+
throw new Error('Workspace ID must be provided');
|
|
179
|
+
}
|
|
180
|
+
this.config = config;
|
|
181
|
+
var sanitisedUrl = sanitiseUrl(baseUrl);
|
|
182
|
+
this.apiClient = new ApiClient({
|
|
183
|
+
apiKey: apiKey,
|
|
184
|
+
baseUrl: sanitisedUrl,
|
|
185
|
+
config: config
|
|
186
|
+
});
|
|
187
|
+
this.isInitialised = true;
|
|
188
|
+
};
|
|
189
|
+
_proto.destroy = function destroy() {
|
|
190
|
+
this.config = {
|
|
191
|
+
workspaceId: ''
|
|
192
|
+
};
|
|
193
|
+
this.isInitialised = false;
|
|
194
|
+
};
|
|
195
|
+
_proto.getIsInitialised = function getIsInitialised() {
|
|
196
|
+
return this.isInitialised;
|
|
197
|
+
};
|
|
198
|
+
_proto.getApiClient = function getApiClient() {
|
|
199
|
+
return this.apiClient;
|
|
200
|
+
};
|
|
201
|
+
_proto.getConfig = function getConfig() {
|
|
202
|
+
return this.config;
|
|
203
|
+
};
|
|
204
|
+
return SDKInstanceManager;
|
|
205
|
+
}();
|
|
206
|
+
var instance = /*#__PURE__*/new SDKInstanceManager();
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param apiKey - required string value representing the API key
|
|
210
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
211
|
+
* @param options - optional object value
|
|
212
|
+
* @returns boolean indicating whether the initialisation of the sdk was successful or not
|
|
213
|
+
*/
|
|
214
|
+
var initialise = function initialise(apiKey, baseUrl, config) {
|
|
215
|
+
try {
|
|
216
|
+
instance.initialise(apiKey, baseUrl, config);
|
|
217
|
+
return true;
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.info(e == null ? void 0 : e.message);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
var identify = function identify(user) {
|
|
225
|
+
var _sdkInstanceManager$g;
|
|
226
|
+
(_sdkInstanceManager$g = instance.getApiClient()) == null || _sdkInstanceManager$g.identify(user);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
var track = function track(eventName, eventProperties) {
|
|
230
|
+
var _sdkInstanceManager$g;
|
|
231
|
+
if (!instance.getIsInitialised()) {
|
|
232
|
+
console.error('SDK must be initialised first');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (!eventName || typeof eventName !== 'string') {
|
|
236
|
+
console.error('Event name must be provided');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
(_sdkInstanceManager$g = instance.getApiClient()) == null || _sdkInstanceManager$g.track(eventName, eventProperties);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export { identify, initialise, track };
|
|
243
|
+
//# sourceMappingURL=churnsignal-sdk-web.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churnsignal-sdk-web.esm.js","sources":["../src/utils/url-validator.ts","../src/utils/request-transformations.ts","../src/api-client.ts","../src/managers/sdk-instance-manager.ts","../src/core/initialise.ts","../src/core/identify.ts","../src/core/track.ts"],"sourcesContent":["const urlRegex = /^https?:\\/\\//;\n\nexport const isValidUrl = (url: string) => urlRegex.test(url);\n\nexport const sanitiseUrl = (url: string) => {\n // Trim whitespace\n const sanitisedUrl = url.trim();\n\n // Validate scheme\n if (!isValidUrl(url)) {\n throw new Error(\"URL must start with 'http://' or 'https://'\");\n }\n\n try {\n let urlObject = new URL(sanitisedUrl);\n\n urlObject.hostname = urlObject.hostname.toLowerCase();\n\n // Remove trailing slash\n if (urlObject.pathname !== '/') {\n urlObject.pathname = urlObject.pathname.replace(/\\/+$/, '') || '';\n }\n\n // Reconstruct the URL from its components\n // Note: The URL interface automatically handles encoding of the pathname\n return urlObject.toString().replace(/\\/+$/, '');\n } catch (e) {\n throw new Error('Invalid URL provided');\n }\n};\n","import { EventProperties } from '../api-client';\n\ntype TrackRequest = {\n eventName: string;\n eventProperties?: EventProperties;\n userProperties?: {\n email: string;\n };\n configProperties: {\n workspaceId: string;\n tenantId?: string\n };\n};\n\nexport const transformTrackRequestData = ({\n eventName,\n eventProperties,\n userProperties,\n configProperties,\n}: TrackRequest) => {\n const transformedEvent = {\n name: eventName,\n timestamp: new Date().getTime().toString(),\n userEmail: userProperties?.email,\n tenantId: configProperties.tenantId, // TODO this will be handled on the API side, for now pass it in SDK setup\n workspaceId: configProperties.workspaceId,\n type: 'event',\n data: eventProperties,\n };\n return JSON.stringify(transformedEvent);\n};\n\ntype IdentifyRequest = {\n email: string\n}\n\nexport const transformIdentifyRequestData = ({\n email\n}: IdentifyRequest) => {\n const transformedIdentify = {\n userEmail: email\n }\n return JSON.stringify(transformedIdentify);\n}","import {\n transformTrackRequestData,\n // transformIdentifyRequestData,\n} from './utils/request-transformations';\n\ntype ApiClientProps = {\n apiKey: string;\n baseUrl?: string;\n config: {\n workspaceId: string;\n tenantId?: string;\n };\n};\n\nexport type EventProperties = Record<string, any>;\n\ntype User = {\n email: string;\n};\n\nconst sdkConfig = {\n defaultHeaders: {\n 'Content-Type': 'application/json',\n },\n};\n\nconst API_PREFIX = 'sdk-prod';\n\nconst TRACK_ENDPOINT = `${API_PREFIX}/track`;\n// const IDENTIFY_USER_ENDPOINT = `${API_PREFIX}/identify`;\n\nclass ApiClient {\n private apiKey: string;\n private baseUrl?: string;\n private workspaceId: string = '';\n private tenantId?: string;\n private user?: User;\n\n constructor({ apiKey, baseUrl, config }: ApiClientProps) {\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n this.workspaceId = config.workspaceId;\n this.tenantId = config.tenantId;\n }\n\n // Wrapper function for fetch\n fetchWithConfig = (\n endpoint: string,\n options: RequestInit = { method: 'GET' }\n ) => {\n // Construct the full URL\n const url = `${this.getBaseUrl()}/${endpoint}`;\n\n // Merge the default headers with any headers provided in the options\n const headers = {\n ...sdkConfig.defaultHeaders,\n 'x-api-key': this.getApiKey(),\n ...(options.headers || {}),\n };\n\n // Merge the rest of the options with the headers\n const config = {\n ...options,\n headers,\n };\n\n // Execute the fetch call with the merged configuration\n return fetch(url, config);\n };\n\n getApiKey() {\n return this.apiKey;\n }\n\n getBaseUrl() {\n return this.baseUrl;\n }\n\n getWorkspaceId() {\n return this.workspaceId;\n }\n\n getUser() {\n return this.user;\n }\n\n identify(user: User) {\n this.user = user;\n // this.fetchWithConfig(IDENTIFY_USER_ENDPOINT, {\n // method: 'POST',\n // body: transformIdentifyRequestData({\n // ...user,\n // }),\n // });\n }\n\n track(eventName: string, eventProperties?: EventProperties) {\n if (!this.user) {\n throw new Error('No identified users to track');\n }\n this.fetchWithConfig(TRACK_ENDPOINT, {\n method: 'POST',\n body: transformTrackRequestData({\n eventName,\n eventProperties,\n configProperties: {\n workspaceId: this.getWorkspaceId(),\n tenantId: this.tenantId,\n },\n userProperties: {\n email: this.user.email,\n },\n }),\n });\n }\n}\n\nexport default ApiClient;\n","import { isValidUrl, sanitiseUrl } from '../utils/url-validator';\nimport ApiClient from './../api-client';\n\ntype Config = {\n workspaceId: string;\n tenantId?: string;\n};\n\nclass SDKInstanceManager {\n private static instance: SDKInstanceManager;\n private isInitialised: boolean = false;\n private config: Config = { workspaceId: '' };\n private apiClient?: ApiClient;\n\n constructor() {\n if (!SDKInstanceManager.instance) {\n this.config = { workspaceId: '' };\n this.isInitialised = false;\n SDKInstanceManager.instance = this;\n }\n\n return SDKInstanceManager.instance;\n }\n\n /**\n * @throws Error in case validation of parameters fails\n * @param apiKey - required string value representing the API key\n * @param baseUrl - required string value representing the API endpoint\n * @param config - required object value\n * @returns void\n */\n initialise(apiKey: string, baseUrl: string, config: Config) {\n if (this.getIsInitialised()) {\n console.info('SDK is already initialised with API key');\n return;\n }\n if (!apiKey || typeof apiKey !== 'string') {\n throw new Error('SDK needs a valid API key to be inialised');\n }\n if (!baseUrl || typeof baseUrl !== 'string' || !isValidUrl(baseUrl)) {\n throw new Error('SDK needs a valid base URL to be initialised');\n }\n if (!config.workspaceId) {\n throw new Error('Workspace ID must be provided');\n }\n this.config = config;\n let sanitisedUrl = sanitiseUrl(baseUrl);\n this.apiClient = new ApiClient({ apiKey, baseUrl: sanitisedUrl, config });\n this.isInitialised = true;\n }\n\n destroy() {\n this.config = { workspaceId: '' };\n this.isInitialised = false;\n }\n\n getIsInitialised() {\n return this.isInitialised;\n }\n\n getApiClient() {\n return this.apiClient;\n }\n\n getConfig() {\n return this.config;\n }\n}\n\nconst instance = new SDKInstanceManager();\nexport { instance as default };\n","import sdkInstanceManager from './../managers/sdk-instance-manager';\n\nexport type InitConfig = {\n workspaceId: string\n tenantId: string; // TODO to be removed should be done on the API side\n};\n\n/**\n * @param apiKey - required string value representing the API key\n * @param baseUrl - required string value representing the API endpoint\n * @param options - optional object value\n * @returns boolean indicating whether the initialisation of the sdk was successful or not\n */\nexport const initialise = (\n apiKey: string,\n baseUrl: string,\n config: InitConfig\n) => {\n try {\n sdkInstanceManager.initialise(apiKey, baseUrl, config);\n return true;\n } catch (e) {\n console.info((e as any)?.message);\n return false;\n }\n};\n","import sdkInstanceManager from '../managers/sdk-instance-manager';\n\nexport const identify = (user: { email: string }) => {\n sdkInstanceManager.getApiClient()?.identify(user);\n};\n","import sdkInstanceManager from '../managers/sdk-instance-manager';\n\ntype Properties = Record<string, any>;\n\nexport const track = (eventName: string, eventProperties?: Properties) => {\n if (!sdkInstanceManager.getIsInitialised()) {\n console.error('SDK must be initialised first');\n return;\n }\n if (!eventName || typeof eventName !== 'string') {\n console.error('Event name must be provided');\n return;\n }\n sdkInstanceManager.getApiClient()?.track(eventName, eventProperties);\n};\n"],"names":["urlRegex","isValidUrl","url","test","sanitiseUrl","sanitisedUrl","trim","Error","urlObject","URL","hostname","toLowerCase","pathname","replace","toString","e","transformTrackRequestData","_ref","eventName","eventProperties","userProperties","configProperties","transformedEvent","name","timestamp","Date","getTime","userEmail","email","tenantId","workspaceId","type","data","JSON","stringify","sdkConfig","defaultHeaders","API_PREFIX","TRACK_ENDPOINT","ApiClient","apiKey","baseUrl","config","endpoint","options","method","_this","getBaseUrl","headers","_extends","getApiKey","fetch","_proto","prototype","getWorkspaceId","getUser","user","identify","track","fetchWithConfig","body","SDKInstanceManager","instance","isInitialised","initialise","getIsInitialised","console","info","apiClient","destroy","getApiClient","getConfig","sdkInstanceManager","message","_sdkInstanceManager$g","error"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const destroy: () => void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare type InitConfig = {
|
|
2
|
+
workspaceId: string;
|
|
3
|
+
tenantId: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* @param apiKey - required string value representing the API key
|
|
7
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
8
|
+
* @param options - optional object value
|
|
9
|
+
* @returns boolean indicating whether the initialisation of the sdk was successful or not
|
|
10
|
+
*/
|
|
11
|
+
export declare const initialise: (apiKey: string, baseUrl: string, config: InitConfig) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const track: (eventName: string, eventProperties?: Record<string, any> | undefined) => void;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './core/index';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sdk-instance-manager';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import ApiClient from './../api-client';
|
|
2
|
+
declare type Config = {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
tenantId?: string;
|
|
5
|
+
};
|
|
6
|
+
declare class SDKInstanceManager {
|
|
7
|
+
private static instance;
|
|
8
|
+
private isInitialised;
|
|
9
|
+
private config;
|
|
10
|
+
private apiClient?;
|
|
11
|
+
constructor();
|
|
12
|
+
/**
|
|
13
|
+
* @throws Error in case validation of parameters fails
|
|
14
|
+
* @param apiKey - required string value representing the API key
|
|
15
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
16
|
+
* @param config - required object value
|
|
17
|
+
* @returns void
|
|
18
|
+
*/
|
|
19
|
+
initialise(apiKey: string, baseUrl: string, config: Config): void;
|
|
20
|
+
destroy(): void;
|
|
21
|
+
getIsInitialised(): boolean;
|
|
22
|
+
getApiClient(): ApiClient | undefined;
|
|
23
|
+
getConfig(): Config;
|
|
24
|
+
}
|
|
25
|
+
declare const instance: SDKInstanceManager;
|
|
26
|
+
export { instance as default };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EventProperties } from '../api-client';
|
|
2
|
+
declare type TrackRequest = {
|
|
3
|
+
eventName: string;
|
|
4
|
+
eventProperties?: EventProperties;
|
|
5
|
+
userProperties?: {
|
|
6
|
+
email: string;
|
|
7
|
+
};
|
|
8
|
+
configProperties: {
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
tenantId?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export declare const transformTrackRequestData: ({ eventName, eventProperties, userProperties, configProperties, }: TrackRequest) => string;
|
|
14
|
+
declare type IdentifyRequest = {
|
|
15
|
+
email: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const transformIdentifyRequestData: ({ email }: IdentifyRequest) => string;
|
|
18
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.1",
|
|
3
|
+
"license": "ISC",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"typings": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=10"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "tsdx watch",
|
|
15
|
+
"build": "tsdx build",
|
|
16
|
+
"test": "tsdx test",
|
|
17
|
+
"lint": "tsdx lint",
|
|
18
|
+
"prepare": "tsdx build",
|
|
19
|
+
"size": "size-limit",
|
|
20
|
+
"analyze": "size-limit --why"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {},
|
|
23
|
+
"husky": {
|
|
24
|
+
"hooks": {
|
|
25
|
+
"pre-commit": "tsdx lint"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"prettier": {
|
|
29
|
+
"printWidth": 80,
|
|
30
|
+
"semi": true,
|
|
31
|
+
"singleQuote": true,
|
|
32
|
+
"trailingComma": "es5"
|
|
33
|
+
},
|
|
34
|
+
"name": "@churnsignal/churnsignal-sdk-web",
|
|
35
|
+
"author": "cyberraven",
|
|
36
|
+
"module": "dist/churnsignal-sdk-web.esm.js",
|
|
37
|
+
"size-limit": [
|
|
38
|
+
{
|
|
39
|
+
"path": "dist/churnsignal-sdk-web.cjs.production.min.js",
|
|
40
|
+
"limit": "10 KB"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "dist/churnsignal-sdk-web.esm.js",
|
|
44
|
+
"limit": "10 KB"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@size-limit/preset-small-lib": "^11.0.1",
|
|
49
|
+
"husky": "^8.0.3",
|
|
50
|
+
"size-limit": "^11.0.1",
|
|
51
|
+
"tsdx": "^0.14.1",
|
|
52
|
+
"tslib": "^2.6.2",
|
|
53
|
+
"typescript": "^5.3.3"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
transformTrackRequestData,
|
|
3
|
+
// transformIdentifyRequestData,
|
|
4
|
+
} from './utils/request-transformations';
|
|
5
|
+
|
|
6
|
+
type ApiClientProps = {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
config: {
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
tenantId?: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type EventProperties = Record<string, any>;
|
|
16
|
+
|
|
17
|
+
type User = {
|
|
18
|
+
email: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const sdkConfig = {
|
|
22
|
+
defaultHeaders: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const API_PREFIX = 'sdk-prod';
|
|
28
|
+
|
|
29
|
+
const TRACK_ENDPOINT = `${API_PREFIX}/track`;
|
|
30
|
+
// const IDENTIFY_USER_ENDPOINT = `${API_PREFIX}/identify`;
|
|
31
|
+
|
|
32
|
+
class ApiClient {
|
|
33
|
+
private apiKey: string;
|
|
34
|
+
private baseUrl?: string;
|
|
35
|
+
private workspaceId: string = '';
|
|
36
|
+
private tenantId?: string;
|
|
37
|
+
private user?: User;
|
|
38
|
+
|
|
39
|
+
constructor({ apiKey, baseUrl, config }: ApiClientProps) {
|
|
40
|
+
this.apiKey = apiKey;
|
|
41
|
+
this.baseUrl = baseUrl;
|
|
42
|
+
this.workspaceId = config.workspaceId;
|
|
43
|
+
this.tenantId = config.tenantId;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Wrapper function for fetch
|
|
47
|
+
fetchWithConfig = (
|
|
48
|
+
endpoint: string,
|
|
49
|
+
options: RequestInit = { method: 'GET' }
|
|
50
|
+
) => {
|
|
51
|
+
// Construct the full URL
|
|
52
|
+
const url = `${this.getBaseUrl()}/${endpoint}`;
|
|
53
|
+
|
|
54
|
+
// Merge the default headers with any headers provided in the options
|
|
55
|
+
const headers = {
|
|
56
|
+
...sdkConfig.defaultHeaders,
|
|
57
|
+
'x-api-key': this.getApiKey(),
|
|
58
|
+
...(options.headers || {}),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Merge the rest of the options with the headers
|
|
62
|
+
const config = {
|
|
63
|
+
...options,
|
|
64
|
+
headers,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Execute the fetch call with the merged configuration
|
|
68
|
+
return fetch(url, config);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
getApiKey() {
|
|
72
|
+
return this.apiKey;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getBaseUrl() {
|
|
76
|
+
return this.baseUrl;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getWorkspaceId() {
|
|
80
|
+
return this.workspaceId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getUser() {
|
|
84
|
+
return this.user;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
identify(user: User) {
|
|
88
|
+
this.user = user;
|
|
89
|
+
// this.fetchWithConfig(IDENTIFY_USER_ENDPOINT, {
|
|
90
|
+
// method: 'POST',
|
|
91
|
+
// body: transformIdentifyRequestData({
|
|
92
|
+
// ...user,
|
|
93
|
+
// }),
|
|
94
|
+
// });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
track(eventName: string, eventProperties?: EventProperties) {
|
|
98
|
+
if (!this.user) {
|
|
99
|
+
throw new Error('No identified users to track');
|
|
100
|
+
}
|
|
101
|
+
this.fetchWithConfig(TRACK_ENDPOINT, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
body: transformTrackRequestData({
|
|
104
|
+
eventName,
|
|
105
|
+
eventProperties,
|
|
106
|
+
configProperties: {
|
|
107
|
+
workspaceId: this.getWorkspaceId(),
|
|
108
|
+
tenantId: this.tenantId,
|
|
109
|
+
},
|
|
110
|
+
userProperties: {
|
|
111
|
+
email: this.user.email,
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default ApiClient;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { destroy } from './destroy';
|
|
2
|
+
import { initialise } from './initialise';
|
|
3
|
+
|
|
4
|
+
const config = {
|
|
5
|
+
workspaceId: 'test-workspace-id',
|
|
6
|
+
tenantId: ''
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
describe('Initialise', () => {
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
destroy();
|
|
12
|
+
})
|
|
13
|
+
it('returns false if no API key is provided', () => {
|
|
14
|
+
const apiKey = '';
|
|
15
|
+
const url = 'http://example.url';
|
|
16
|
+
expect(initialise(apiKey, url, config)).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
it('returns false if invalid base URL is provided', () => {
|
|
19
|
+
const apiKey = 'example-api-key';
|
|
20
|
+
const url = 'http//example.url';
|
|
21
|
+
expect(initialise(apiKey, url, config)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it('returns true if valid params are provided', () => {
|
|
24
|
+
const apiKey = 'example-api-key';
|
|
25
|
+
const url = 'http://example.url';
|
|
26
|
+
expect(initialise(apiKey, url, config)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('returns false if workspace id is not provided', () => {
|
|
29
|
+
const apiKey = 'example-api-key';
|
|
30
|
+
const url = 'http://example.url';
|
|
31
|
+
expect(initialise(apiKey, url, {} as typeof config)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import sdkInstanceManager from './../managers/sdk-instance-manager';
|
|
2
|
+
|
|
3
|
+
export type InitConfig = {
|
|
4
|
+
workspaceId: string
|
|
5
|
+
tenantId: string; // TODO to be removed should be done on the API side
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param apiKey - required string value representing the API key
|
|
10
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
11
|
+
* @param options - optional object value
|
|
12
|
+
* @returns boolean indicating whether the initialisation of the sdk was successful or not
|
|
13
|
+
*/
|
|
14
|
+
export const initialise = (
|
|
15
|
+
apiKey: string,
|
|
16
|
+
baseUrl: string,
|
|
17
|
+
config: InitConfig
|
|
18
|
+
) => {
|
|
19
|
+
try {
|
|
20
|
+
sdkInstanceManager.initialise(apiKey, baseUrl, config);
|
|
21
|
+
return true;
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.info((e as any)?.message);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import sdkInstanceManager from '../managers/sdk-instance-manager';
|
|
2
|
+
|
|
3
|
+
type Properties = Record<string, any>;
|
|
4
|
+
|
|
5
|
+
export const track = (eventName: string, eventProperties?: Properties) => {
|
|
6
|
+
if (!sdkInstanceManager.getIsInitialised()) {
|
|
7
|
+
console.error('SDK must be initialised first');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (!eventName || typeof eventName !== 'string') {
|
|
11
|
+
console.error('Event name must be provided');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
sdkInstanceManager.getApiClient()?.track(eventName, eventProperties);
|
|
15
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './core/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sdk-instance-manager';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isValidUrl, sanitiseUrl } from '../utils/url-validator';
|
|
2
|
+
import ApiClient from './../api-client';
|
|
3
|
+
|
|
4
|
+
type Config = {
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
tenantId?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
class SDKInstanceManager {
|
|
10
|
+
private static instance: SDKInstanceManager;
|
|
11
|
+
private isInitialised: boolean = false;
|
|
12
|
+
private config: Config = { workspaceId: '' };
|
|
13
|
+
private apiClient?: ApiClient;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
if (!SDKInstanceManager.instance) {
|
|
17
|
+
this.config = { workspaceId: '' };
|
|
18
|
+
this.isInitialised = false;
|
|
19
|
+
SDKInstanceManager.instance = this;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return SDKInstanceManager.instance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @throws Error in case validation of parameters fails
|
|
27
|
+
* @param apiKey - required string value representing the API key
|
|
28
|
+
* @param baseUrl - required string value representing the API endpoint
|
|
29
|
+
* @param config - required object value
|
|
30
|
+
* @returns void
|
|
31
|
+
*/
|
|
32
|
+
initialise(apiKey: string, baseUrl: string, config: Config) {
|
|
33
|
+
if (this.getIsInitialised()) {
|
|
34
|
+
console.info('SDK is already initialised with API key');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
38
|
+
throw new Error('SDK needs a valid API key to be inialised');
|
|
39
|
+
}
|
|
40
|
+
if (!baseUrl || typeof baseUrl !== 'string' || !isValidUrl(baseUrl)) {
|
|
41
|
+
throw new Error('SDK needs a valid base URL to be initialised');
|
|
42
|
+
}
|
|
43
|
+
if (!config.workspaceId) {
|
|
44
|
+
throw new Error('Workspace ID must be provided');
|
|
45
|
+
}
|
|
46
|
+
this.config = config;
|
|
47
|
+
let sanitisedUrl = sanitiseUrl(baseUrl);
|
|
48
|
+
this.apiClient = new ApiClient({ apiKey, baseUrl: sanitisedUrl, config });
|
|
49
|
+
this.isInitialised = true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
destroy() {
|
|
53
|
+
this.config = { workspaceId: '' };
|
|
54
|
+
this.isInitialised = false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getIsInitialised() {
|
|
58
|
+
return this.isInitialised;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getApiClient() {
|
|
62
|
+
return this.apiClient;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getConfig() {
|
|
66
|
+
return this.config;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const instance = new SDKInstanceManager();
|
|
71
|
+
export { instance as default };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { EventProperties } from '../api-client';
|
|
2
|
+
|
|
3
|
+
type TrackRequest = {
|
|
4
|
+
eventName: string;
|
|
5
|
+
eventProperties?: EventProperties;
|
|
6
|
+
userProperties?: {
|
|
7
|
+
email: string;
|
|
8
|
+
};
|
|
9
|
+
configProperties: {
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
tenantId?: string
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const transformTrackRequestData = ({
|
|
16
|
+
eventName,
|
|
17
|
+
eventProperties,
|
|
18
|
+
userProperties,
|
|
19
|
+
configProperties,
|
|
20
|
+
}: TrackRequest) => {
|
|
21
|
+
const transformedEvent = {
|
|
22
|
+
name: eventName,
|
|
23
|
+
timestamp: new Date().getTime().toString(),
|
|
24
|
+
userEmail: userProperties?.email,
|
|
25
|
+
tenantId: configProperties.tenantId, // TODO this will be handled on the API side, for now pass it in SDK setup
|
|
26
|
+
workspaceId: configProperties.workspaceId,
|
|
27
|
+
type: 'event',
|
|
28
|
+
data: eventProperties,
|
|
29
|
+
};
|
|
30
|
+
return JSON.stringify(transformedEvent);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type IdentifyRequest = {
|
|
34
|
+
email: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const transformIdentifyRequestData = ({
|
|
38
|
+
email
|
|
39
|
+
}: IdentifyRequest) => {
|
|
40
|
+
const transformedIdentify = {
|
|
41
|
+
userEmail: email
|
|
42
|
+
}
|
|
43
|
+
return JSON.stringify(transformedIdentify);
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isValidUrl, sanitiseUrl } from './url-validator';
|
|
2
|
+
|
|
3
|
+
describe('URL validator', () => {
|
|
4
|
+
it('returns true if the URL starts with http://', () => {
|
|
5
|
+
const url = 'http://example.url';
|
|
6
|
+
expect(isValidUrl(url)).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it('returns true if the URL starts with https://', () => {
|
|
9
|
+
const url = 'https://example.url';
|
|
10
|
+
expect(isValidUrl(url)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
it('returns false if the URL doesnt start with http:// or https://', () => {
|
|
13
|
+
const url = 'https//example.url';
|
|
14
|
+
expect(isValidUrl(url)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
it('Removes trailing slash from the pathname', () => {
|
|
17
|
+
const url = 'https://example.url/example/';
|
|
18
|
+
const sanitisedUrl = sanitiseUrl(url);
|
|
19
|
+
expect(sanitisedUrl).toBe('https://example.url/example');
|
|
20
|
+
});
|
|
21
|
+
it('Removes trailing slash from the root', () => {
|
|
22
|
+
const url = 'https://example.url/';
|
|
23
|
+
const sanitisedUrl = sanitiseUrl(url);
|
|
24
|
+
expect(sanitisedUrl).toBe('https://example.url');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const urlRegex = /^https?:\/\//;
|
|
2
|
+
|
|
3
|
+
export const isValidUrl = (url: string) => urlRegex.test(url);
|
|
4
|
+
|
|
5
|
+
export const sanitiseUrl = (url: string) => {
|
|
6
|
+
// Trim whitespace
|
|
7
|
+
const sanitisedUrl = url.trim();
|
|
8
|
+
|
|
9
|
+
// Validate scheme
|
|
10
|
+
if (!isValidUrl(url)) {
|
|
11
|
+
throw new Error("URL must start with 'http://' or 'https://'");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
let urlObject = new URL(sanitisedUrl);
|
|
16
|
+
|
|
17
|
+
urlObject.hostname = urlObject.hostname.toLowerCase();
|
|
18
|
+
|
|
19
|
+
// Remove trailing slash
|
|
20
|
+
if (urlObject.pathname !== '/') {
|
|
21
|
+
urlObject.pathname = urlObject.pathname.replace(/\/+$/, '') || '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Reconstruct the URL from its components
|
|
25
|
+
// Note: The URL interface automatically handles encoding of the pathname
|
|
26
|
+
return urlObject.toString().replace(/\/+$/, '');
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new Error('Invalid URL provided');
|
|
29
|
+
}
|
|
30
|
+
};
|