@nu-art/analytics-backend 0.400.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ import { Module } from '@nu-art/ts-common';
2
+ import { AnalyticsPlugin_Base } from '../plugins/AnalyticsPlugin_Base.js';
3
+ import { AnalyticsPluginRegistry } from '../plugins/index.js';
4
+ type Config = {
5
+ plugins: {
6
+ [K in keyof AnalyticsPluginRegistry]: AnalyticsPluginRegistry[K] extends AnalyticsPlugin_Base<any, infer C> ? C : never;
7
+ };
8
+ };
9
+ declare class ModuleBE_Analytics_Class extends Module<Config> {
10
+ private readonly plugins;
11
+ protected init(): void;
12
+ addPlugin(plugin: AnalyticsPlugin_Base): void;
13
+ removePlugin(plugin: AnalyticsPlugin_Base): void;
14
+ private initPlugins;
15
+ private api_sendEvent;
16
+ private api_updateUser;
17
+ }
18
+ export declare const ModuleBE_Analytics: ModuleBE_Analytics_Class;
19
+ export {};
@@ -0,0 +1,40 @@
1
+ import { Module } from '@nu-art/ts-common';
2
+ import { addRoutes, createBodyServerApi } from '@nu-art/thunderstorm-backend';
3
+ import { ApiDef_Analytics } from '@nu-art/analytics-shared';
4
+ class ModuleBE_Analytics_Class extends Module {
5
+ plugins = new Map();
6
+ init() {
7
+ super.init();
8
+ this.initPlugins();
9
+ addRoutes([
10
+ createBodyServerApi(ApiDef_Analytics()._v1.sendEvent, this.api_sendEvent),
11
+ createBodyServerApi(ApiDef_Analytics()._v1.updateUser, this.api_updateUser),
12
+ ]);
13
+ }
14
+ //######################### Plugin Management #########################
15
+ addPlugin(plugin) {
16
+ this.plugins.set(plugin.key, plugin);
17
+ }
18
+ removePlugin(plugin) {
19
+ this.plugins.delete(plugin.key);
20
+ }
21
+ initPlugins() {
22
+ this.plugins.forEach(plugin => {
23
+ const key = plugin.key;
24
+ const pluginConfig = this.config.plugins[key];
25
+ plugin.init(pluginConfig);
26
+ });
27
+ }
28
+ //######################### API Callbacks #########################
29
+ api_sendEvent = async (request) => {
30
+ return this.plugins.forEach(plugin => {
31
+ plugin.registerEvent(request.event);
32
+ });
33
+ };
34
+ api_updateUser = async (request) => {
35
+ return this.plugins.forEach(plugin => {
36
+ plugin.updateUser?.(request);
37
+ });
38
+ };
39
+ }
40
+ export const ModuleBE_Analytics = new ModuleBE_Analytics_Class();
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './_modules/ModuleBE_Analytics.js';
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './_modules/ModuleBE_Analytics.js';
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@nu-art/analytics-backend",
3
+ "version": "0.400.7",
4
+ "description": "analytics Backend",
5
+ "license": "Apache-2.0",
6
+ "scripts": {
7
+ "build": "tsc"
8
+ },
9
+ "publishConfig": {
10
+ "directory": "dist",
11
+ "linkDirectory": true
12
+ },
13
+ "dependencies": {
14
+ "@nu-art/analytics-shared": "0.400.7",
15
+ "@nu-art/ts-common": "0.400.7",
16
+ "@nu-art/firebase-backend": "0.400.7",
17
+ "@nu-art/firebase-shared": "0.400.7",
18
+ "@nu-art/thunderstorm-backend": "0.400.7",
19
+ "@nu-art/thunderstorm-shared": "0.400.7",
20
+ "mixpanel": "~0.18.1"
21
+ },
22
+ "devDependencies": {},
23
+ "private": false,
24
+ "unitConfig": {
25
+ "type": "typescript-lib"
26
+ },
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./index.d.ts",
31
+ "import": "./index.js"
32
+ },
33
+ "./*": {
34
+ "types": "./*.d.ts",
35
+ "import": "./*.js"
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,41 @@
1
+ import { Analytics_UpdateUser, TSAnalyticsEvent } from '@nu-art/analytics-shared';
2
+ import { Logger } from '@nu-art/ts-common';
3
+ import { AnalyticsPluginBaseConfig } from './types.js';
4
+ /**
5
+ * C - plugin specific config, to be used later in interface implementations
6
+ * R - Translation response
7
+ */
8
+ export declare abstract class AnalyticsPlugin_Base<R extends any = any, C extends AnalyticsPluginBaseConfig = AnalyticsPluginBaseConfig> extends Logger {
9
+ /**
10
+ * Unique key used to identify this plugin and retrieve its config.
11
+ * Example: 'mixpanel', 'amplitude', 'customLogger'
12
+ */
13
+ abstract readonly key: string;
14
+ /**
15
+ * the config of the plugin, keeping important plugin specific data
16
+ * @private
17
+ */
18
+ protected config: C | undefined;
19
+ /**
20
+ * An event buffer, to packet the events into groups so they can later be bulk sent
21
+ * @private
22
+ */
23
+ private eventBuffer;
24
+ /**
25
+ * A debouncer for the "Empty event buffer" functionality.
26
+ * @private
27
+ */
28
+ private debounce_EmptyEventBuffer;
29
+ /**
30
+ * A queue for sending the events to the provider.
31
+ * @private
32
+ */
33
+ private queue_Events;
34
+ protected abstract translateEvent(event: TSAnalyticsEvent): R;
35
+ protected abstract sendEvents: (events: R[]) => Promise<void>;
36
+ protected abstract updateUser_Impl: undefined | ((mode: Analytics_UpdateUser['request']['mode'], data: Analytics_UpdateUser['request']['userData']) => Promise<void>);
37
+ init(config: C): void;
38
+ private emptyEventBuffer;
39
+ registerEvent(event: TSAnalyticsEvent): void;
40
+ updateUser(request: Analytics_UpdateUser['request']): void;
41
+ }
@@ -0,0 +1,61 @@
1
+ import { debounce, Logger, LogLevel } from '@nu-art/ts-common';
2
+ import { QueueV2 } from '@nu-art/ts-common/utils/queue-v2';
3
+ /**
4
+ * C - plugin specific config, to be used later in interface implementations
5
+ * R - Translation response
6
+ */
7
+ export class AnalyticsPlugin_Base extends Logger {
8
+ /**
9
+ * the config of the plugin, keeping important plugin specific data
10
+ * @private
11
+ */
12
+ config;
13
+ /**
14
+ * An event buffer, to packet the events into groups so they can later be bulk sent
15
+ * @private
16
+ */
17
+ eventBuffer = [];
18
+ /**
19
+ * A debouncer for the "Empty event buffer" functionality.
20
+ * @private
21
+ */
22
+ debounce_EmptyEventBuffer = debounce(() => this.emptyEventBuffer(), 200, 2000);
23
+ /**
24
+ * A queue for sending the events to the provider.
25
+ * @private
26
+ */
27
+ queue_Events = new QueueV2('plugin-events', e => this.sendEvents(e));
28
+ //######################### Initialization #########################
29
+ init(config) {
30
+ this.config = config;
31
+ const tag = `AnalyticsPlugin_${this.key}`;
32
+ this.setTag(tag);
33
+ this.setMinLevel(LogLevel.Info);
34
+ const message = `Loaded - ${this.config.active ? 'Active' : 'Inactive'}`;
35
+ this.logInfo(message);
36
+ }
37
+ //######################### Internal Logic #########################
38
+ emptyEventBuffer = () => {
39
+ if (this.eventBuffer.length === 0)
40
+ return;
41
+ this.queue_Events.addItem([...this.eventBuffer]);
42
+ this.eventBuffer = [];
43
+ };
44
+ //######################### Public Logic #########################
45
+ registerEvent(event) {
46
+ if (!this.config?.active)
47
+ return;
48
+ const translatedEvent = this.translateEvent(event);
49
+ this.eventBuffer.push(translatedEvent);
50
+ //If the max packet size has not been reached
51
+ if (this.eventBuffer.length < (this.config.eventPacketSize ?? 0))
52
+ return this.debounce_EmptyEventBuffer();
53
+ //Max packet size has been reached, empty the buffer now
54
+ this.emptyEventBuffer();
55
+ }
56
+ updateUser(request) {
57
+ if (!this.config?.active)
58
+ return;
59
+ this.updateUser_Impl?.(request.mode, request.userData);
60
+ }
61
+ }
@@ -0,0 +1,9 @@
1
+ import { TSAnalyticsEvent } from '@nu-art/analytics-shared';
2
+ import { AnalyticsPlugin_Base } from './AnalyticsPlugin_Base.js';
3
+ export declare const pluginKey_Logger = "logger";
4
+ export declare class AnalyticsPlugin_Logger extends AnalyticsPlugin_Base {
5
+ key: string;
6
+ protected translateEvent(event: TSAnalyticsEvent): TSAnalyticsEvent;
7
+ protected updateUser_Impl: undefined;
8
+ protected sendEvents: (events: any[]) => Promise<void>;
9
+ }
@@ -0,0 +1,14 @@
1
+ import { AnalyticsPlugin_Base } from './AnalyticsPlugin_Base.js';
2
+ export const pluginKey_Logger = 'logger';
3
+ export class AnalyticsPlugin_Logger extends AnalyticsPlugin_Base {
4
+ key = pluginKey_Logger;
5
+ translateEvent(event) {
6
+ return event;
7
+ }
8
+ updateUser_Impl = undefined;
9
+ sendEvents = async (events) => {
10
+ this.logInfoBold('######## Analytics Event - Start ########');
11
+ this.logInfo(events);
12
+ this.logInfoBold('######## Analytics Event - End ########');
13
+ };
14
+ }
@@ -0,0 +1,30 @@
1
+ import { Analytics_UpdateUser, TSAnalyticsEvent } from '@nu-art/analytics-shared';
2
+ import { AnalyticsPlugin_Base } from './AnalyticsPlugin_Base.js';
3
+ import mixpanelLib from 'mixpanel';
4
+ import { TypedMap } from '@nu-art/ts-common';
5
+ import { AnalyticPanelConfig } from './types.js';
6
+ type MixedPanelsEventProperties = {
7
+ distinct_id: string;
8
+ time: number;
9
+ $session_id?: string;
10
+ $groups?: TypedMap<string>;
11
+ [key: string]: any;
12
+ };
13
+ type MixedPanelsEvent = {
14
+ event: string;
15
+ properties: MixedPanelsEventProperties;
16
+ };
17
+ export declare const pluginKey_MixedPanels = "mixed-panels";
18
+ type MPConfig = AnalyticPanelConfig<{
19
+ mxConfig?: Partial<mixpanelLib.InitConfig>;
20
+ }>;
21
+ export declare class AnalyticsPlugin_MixedPanels extends AnalyticsPlugin_Base<MixedPanelsEvent, MPConfig> {
22
+ readonly key = "mixed-panels";
23
+ private mixpanel;
24
+ init(config: MPConfig): void;
25
+ private prepareUserProps;
26
+ protected translateEvent(event: TSAnalyticsEvent): MixedPanelsEvent;
27
+ protected sendEvents: (events: MixedPanelsEvent[]) => Promise<void>;
28
+ protected updateUser_Impl: (mode: Analytics_UpdateUser["request"]["mode"], data: Analytics_UpdateUser["request"]["userData"]) => Promise<void>;
29
+ }
30
+ export {};
@@ -0,0 +1,98 @@
1
+ import { AnalyticsPlugin_Base } from './AnalyticsPlugin_Base.js';
2
+ import mixpanelLib from 'mixpanel';
3
+ import { _keys, BadImplementationException, exists, MissingDataException } from '@nu-art/ts-common';
4
+ export const pluginKey_MixedPanels = 'mixed-panels';
5
+ export class AnalyticsPlugin_MixedPanels extends AnalyticsPlugin_Base {
6
+ key = pluginKey_MixedPanels;
7
+ mixpanel;
8
+ init(config) {
9
+ super.init(config);
10
+ if (!config.token) {
11
+ if (config.active)
12
+ throw new MissingDataException(`Missing token for analytics plugin "${pluginKey_MixedPanels}"`);
13
+ else
14
+ return;
15
+ }
16
+ this.mixpanel = mixpanelLib.init(config.token, config.mxConfig);
17
+ }
18
+ //######################### Internal Logic #########################
19
+ prepareUserProps = (data) => {
20
+ const { userId, ...rest } = data;
21
+ const props = {};
22
+ _keys(rest).forEach(key => {
23
+ switch (key) {
24
+ case 'firstName':
25
+ props.$first_name = rest[key];
26
+ break;
27
+ case 'lastName':
28
+ props.$last_name = rest[key];
29
+ break;
30
+ case 'displayName':
31
+ props.$name = rest[key];
32
+ break;
33
+ default:
34
+ props[key] = rest[key];
35
+ }
36
+ });
37
+ return props;
38
+ };
39
+ //######################### Implemented Logic #########################
40
+ translateEvent(event) {
41
+ return {
42
+ event: event.key,
43
+ properties: {
44
+ distinct_id: event.userId ?? 'unknown',
45
+ time: Math.floor(event.timestamp / 1000), //Mixed panels expects seconds
46
+ ...(exists(event.context) ? event.context : {}),
47
+ ...(exists(event.properties) ? event.properties : {}),
48
+ ...(exists(event.groups) ? event.groups : {}),
49
+ ...(exists(event.sessionId) ? { $session_id: event.sessionId } : {}),
50
+ }
51
+ };
52
+ }
53
+ sendEvents = async (events) => {
54
+ if (!this.mixpanel)
55
+ throw new BadImplementationException(`Calling send before analytics plugin ${pluginKey_MixedPanels} finished initializing`);
56
+ return new Promise((resolve, reject) => {
57
+ this.logDebug('Sending Events', events);
58
+ this.mixpanel.track_batch(events, {}, (errors) => {
59
+ if (errors?.length) {
60
+ this.logError(errors);
61
+ reject(errors);
62
+ }
63
+ else {
64
+ this.logDebug('Events Sent');
65
+ resolve();
66
+ }
67
+ });
68
+ });
69
+ };
70
+ updateUser_Impl = (mode, data) => {
71
+ if (!this.mixpanel)
72
+ throw new BadImplementationException(`Calling update user before analytics plugin ${pluginKey_MixedPanels} finished initializing`);
73
+ return new Promise((resolve, reject) => {
74
+ this.logDebug('Updating user', data);
75
+ const userProps = this.prepareUserProps(data);
76
+ const cb = (err) => {
77
+ if (err) {
78
+ this.logError(err);
79
+ reject(err);
80
+ }
81
+ else {
82
+ this.logDebug('Successfully updated user');
83
+ resolve();
84
+ }
85
+ };
86
+ switch (mode) {
87
+ case 'set':
88
+ this.mixpanel?.people.set(data.userId, userProps, cb);
89
+ break;
90
+ case 'set_once':
91
+ this.mixpanel?.people.set_once(data.userId, userProps, cb);
92
+ break;
93
+ default:
94
+ throw new BadImplementationException(`No Implementation for mode ${mode}`);
95
+ }
96
+ });
97
+ };
98
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './registry.js';
3
+ export * from './AnalyticsPlugin_Logger.js';
4
+ export * from './AnalyticsPlugin_MixedPanels.js';
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './registry.js';
3
+ export * from './AnalyticsPlugin_Logger.js';
4
+ export * from './AnalyticsPlugin_MixedPanels.js';
@@ -0,0 +1,6 @@
1
+ import { AnalyticsPlugin_Logger, pluginKey_Logger } from './AnalyticsPlugin_Logger.js';
2
+ import { AnalyticsPlugin_MixedPanels, pluginKey_MixedPanels } from './AnalyticsPlugin_MixedPanels.js';
3
+ export type AnalyticsPluginRegistry = {
4
+ [pluginKey_Logger]: AnalyticsPlugin_Logger;
5
+ [pluginKey_MixedPanels]: AnalyticsPlugin_MixedPanels;
6
+ };
@@ -0,0 +1,2 @@
1
+ import { pluginKey_Logger } from './AnalyticsPlugin_Logger.js';
2
+ import { pluginKey_MixedPanels } from './AnalyticsPlugin_MixedPanels.js';
@@ -0,0 +1,6 @@
1
+ export type AnalyticsPluginBaseConfig = {
2
+ active: boolean;
3
+ token?: string;
4
+ eventPacketSize?: number;
5
+ };
6
+ export type AnalyticPanelConfig<C> = AnalyticsPluginBaseConfig & C;
@@ -0,0 +1 @@
1
+ export {};