@nommos/core 0.0.3
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 +527 -0
- package/package.json +16 -0
- package/src/index.d.ts +7 -0
- package/src/index.js +11 -0
- package/src/index.js.map +1 -0
- package/src/lib/constants.d.ts +11 -0
- package/src/lib/constants.js +55 -0
- package/src/lib/constants.js.map +1 -0
- package/src/lib/data-attribution.d.ts +65 -0
- package/src/lib/data-attribution.js +213 -0
- package/src/lib/data-attribution.js.map +1 -0
- package/src/lib/enums.d.ts +30 -0
- package/src/lib/enums.js +37 -0
- package/src/lib/enums.js.map +1 -0
- package/src/lib/event-tracker.d.ts +55 -0
- package/src/lib/event-tracker.js +174 -0
- package/src/lib/event-tracker.js.map +1 -0
- package/src/lib/types.d.ts +40 -0
- package/src/lib/types.js +3 -0
- package/src/lib/types.js.map +1 -0
- package/src/lib/utils.d.ts +24 -0
- package/src/lib/utils.js +52 -0
- package/src/lib/utils.js.map +1 -0
- package/src/lib/webSocket-client.d.ts +69 -0
- package/src/lib/webSocket-client.js +132 -0
- package/src/lib/webSocket-client.js.map +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets all the query parameters found on a given url
|
|
3
|
+
*
|
|
4
|
+
* @param url - the url from which we want to get the query parameters from
|
|
5
|
+
* @returns A record of query parameters' name and value
|
|
6
|
+
*/
|
|
7
|
+
export declare function getQueryParams(url: string): Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Removes trailing spaces in a string and convert to lowerCase.
|
|
10
|
+
*
|
|
11
|
+
* @param s - string to convert
|
|
12
|
+
* @returns normalized string in lowerCase and no trailing space.
|
|
13
|
+
*/
|
|
14
|
+
export declare const normString: (s: string) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Extract the browser, the operating system and the channel (3 or 7) from the user agent of a device
|
|
17
|
+
* @param ua - User Agent of the device
|
|
18
|
+
* @returns An Object with the browser, operating system and the channel used by the user agent.
|
|
19
|
+
*/
|
|
20
|
+
export declare const userAgentInfo: (ua: string) => {
|
|
21
|
+
browser: string;
|
|
22
|
+
os: string;
|
|
23
|
+
channel: 3 | 7;
|
|
24
|
+
};
|
package/src/lib/utils.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.userAgentInfo = exports.normString = void 0;
|
|
4
|
+
exports.getQueryParams = getQueryParams;
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
/**
|
|
7
|
+
* Gets all the query parameters found on a given url
|
|
8
|
+
*
|
|
9
|
+
* @param url - the url from which we want to get the query parameters from
|
|
10
|
+
* @returns A record of query parameters' name and value
|
|
11
|
+
*/
|
|
12
|
+
function getQueryParams(url) {
|
|
13
|
+
const params = {};
|
|
14
|
+
const urlObj = new URL(url);
|
|
15
|
+
urlObj.searchParams.forEach((value, key) => {
|
|
16
|
+
// Handle repeated keys as arrays
|
|
17
|
+
if (params[key] === undefined && value.length > 0) {
|
|
18
|
+
params[key] = value;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return params;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Removes trailing spaces in a string and convert to lowerCase.
|
|
25
|
+
*
|
|
26
|
+
* @param s - string to convert
|
|
27
|
+
* @returns normalized string in lowerCase and no trailing space.
|
|
28
|
+
*/
|
|
29
|
+
const normString = (s) => s.trim().toLowerCase();
|
|
30
|
+
exports.normString = normString;
|
|
31
|
+
/**
|
|
32
|
+
* Extract the browser, the operating system and the channel (3 or 7) from the user agent of a device
|
|
33
|
+
* @param ua - User Agent of the device
|
|
34
|
+
* @returns An Object with the browser, operating system and the channel used by the user agent.
|
|
35
|
+
*/
|
|
36
|
+
const userAgentInfo = (ua) => ({
|
|
37
|
+
browser: detect(ua, constants_1.BROWSER_DEFS),
|
|
38
|
+
os: detect(ua, constants_1.OS_DEFS),
|
|
39
|
+
channel: /Mobi|Android|iPhone|iPad|iPod|Windows Phone/i.test(ua) ? 3 : 7,
|
|
40
|
+
});
|
|
41
|
+
exports.userAgentInfo = userAgentInfo;
|
|
42
|
+
const detect = (ua, defs, fallback = 'Unknown') => {
|
|
43
|
+
for (const [re, name, map] of defs) {
|
|
44
|
+
const m = new RegExp(re).exec(ua);
|
|
45
|
+
if (m) {
|
|
46
|
+
const v = map ? map(m) : m[1];
|
|
47
|
+
return v ? `${name} ${v}` : name;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return fallback;
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../../nommos/core/src/lib/utils.ts"],"names":[],"mappings":";;;AAUA,wCAYC;AAtBD,2CAAoD;AAIpD;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,GAAW;IACxC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAE5B,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACzC,iCAAiC;QACjC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACI,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAAnD,QAAA,UAAU,cAAyC;AAEhE;;;;GAIG;AACI,MAAM,aAAa,GAAG,CAC3B,EAAU,EACuC,EAAE,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,wBAAY,CAAC;IACjC,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,mBAAO,CAAC;IACvB,OAAO,EAAE,8CAA8C,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACzE,CAAC,CAAC;AANU,QAAA,aAAa,iBAMvB;AACH,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,IAAa,EAAE,QAAQ,GAAG,SAAS,EAAU,EAAE;IACzE,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,EAAE,CAAC;YACN,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9B,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AnalyticsEvent, FullConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* WebSocketClient
|
|
4
|
+
* ----------------
|
|
5
|
+
* A lightweight STOMP-over-WebSocket wrapper designed for publishing
|
|
6
|
+
* analytics events to a RabbitMQ broker. Includes:
|
|
7
|
+
* - connection lifecycle management
|
|
8
|
+
* - auto-reconnect support
|
|
9
|
+
* - offline event buffering
|
|
10
|
+
* - reactive connection status via RxJS
|
|
11
|
+
*
|
|
12
|
+
* Intended to be used inside applications where analytics events must
|
|
13
|
+
* never be lost due to temporary connection issues.
|
|
14
|
+
*/
|
|
15
|
+
export declare class WebSocketClient {
|
|
16
|
+
/**
|
|
17
|
+
* The underlying STOMP client instance.
|
|
18
|
+
* Initialized in `connectAnalyticsStomp()`.
|
|
19
|
+
*/
|
|
20
|
+
private analyticsClient;
|
|
21
|
+
/**
|
|
22
|
+
* Buffer containing analytics events produced before the socket
|
|
23
|
+
* becomes connected. They are automatically flushed on connection.
|
|
24
|
+
*/
|
|
25
|
+
pendingEvents: AnalyticsEvent[];
|
|
26
|
+
private readonly stompAnalyticsConnected;
|
|
27
|
+
/**
|
|
28
|
+
* Reactive connection state for consumers who want to listen for
|
|
29
|
+
* connection availability changes.
|
|
30
|
+
*
|
|
31
|
+
* `true` -> connected
|
|
32
|
+
* `false` -> disconnected
|
|
33
|
+
*/
|
|
34
|
+
stompAnalyticsConnected$: import("rxjs").Observable<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Creates and configures a STOMP client instance.
|
|
37
|
+
*
|
|
38
|
+
* @param cfg Connection configuration (broker URL, credentials, reconnect delay, mode).
|
|
39
|
+
* @returns A configured but not yet activated STOMP `Client` instance.
|
|
40
|
+
*/
|
|
41
|
+
private createStompClient;
|
|
42
|
+
/**
|
|
43
|
+
* Establishes a STOMP WebSocket connection and wires lifecycle handlers.
|
|
44
|
+
*
|
|
45
|
+
* - On successful connection:
|
|
46
|
+
* • Emits `true` for reactive subscribers
|
|
47
|
+
* • Flushes all queued analytics events
|
|
48
|
+
* - On broker error:
|
|
49
|
+
* • Logs structured error output
|
|
50
|
+
*
|
|
51
|
+
* @param cfg Configuration including URL, credentials, mode etc.
|
|
52
|
+
*/
|
|
53
|
+
connectAnalyticsStomp(cfg: FullConfig): void;
|
|
54
|
+
/**
|
|
55
|
+
* Publishes an analytics event to a RabbitMQ queue.
|
|
56
|
+
*
|
|
57
|
+
* If the connection is not ready, the event is automatically queued
|
|
58
|
+
* and delivered once the socket reconnects.
|
|
59
|
+
*
|
|
60
|
+
* @param payload The analytics event to send.
|
|
61
|
+
*/
|
|
62
|
+
publishAnalyticsMessage(payload: AnalyticsEvent): void;
|
|
63
|
+
/**
|
|
64
|
+
* Gracefully shuts down the STOMP client and updates connection status.
|
|
65
|
+
*
|
|
66
|
+
* Safe to call even if the client was never created or already disconnected.
|
|
67
|
+
*/
|
|
68
|
+
disconnect(): void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebSocketClient = void 0;
|
|
4
|
+
const stompjs_1 = require("@stomp/stompjs");
|
|
5
|
+
const rxjs_1 = require("rxjs");
|
|
6
|
+
const constants_1 = require("./constants");
|
|
7
|
+
/**
|
|
8
|
+
* WebSocketClient
|
|
9
|
+
* ----------------
|
|
10
|
+
* A lightweight STOMP-over-WebSocket wrapper designed for publishing
|
|
11
|
+
* analytics events to a RabbitMQ broker. Includes:
|
|
12
|
+
* - connection lifecycle management
|
|
13
|
+
* - auto-reconnect support
|
|
14
|
+
* - offline event buffering
|
|
15
|
+
* - reactive connection status via RxJS
|
|
16
|
+
*
|
|
17
|
+
* Intended to be used inside applications where analytics events must
|
|
18
|
+
* never be lost due to temporary connection issues.
|
|
19
|
+
*/
|
|
20
|
+
class WebSocketClient {
|
|
21
|
+
constructor() {
|
|
22
|
+
/**
|
|
23
|
+
* The underlying STOMP client instance.
|
|
24
|
+
* Initialized in `connectAnalyticsStomp()`.
|
|
25
|
+
*/
|
|
26
|
+
this.analyticsClient = null;
|
|
27
|
+
/**
|
|
28
|
+
* Buffer containing analytics events produced before the socket
|
|
29
|
+
* becomes connected. They are automatically flushed on connection.
|
|
30
|
+
*/
|
|
31
|
+
this.pendingEvents = [];
|
|
32
|
+
this.stompAnalyticsConnected = new rxjs_1.BehaviorSubject(false);
|
|
33
|
+
/**
|
|
34
|
+
* Reactive connection state for consumers who want to listen for
|
|
35
|
+
* connection availability changes.
|
|
36
|
+
*
|
|
37
|
+
* `true` -> connected
|
|
38
|
+
* `false` -> disconnected
|
|
39
|
+
*/
|
|
40
|
+
this.stompAnalyticsConnected$ = this.stompAnalyticsConnected.asObservable();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates and configures a STOMP client instance.
|
|
44
|
+
*
|
|
45
|
+
* @param cfg Connection configuration (broker URL, credentials, reconnect delay, mode).
|
|
46
|
+
* @returns A configured but not yet activated STOMP `Client` instance.
|
|
47
|
+
*/
|
|
48
|
+
createStompClient(cfg) {
|
|
49
|
+
var _a, _b, _c, _d;
|
|
50
|
+
console.log('[Nommos Tracker] Rabbit Configs... ', cfg);
|
|
51
|
+
const client = new stompjs_1.Client({
|
|
52
|
+
brokerURL: (_a = cfg.rabbitUrl) !== null && _a !== void 0 ? _a : '',
|
|
53
|
+
connectHeaders: {
|
|
54
|
+
login: (_b = cfg.username) !== null && _b !== void 0 ? _b : '',
|
|
55
|
+
passcode: (_c = cfg.password) !== null && _c !== void 0 ? _c : '',
|
|
56
|
+
},
|
|
57
|
+
reconnectDelay: (_d = cfg.reconnectDelay) !== null && _d !== void 0 ? _d : 5000,
|
|
58
|
+
});
|
|
59
|
+
if (cfg.mode === 'dev') {
|
|
60
|
+
client.debug = (msg) => console.log('[Nommos Tracker]', msg);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
client.debug = () => {
|
|
64
|
+
/* empty */
|
|
65
|
+
}; // silence logs in non-dev mode
|
|
66
|
+
}
|
|
67
|
+
return client;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Establishes a STOMP WebSocket connection and wires lifecycle handlers.
|
|
71
|
+
*
|
|
72
|
+
* - On successful connection:
|
|
73
|
+
* • Emits `true` for reactive subscribers
|
|
74
|
+
* • Flushes all queued analytics events
|
|
75
|
+
* - On broker error:
|
|
76
|
+
* • Logs structured error output
|
|
77
|
+
*
|
|
78
|
+
* @param cfg Configuration including URL, credentials, mode etc.
|
|
79
|
+
*/
|
|
80
|
+
connectAnalyticsStomp(cfg) {
|
|
81
|
+
this.analyticsClient = this.createStompClient(cfg);
|
|
82
|
+
this.analyticsClient.onConnect = () => {
|
|
83
|
+
console.log('[Nommos Tracker] Connected');
|
|
84
|
+
this.stompAnalyticsConnected.next(true);
|
|
85
|
+
// flush pending events
|
|
86
|
+
for (const m of this.pendingEvents) {
|
|
87
|
+
this.publishAnalyticsMessage(m);
|
|
88
|
+
}
|
|
89
|
+
this.pendingEvents = [];
|
|
90
|
+
};
|
|
91
|
+
this.analyticsClient.onStompError = (frame) => {
|
|
92
|
+
console.error('[Nommos Tracker] Broker error:', frame.headers['message']);
|
|
93
|
+
};
|
|
94
|
+
console.log('[Nommos Tracker] Activating STOMP client...');
|
|
95
|
+
this.analyticsClient.activate();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Publishes an analytics event to a RabbitMQ queue.
|
|
99
|
+
*
|
|
100
|
+
* If the connection is not ready, the event is automatically queued
|
|
101
|
+
* and delivered once the socket reconnects.
|
|
102
|
+
*
|
|
103
|
+
* @param payload The analytics event to send.
|
|
104
|
+
*/
|
|
105
|
+
publishAnalyticsMessage(payload) {
|
|
106
|
+
var _a;
|
|
107
|
+
if (!((_a = this.analyticsClient) === null || _a === void 0 ? void 0 : _a.connected) || this.analyticsClient == null) {
|
|
108
|
+
this.pendingEvents.push(payload);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.analyticsClient.publish({
|
|
112
|
+
destination: constants_1.ANALYTICS_QUEUE,
|
|
113
|
+
headers: { 'content-type': 'application/json' },
|
|
114
|
+
body: JSON.stringify(payload),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Gracefully shuts down the STOMP client and updates connection status.
|
|
119
|
+
*
|
|
120
|
+
* Safe to call even if the client was never created or already disconnected.
|
|
121
|
+
*/
|
|
122
|
+
disconnect() {
|
|
123
|
+
if (this.analyticsClient) {
|
|
124
|
+
this.analyticsClient.deactivate().then(() => {
|
|
125
|
+
this.stompAnalyticsConnected.next(false);
|
|
126
|
+
console.log('[Nommos Tracker] Disconnected');
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.WebSocketClient = WebSocketClient;
|
|
132
|
+
//# sourceMappingURL=webSocket-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webSocket-client.js","sourceRoot":"","sources":["../../../../../nommos/core/src/lib/webSocket-client.ts"],"names":[],"mappings":"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"}
|