@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 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":";;;;AAAA,IAAMA,QAAQ,GAAG,cAAc;AAExB,IAAMC,UAAU,GAAG,SAAbA,UAAUA,CAAIC,GAAW;EAAA,OAAKF,QAAQ,CAACG,IAAI,CAACD,GAAG,CAAC;AAAA;AAEtD,IAAME,WAAW,GAAG,SAAdA,WAAWA,CAAIF,GAAW;;EAErC,IAAMG,YAAY,GAAGH,GAAG,CAACI,IAAI,EAAE;;EAG/B,IAAI,CAACL,UAAU,CAACC,GAAG,CAAC,EAAE;IACpB,MAAM,IAAIK,KAAK,CAAC,6CAA6C,CAAC;;EAGhE,IAAI;IACF,IAAIC,SAAS,GAAG,IAAIC,GAAG,CAACJ,YAAY,CAAC;IAErCG,SAAS,CAACE,QAAQ,GAAGF,SAAS,CAACE,QAAQ,CAACC,WAAW,EAAE;;IAGrD,IAAIH,SAAS,CAACI,QAAQ,KAAK,GAAG,EAAE;MAC9BJ,SAAS,CAACI,QAAQ,GAAGJ,SAAS,CAACI,QAAQ,CAACC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE;;;;IAKnE,OAAOL,SAAS,CAACM,QAAQ,EAAE,CAACD,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;GAChD,CAAC,OAAOE,CAAC,EAAE;IACV,MAAM,IAAIR,KAAK,CAAC,sBAAsB,CAAC;;AAE3C,CAAC;;;;;;;;;;;;;;;;;ACfM,IAAMS,yBAAyB,GAAG,SAA5BA,yBAAyBA,CAAAC,IAAA;MACpCC,SAAS,GAAAD,IAAA,CAATC,SAAS;IACTC,eAAe,GAAAF,IAAA,CAAfE,eAAe;IACfC,cAAc,GAAAH,IAAA,CAAdG,cAAc;IACdC,gBAAgB,GAAAJ,IAAA,CAAhBI,gBAAgB;EAEhB,IAAMC,gBAAgB,GAAG;IACvBC,IAAI,EAAEL,SAAS;IACfM,SAAS,EAAE,IAAIC,IAAI,EAAE,CAACC,OAAO,EAAE,CAACZ,QAAQ,EAAE;IAC1Ca,SAAS,EAAEP,cAAc,oBAAdA,cAAc,CAAEQ,KAAK;IAChCC,QAAQ,EAAER,gBAAgB,CAACQ,QAAQ;IACnCC,WAAW,EAAET,gBAAgB,CAACS,WAAW;IACzCC,IAAI,EAAE,OAAO;IACbC,IAAI,EAAEb;GACP;EACD,OAAOc,IAAI,CAACC,SAAS,CAACZ,gBAAgB,CAAC;AACzC,CAAC;;ACVD,IAAMa,SAAS,GAAG;EAChBC,cAAc,EAAE;IACd,cAAc,EAAE;;CAEnB;AAED,IAAMC,UAAU,GAAG,UAAU;AAE7B,IAAMC,cAAc,GAAMD,UAAU,WAAQ;AAC5C;AAAA,IAEME,SAAS;EAOb,SAAAA,UAAAtB,IAAA;;QAAcuB,MAAM,GAAAvB,IAAA,CAANuB,MAAM;MAAEC,OAAO,GAAAxB,IAAA,CAAPwB,OAAO;MAAEC,MAAM,GAAAzB,IAAA,CAANyB,MAAM;IAJ7B,gBAAW,GAAW,EAAE;;IAYhC,oBAAe,GAAG,UAChBC,QAAgB,EAChBC;UAAAA;QAAAA,UAAuB;UAAEC,MAAM,EAAE;SAAO;;;MAGxC,IAAM3C,GAAG,GAAM4C,KAAI,CAACC,UAAU,EAAE,SAAIJ,QAAU;;MAG9C,IAAMK,OAAO,GAAAC,QAAA,KACRd,SAAS,CAACC,cAAc;QAC3B,WAAW,EAAEU,KAAI,CAACI,SAAS;SACvBN,OAAO,CAACI,OAAO,IAAI,EAAE,CAC1B;;MAGD,IAAMN,MAAM,GAAAO,QAAA,KACPL,OAAO;QACVI,OAAO,EAAPA;QACD;;MAGD,OAAOG,KAAK,CAACjD,GAAG,EAAEwC,MAAM,CAAC;KAC1B;IA7BC,IAAI,CAACF,MAAM,GAAGA,MAAM;IACpB,IAAI,CAACC,OAAO,GAAGA,OAAO;IACtB,IAAI,CAACX,WAAW,GAAGY,MAAM,CAACZ,WAAW;IACrC,IAAI,CAACD,QAAQ,GAAGa,MAAM,CAACb,QAAQ;;EAChC,IAAAuB,MAAA,GAAAb,SAAA,CAAAc,SAAA;EAAAD,MAAA,CA2BDF,SAAS,GAAT,SAAAA;IACE,OAAO,IAAI,CAACV,MAAM;GACnB;EAAAY,MAAA,CAEDL,UAAU,GAAV,SAAAA;IACE,OAAO,IAAI,CAACN,OAAO;GACpB;EAAAW,MAAA,CAEDE,cAAc,GAAd,SAAAA;IACE,OAAO,IAAI,CAACxB,WAAW;GACxB;EAAAsB,MAAA,CAEDG,OAAO,GAAP,SAAAA;IACE,OAAO,IAAI,CAACC,IAAI;GACjB;EAAAJ,MAAA,CAEDK,QAAQ,GAAR,SAAAA,SAASD,IAAU;IACjB,IAAI,CAACA,IAAI,GAAGA,IAAI;;;;;;;GAOjB;EAAAJ,MAAA,CAEDM,KAAK,GAAL,SAAAA,MAAMxC,SAAiB,EAAEC,eAAiC;IACxD,IAAI,CAAC,IAAI,CAACqC,IAAI,EAAE;MACd,MAAM,IAAIjD,KAAK,CAAC,8BAA8B,CAAC;;IAEjD,IAAI,CAACoD,eAAe,CAACrB,cAAc,EAAE;MACnCO,MAAM,EAAE,MAAM;MACde,IAAI,EAAE5C,yBAAyB,CAAC;QAC9BE,SAAS,EAATA,SAAS;QACTC,eAAe,EAAfA,eAAe;QACfE,gBAAgB,EAAE;UAChBS,WAAW,EAAE,IAAI,CAACwB,cAAc,EAAE;UAClCzB,QAAQ,EAAE,IAAI,CAACA;SAChB;QACDT,cAAc,EAAE;UACdQ,KAAK,EAAE,IAAI,CAAC4B,IAAI,CAAC5B;;OAEpB;KACF,CAAC;GACH;EAAA,OAAAW,SAAA;AAAA;;ACjHqC,IAOlCsB,kBAAkB;EAMtB,SAAAA;IAJQ,kBAAa,GAAY,KAAK;IAC9B,WAAM,GAAW;MAAE/B,WAAW,EAAE;KAAI;IAI1C,IAAI,CAAC+B,kBAAkB,CAACC,QAAQ,EAAE;MAChC,IAAI,CAACpB,MAAM,GAAG;QAAEZ,WAAW,EAAE;OAAI;MACjC,IAAI,CAACiC,aAAa,GAAG,KAAK;MAC1BF,kBAAkB,CAACC,QAAQ,GAAG,IAAI;;IAGpC,OAAOD,kBAAkB,CAACC,QAAQ;;;;;;;;;EAGpC,IAAAV,MAAA,GAAAS,kBAAA,CAAAR,SAAA;EAAAD,MAAA,CAOAY,UAAU,GAAV,SAAAA,WAAWxB,MAAc,EAAEC,OAAe,EAAEC,MAAc;IACxD,IAAI,IAAI,CAACuB,gBAAgB,EAAE,EAAE;MAC3BC,OAAO,CAACC,IAAI,CAAC,yCAAyC,CAAC;MACvD;;IAEF,IAAI,CAAC3B,MAAM,IAAI,OAAOA,MAAM,KAAK,QAAQ,EAAE;MACzC,MAAM,IAAIjC,KAAK,CAAC,2CAA2C,CAAC;;IAE9D,IAAI,CAACkC,OAAO,IAAI,OAAOA,OAAO,KAAK,QAAQ,IAAI,CAACxC,UAAU,CAACwC,OAAO,CAAC,EAAE;MACnE,MAAM,IAAIlC,KAAK,CAAC,8CAA8C,CAAC;;IAEjE,IAAI,CAACmC,MAAM,CAACZ,WAAW,EAAE;MACvB,MAAM,IAAIvB,KAAK,CAAC,+BAA+B,CAAC;;IAElD,IAAI,CAACmC,MAAM,GAAGA,MAAM;IACpB,IAAIrC,YAAY,GAAGD,WAAW,CAACqC,OAAO,CAAC;IACvC,IAAI,CAAC2B,SAAS,GAAG,IAAI7B,SAAS,CAAC;MAAEC,MAAM,EAANA,MAAM;MAAEC,OAAO,EAAEpC,YAAY;MAAEqC,MAAM,EAANA;KAAQ,CAAC;IACzE,IAAI,CAACqB,aAAa,GAAG,IAAI;GAC1B;EAAAX,MAAA,CAEDiB,OAAO,GAAP,SAAAA;IACE,IAAI,CAAC3B,MAAM,GAAG;MAAEZ,WAAW,EAAE;KAAI;IACjC,IAAI,CAACiC,aAAa,GAAG,KAAK;GAC3B;EAAAX,MAAA,CAEDa,gBAAgB,GAAhB,SAAAA;IACE,OAAO,IAAI,CAACF,aAAa;GAC1B;EAAAX,MAAA,CAEDkB,YAAY,GAAZ,SAAAA;IACE,OAAO,IAAI,CAACF,SAAS;GACtB;EAAAhB,MAAA,CAEDmB,SAAS,GAAT,SAAAA;IACE,OAAO,IAAI,CAAC7B,MAAM;GACnB;EAAA,OAAAmB,kBAAA;AAAA;AAGH,IAAMC,QAAQ,gBAAG,IAAID,kBAAkB,EAAE;;AC9DzC;;;;;;AAMA,IAAaG,UAAU,GAAG,SAAbA,UAAUA,CACrBxB,MAAc,EACdC,OAAe,EACfC,MAAkB;EAElB,IAAI;IACF8B,QAAkB,CAACR,UAAU,CAACxB,MAAM,EAAEC,OAAO,EAAEC,MAAM,CAAC;IACtD,OAAO,IAAI;GACZ,CAAC,OAAO3B,CAAC,EAAE;IACVmD,OAAO,CAACC,IAAI,CAAEpD,CAAS,oBAATA,CAAS,CAAE0D,OAAO,CAAC;IACjC,OAAO,KAAK;;AAEhB,CAAC;;ICvBYhB,QAAQ,GAAG,SAAXA,QAAQA,CAAID,IAAuB;;EAC9C,CAAAkB,qBAAA,GAAAF,QAAkB,CAACF,YAAY,EAAE,aAAjCI,qBAAA,CAAmCjB,QAAQ,CAACD,IAAI,CAAC;AACnD,CAAC;;ICAYE,KAAK,GAAG,SAARA,KAAKA,CAAIxC,SAAiB,EAAEC,eAA4B;;EACnE,IAAI,CAACqD,QAAkB,CAACP,gBAAgB,EAAE,EAAE;IAC1CC,OAAO,CAACS,KAAK,CAAC,+BAA+B,CAAC;IAC9C;;EAEF,IAAI,CAACzD,SAAS,IAAI,OAAOA,SAAS,KAAK,QAAQ,EAAE;IAC/CgD,OAAO,CAACS,KAAK,CAAC,6BAA6B,CAAC;IAC5C;;EAEF,CAAAD,qBAAA,GAAAF,QAAkB,CAACF,YAAY,EAAE,aAAjCI,qBAAA,CAAmChB,KAAK,CAACxC,SAAS,EAAEC,eAAe,CAAC;AACtE,CAAC;;;;;;"}
@@ -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,3 @@
1
+ export declare const identify: (user: {
2
+ email: string;
3
+ }) => void;
@@ -0,0 +1,3 @@
1
+ export * from './initialise';
2
+ export * from './identify';
3
+ export * from './track';
@@ -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;
@@ -0,0 +1 @@
1
+ export * from './core/index';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+
2
+ 'use strict'
3
+
4
+ if (process.env.NODE_ENV === 'production') {
5
+ module.exports = require('./churnsignal-sdk-web.cjs.production.min.js')
6
+ } else {
7
+ module.exports = require('./churnsignal-sdk-web.cjs.development.js')
8
+ }
@@ -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 {};
@@ -0,0 +1,2 @@
1
+ export declare const isValidUrl: (url: string) => boolean;
2
+ export declare const sanitiseUrl: (url: string) => string;
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,5 @@
1
+ import sdkInstanceManager from './../managers/sdk-instance-manager';
2
+
3
+ export const destroy = () => {
4
+ sdkInstanceManager.destroy();
5
+ };
@@ -0,0 +1,5 @@
1
+ import sdkInstanceManager from '../managers/sdk-instance-manager';
2
+
3
+ export const identify = (user: { email: string }) => {
4
+ sdkInstanceManager.getApiClient()?.identify(user);
5
+ };
@@ -0,0 +1,3 @@
1
+ export * from './initialise';
2
+ export * from './identify';
3
+ export * from './track';
@@ -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
+ };