@financial-times/custom-code-component 2.0.4 → 2.0.5
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
CHANGED
|
@@ -58,4 +58,37 @@ export default (shadowRoot, props, ...children) => {
|
|
|
58
58
|
* `data-asset-type="custom-code-component"`
|
|
59
59
|
* Part of spec.
|
|
60
60
|
* <any other attributes>
|
|
61
|
-
* All remaining attributes get passed as an object named `props` to render().
|
|
61
|
+
* All remaining attributes get passed as an object named `props` to render().
|
|
62
|
+
|
|
63
|
+
## client-metrics-adaptor
|
|
64
|
+
|
|
65
|
+
This module is an adaptor of the [dotcom-reliability-kit client metrics package](https://github.com/Financial-Times/dotcom-reliability-kit/tree/main/packages/client-metrics-web) to help integrating RUM metrics for the Custom Code Components.
|
|
66
|
+
|
|
67
|
+
### Requirements
|
|
68
|
+
|
|
69
|
+
To use it, you are required to have pre-installed the client-metrics-web package.
|
|
70
|
+
|
|
71
|
+
### Options
|
|
72
|
+
|
|
73
|
+
- **enrichEventCb** (optional): function to enrich the event payload before is sent to AWS RUM
|
|
74
|
+
- **shouldIgnoreEventCb** (optional): function that checks if the event should be sent or not to AWS RUM
|
|
75
|
+
|
|
76
|
+
### Usage:
|
|
77
|
+
|
|
78
|
+
Initialise the ClientMetrics Adaptor in your project to start listening to CCC Events and dispatch automatically AWS RUM events
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
#### example.jsx
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import { MetricsClient } from '@dotcom-reliability-kit/client-metrics-web';
|
|
85
|
+
import * as ClientMetricsAdaptor from '@financial-times/custom-code-component';
|
|
86
|
+
|
|
87
|
+
const client = new MetricsClient({
|
|
88
|
+
// Options go here
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
ClientMetricsAdaptor.init(client, {
|
|
92
|
+
enrichEventCb: enrichCCCEventWithHomepageContext,
|
|
93
|
+
shouldIgnoreEventCb: shouldIgnoreCCCEvent,
|
|
94
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
declare class CCCEvent extends Event {
|
|
2
|
+
component: ComponentPath;
|
|
3
|
+
source?: string;
|
|
4
|
+
static eventType: string;
|
|
5
|
+
constructor(eventType: string | undefined, detail: {
|
|
6
|
+
component: ComponentPath;
|
|
7
|
+
source?: string;
|
|
8
|
+
}, opts?: EventInit);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare class ComponentPath {
|
|
12
|
+
org: string;
|
|
13
|
+
repo: string;
|
|
14
|
+
name: string;
|
|
15
|
+
versionRange: string;
|
|
16
|
+
constructor(path?: ComponentPathType | string);
|
|
17
|
+
set path(path: ComponentPathType | string);
|
|
18
|
+
get path(): string;
|
|
19
|
+
get isValid(): boolean;
|
|
20
|
+
toString(): string;
|
|
21
|
+
static fromString(p?: string | null, v?: string | null): ComponentPath;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare type ComponentPathType = {
|
|
25
|
+
org: string;
|
|
26
|
+
repo: string;
|
|
27
|
+
name: string;
|
|
28
|
+
versionRange: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export declare const DEFAULT_VALUE = "uknown";
|
|
32
|
+
|
|
33
|
+
export declare const EVENT_TYPES: Record<string, EventType>;
|
|
34
|
+
|
|
35
|
+
declare type EventType = {
|
|
36
|
+
namespace: string;
|
|
37
|
+
payloadFn: (event: GenericCCCEvent) => Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export declare type GenericCCCEvent = CCCEvent | ErrorEvent;
|
|
41
|
+
|
|
42
|
+
export declare function getComponentProps(eventComponentProp: ComponentPathType): {
|
|
43
|
+
name: string;
|
|
44
|
+
org: string;
|
|
45
|
+
repo: string;
|
|
46
|
+
versionRange: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export declare function getEnrichEventProps(event: GenericCCCEvent): Record<string, unknown>;
|
|
50
|
+
|
|
51
|
+
export declare function getErrorPayload(event: GenericCCCEvent): Record<string, unknown>;
|
|
52
|
+
|
|
53
|
+
export declare function getPendingEventsQueue(): PendingEvent[];
|
|
54
|
+
|
|
55
|
+
export declare function getSuccessPayload(event: GenericCCCEvent): Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialises the Adaptor with the provided configuration and starts listening to CCC events.
|
|
59
|
+
* If there's any pending event, it'll send them to the Metrics server
|
|
60
|
+
* @param _metricsClient MetricClient instance
|
|
61
|
+
* @param _options Options to configure adaptor
|
|
62
|
+
*/
|
|
63
|
+
export declare function init(_metricsClient: MetricsClient, _options?: Options): void;
|
|
64
|
+
|
|
65
|
+
export declare const MAX_PENDING_EVENTS = 50;
|
|
66
|
+
|
|
67
|
+
export declare type MetricsClient = {
|
|
68
|
+
recordEvent: (namespace: string, payload?: Record<string, unknown>) => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export declare function onEventReceived(_event: Event): void;
|
|
72
|
+
|
|
73
|
+
declare type Options = {
|
|
74
|
+
enrichEventCb?: (event: GenericCCCEvent) => Record<string, unknown> | void;
|
|
75
|
+
shouldIgnoreEventCb?: (event: GenericCCCEvent) => boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
declare type PendingEvent = {
|
|
79
|
+
time: number;
|
|
80
|
+
event: GenericCCCEvent;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export declare function processEvent(event: GenericCCCEvent): void;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Sends all pending events waiting to be processed
|
|
87
|
+
*
|
|
88
|
+
* Notes:
|
|
89
|
+
* 1. All these events will be registered in RUM with the same time
|
|
90
|
+
* (at this moment the MetricsClient API doesn't allow to override it)
|
|
91
|
+
* 2. It's safe to process them in bulk since ClientMetrics will send them in bulk
|
|
92
|
+
* (all in 1 request)
|
|
93
|
+
*/
|
|
94
|
+
export declare function sendPendingEvents(): void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Start listening to CCC events
|
|
98
|
+
* In case there's no MetricsClient registered, it'll store them until they can be sent
|
|
99
|
+
*/
|
|
100
|
+
export declare function startListeners(): void;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Stop listening CCC events
|
|
104
|
+
*/
|
|
105
|
+
export declare function stopListeners(): void;
|
|
106
|
+
|
|
107
|
+
export { }
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const f = Object.freeze({
|
|
2
|
+
"ccc:ready": {
|
|
3
|
+
namespace: "ccc.success",
|
|
4
|
+
payloadFn: y
|
|
5
|
+
},
|
|
6
|
+
"ccc:error": {
|
|
7
|
+
namespace: "ccc.failure",
|
|
8
|
+
payloadFn: w
|
|
9
|
+
}
|
|
10
|
+
}), i = "uknown", t = [], g = 50;
|
|
11
|
+
let a, n, s = !1;
|
|
12
|
+
function h() {
|
|
13
|
+
s || (window.addEventListener("ccc:error", u), window.addEventListener("ccc:ready", u)), s = !0;
|
|
14
|
+
}
|
|
15
|
+
function b() {
|
|
16
|
+
s && (window.removeEventListener("ccc:error", u), window.removeEventListener("ccc:ready", u)), s = !1, t.length = 0, a = null, n = {};
|
|
17
|
+
}
|
|
18
|
+
function v(e, c) {
|
|
19
|
+
if (!e || typeof e.recordEvent != "function") {
|
|
20
|
+
console.warn("CCC Can't initialise MetricsClientAdaptor");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
a = e, n = {}, typeof (c == null ? void 0 : c.shouldIgnoreEventCb) == "function" && (n.shouldIgnoreEventCb = c.shouldIgnoreEventCb), typeof (c == null ? void 0 : c.enrichEventCb) == "function" && (n.enrichEventCb = c.enrichEventCb), o(), h();
|
|
24
|
+
}
|
|
25
|
+
function d(e) {
|
|
26
|
+
var c;
|
|
27
|
+
try {
|
|
28
|
+
const r = ((c = n == null ? void 0 : n.enrichEventCb) == null ? void 0 : c.call(n, e)) || {};
|
|
29
|
+
if (typeof r != "object" || Array.isArray(r))
|
|
30
|
+
throw new TypeError("Enrich event callback returned invalid value");
|
|
31
|
+
return r;
|
|
32
|
+
} catch (r) {
|
|
33
|
+
return console.error("CCC RUM event couldn't be enriched", r), {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function l(e) {
|
|
37
|
+
return {
|
|
38
|
+
name: (e == null ? void 0 : e.name) || i,
|
|
39
|
+
org: (e == null ? void 0 : e.org) || i,
|
|
40
|
+
repo: (e == null ? void 0 : e.repo) || i,
|
|
41
|
+
versionRange: (e == null ? void 0 : e.versionRange) || i
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function y(e) {
|
|
45
|
+
return {
|
|
46
|
+
...d(e),
|
|
47
|
+
component: l(e.component)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function w(e) {
|
|
51
|
+
const c = e.error, r = c == null ? void 0 : c.name;
|
|
52
|
+
return {
|
|
53
|
+
...d(e),
|
|
54
|
+
component: l(c == null ? void 0 : c.component),
|
|
55
|
+
error: r || i
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function E(e) {
|
|
59
|
+
if (n.shouldIgnoreEventCb && n.shouldIgnoreEventCb(e))
|
|
60
|
+
return;
|
|
61
|
+
if (!a) {
|
|
62
|
+
console.warn("CCC Couldn't process event: ClientMetrics not initialised");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const c = f[e.type];
|
|
66
|
+
if (!c) {
|
|
67
|
+
console.warn("CCC event.type not registered");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
a.recordEvent(c.namespace, c.payloadFn(e));
|
|
71
|
+
}
|
|
72
|
+
function u(e) {
|
|
73
|
+
const c = e;
|
|
74
|
+
a ? E(c) : (t.push({ event: c, time: Date.now() }), t.splice(0, t.length - g));
|
|
75
|
+
}
|
|
76
|
+
function o() {
|
|
77
|
+
t.forEach(({ event: e }) => E(e)), t.length = 0;
|
|
78
|
+
}
|
|
79
|
+
function L() {
|
|
80
|
+
return t;
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
i as DEFAULT_VALUE,
|
|
84
|
+
f as EVENT_TYPES,
|
|
85
|
+
g as MAX_PENDING_EVENTS,
|
|
86
|
+
l as getComponentProps,
|
|
87
|
+
d as getEnrichEventProps,
|
|
88
|
+
w as getErrorPayload,
|
|
89
|
+
L as getPendingEventsQueue,
|
|
90
|
+
y as getSuccessPayload,
|
|
91
|
+
v as init,
|
|
92
|
+
u as onEventReceived,
|
|
93
|
+
E as processEvent,
|
|
94
|
+
o as sendPendingEvents,
|
|
95
|
+
h as startListeners,
|
|
96
|
+
b as stopListeners
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=client-metrics-adaptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-metrics-adaptor.js","sources":["../src/client-metrics-adaptor.ts"],"sourcesContent":["// MetricsClient type from https://github.com/Financial-Times/dotcom-reliability-kit/tree/main/packages/client-metrics-web\n// We intentionally copied this type here to avoid having version conflicts between this module and it's client\nimport type { ComponentPathType } from './path'\nimport type { CCCEvent } from './events'\n\nexport type MetricsClient = {\n recordEvent: (namespace: string, payload?: Record<string, unknown>) => void\n};\n\nexport type GenericCCCEvent = CCCEvent | ErrorEvent;\n\ntype Options = {\n enrichEventCb?: (event: GenericCCCEvent) => Record<string, unknown> | void\n shouldIgnoreEventCb?: (event: GenericCCCEvent) => boolean\n};\n\ntype PendingEvent = {\n time: number\n event: GenericCCCEvent\n};\n\ntype EventType = {\n namespace: string\n payloadFn: (event: GenericCCCEvent) => Record<string, unknown>\n};\n\nexport const EVENT_TYPES: Record<string, EventType> = Object.freeze({\n 'ccc:ready': {\n namespace: 'ccc.success',\n payloadFn: getSuccessPayload,\n },\n 'ccc:error': {\n namespace: 'ccc.failure',\n payloadFn: getErrorPayload,\n },\n});\n\nexport const DEFAULT_VALUE = 'uknown';\n\nconst pendingEvents: PendingEvent[] = [];\nexport const MAX_PENDING_EVENTS = 50;\nlet metricsClient: MetricsClient | null;\nlet options: Options;\nlet isRunning = false;\n\n/**\n * Start listening to CCC events\n * In case there's no MetricsClient registered, it'll store them until they can be sent\n */\nexport function startListeners() {\n if (!isRunning) {\n window.addEventListener('ccc:error', onEventReceived);\n window.addEventListener('ccc:ready', onEventReceived);\n }\n isRunning = true;\n}\n\n/**\n * Stop listening CCC events\n */\nexport function stopListeners() {\n if (isRunning) {\n window.removeEventListener('ccc:error', onEventReceived);\n window.removeEventListener('ccc:ready', onEventReceived);\n }\n\n isRunning = false;\n pendingEvents.length = 0;\n metricsClient = null;\n options = {};\n}\n\n/**\n * Initialises the Adaptor with the provided configuration and starts listening to CCC events.\n * If there's any pending event, it'll send them to the Metrics server\n * @param _metricsClient MetricClient instance\n * @param _options Options to configure adaptor\n */\nexport function init(_metricsClient: MetricsClient, _options?: Options) {\n if (!_metricsClient || typeof _metricsClient.recordEvent !== 'function') {\n console.warn(\"CCC Can't initialise MetricsClientAdaptor\");\n return;\n }\n\n metricsClient = _metricsClient;\n\n options = {};\n if (typeof _options?.shouldIgnoreEventCb === 'function') {\n options.shouldIgnoreEventCb = _options.shouldIgnoreEventCb;\n }\n if (typeof _options?.enrichEventCb === 'function') {\n options.enrichEventCb = _options.enrichEventCb;\n }\n\n sendPendingEvents();\n startListeners();\n}\n\nexport function getEnrichEventProps(event: GenericCCCEvent) {\n try {\n const props = options?.enrichEventCb?.(event) || {};\n if (typeof props !== 'object' || Array.isArray(props)) {\n throw new TypeError('Enrich event callback returned invalid value');\n }\n return props;\n } catch (error) {\n console.error(\"CCC RUM event couldn't be enriched\", error);\n return {};\n }\n}\n\nexport function getComponentProps(eventComponentProp: ComponentPathType) {\n return {\n name: eventComponentProp?.name || DEFAULT_VALUE,\n org: eventComponentProp?.org || DEFAULT_VALUE,\n repo: eventComponentProp?.repo || DEFAULT_VALUE,\n versionRange: eventComponentProp?.versionRange || DEFAULT_VALUE,\n };\n}\n\nexport function getSuccessPayload(event: GenericCCCEvent): Record<string, unknown> {\n return {\n ...getEnrichEventProps(event),\n component: getComponentProps((event as CCCEvent).component),\n };\n}\n\nexport function getErrorPayload(event: GenericCCCEvent): Record<string, unknown> {\n const error = (event as ErrorEvent).error;\n const errorName = error?.name;\n\n return {\n ...getEnrichEventProps(event),\n component: getComponentProps(error?.component),\n error: errorName || DEFAULT_VALUE,\n };\n}\n\nexport function processEvent(event: GenericCCCEvent) {\n // Stop processing the event if we should ignore it\n if (options.shouldIgnoreEventCb && options.shouldIgnoreEventCb(event)) {\n return;\n }\n\n if (!metricsClient) {\n console.warn(\"CCC Couldn't process event: ClientMetrics not initialised\");\n return;\n }\n\n const eventType = EVENT_TYPES[event.type];\n if (!eventType) {\n console.warn('CCC event.type not registered');\n return;\n }\n\n metricsClient.recordEvent(eventType.namespace, eventType.payloadFn(event));\n}\n\nexport function onEventReceived(_event: Event) {\n // The Event object dispatched from the CCC has extra custom props\n const event = _event as GenericCCCEvent\n\n if (metricsClient) {\n processEvent(event);\n } else {\n pendingEvents.push({ event: event, time: Date.now() });\n // keep only the last MAX_PENDING_EVENTS events (FIFO)\n pendingEvents.splice(0, pendingEvents.length - MAX_PENDING_EVENTS);\n }\n}\n\n/**\n * Sends all pending events waiting to be processed\n *\n * Notes:\n * 1. All these events will be registered in RUM with the same time\n * (at this moment the MetricsClient API doesn't allow to override it)\n * 2. It's safe to process them in bulk since ClientMetrics will send them in bulk\n * (all in 1 request)\n */\nexport function sendPendingEvents() {\n pendingEvents.forEach(({ event }) => processEvent(event));\n pendingEvents.length = 0;\n}\n\nexport function getPendingEventsQueue() {\n return pendingEvents;\n}\n"],"names":["EVENT_TYPES","getSuccessPayload","getErrorPayload","DEFAULT_VALUE","pendingEvents","MAX_PENDING_EVENTS","metricsClient","options","isRunning","startListeners","onEventReceived","stopListeners","init","_metricsClient","_options","sendPendingEvents","getEnrichEventProps","event","_a","props","error","getComponentProps","eventComponentProp","errorName","processEvent","eventType","_event","getPendingEventsQueue"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@financial-times/custom-code-component",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"types": "./dist/custom-element.d.ts",
|
|
12
12
|
"default": "./dist/custom-element.js"
|
|
13
13
|
},
|
|
14
|
+
"./client-metrics-adaptor": {
|
|
15
|
+
"types": "./dist/client-metrics-adaptor.d.ts",
|
|
16
|
+
"default": "./dist/client-metrics-adaptor.js"
|
|
17
|
+
},
|
|
14
18
|
"./custom-code-component.css": "./src/custom-code-component.css"
|
|
15
19
|
},
|
|
16
20
|
"scripts": {
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// MetricsClient type from https://github.com/Financial-Times/dotcom-reliability-kit/tree/main/packages/client-metrics-web
|
|
2
|
+
// We intentionally copied this type here to avoid having version conflicts between this module and it's client
|
|
3
|
+
import type { ComponentPathType } from './path'
|
|
4
|
+
import type { CCCEvent } from './events'
|
|
5
|
+
|
|
6
|
+
export type MetricsClient = {
|
|
7
|
+
recordEvent: (namespace: string, payload?: Record<string, unknown>) => void
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type GenericCCCEvent = CCCEvent | ErrorEvent;
|
|
11
|
+
|
|
12
|
+
type Options = {
|
|
13
|
+
enrichEventCb?: (event: GenericCCCEvent) => Record<string, unknown> | void
|
|
14
|
+
shouldIgnoreEventCb?: (event: GenericCCCEvent) => boolean
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type PendingEvent = {
|
|
18
|
+
time: number
|
|
19
|
+
event: GenericCCCEvent
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type EventType = {
|
|
23
|
+
namespace: string
|
|
24
|
+
payloadFn: (event: GenericCCCEvent) => Record<string, unknown>
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const EVENT_TYPES: Record<string, EventType> = Object.freeze({
|
|
28
|
+
'ccc:ready': {
|
|
29
|
+
namespace: 'ccc.success',
|
|
30
|
+
payloadFn: getSuccessPayload,
|
|
31
|
+
},
|
|
32
|
+
'ccc:error': {
|
|
33
|
+
namespace: 'ccc.failure',
|
|
34
|
+
payloadFn: getErrorPayload,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_VALUE = 'uknown';
|
|
39
|
+
|
|
40
|
+
const pendingEvents: PendingEvent[] = [];
|
|
41
|
+
export const MAX_PENDING_EVENTS = 50;
|
|
42
|
+
let metricsClient: MetricsClient | null;
|
|
43
|
+
let options: Options;
|
|
44
|
+
let isRunning = false;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Start listening to CCC events
|
|
48
|
+
* In case there's no MetricsClient registered, it'll store them until they can be sent
|
|
49
|
+
*/
|
|
50
|
+
export function startListeners() {
|
|
51
|
+
if (!isRunning) {
|
|
52
|
+
window.addEventListener('ccc:error', onEventReceived);
|
|
53
|
+
window.addEventListener('ccc:ready', onEventReceived);
|
|
54
|
+
}
|
|
55
|
+
isRunning = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Stop listening CCC events
|
|
60
|
+
*/
|
|
61
|
+
export function stopListeners() {
|
|
62
|
+
if (isRunning) {
|
|
63
|
+
window.removeEventListener('ccc:error', onEventReceived);
|
|
64
|
+
window.removeEventListener('ccc:ready', onEventReceived);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
isRunning = false;
|
|
68
|
+
pendingEvents.length = 0;
|
|
69
|
+
metricsClient = null;
|
|
70
|
+
options = {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Initialises the Adaptor with the provided configuration and starts listening to CCC events.
|
|
75
|
+
* If there's any pending event, it'll send them to the Metrics server
|
|
76
|
+
* @param _metricsClient MetricClient instance
|
|
77
|
+
* @param _options Options to configure adaptor
|
|
78
|
+
*/
|
|
79
|
+
export function init(_metricsClient: MetricsClient, _options?: Options) {
|
|
80
|
+
if (!_metricsClient || typeof _metricsClient.recordEvent !== 'function') {
|
|
81
|
+
console.warn("CCC Can't initialise MetricsClientAdaptor");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
metricsClient = _metricsClient;
|
|
86
|
+
|
|
87
|
+
options = {};
|
|
88
|
+
if (typeof _options?.shouldIgnoreEventCb === 'function') {
|
|
89
|
+
options.shouldIgnoreEventCb = _options.shouldIgnoreEventCb;
|
|
90
|
+
}
|
|
91
|
+
if (typeof _options?.enrichEventCb === 'function') {
|
|
92
|
+
options.enrichEventCb = _options.enrichEventCb;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
sendPendingEvents();
|
|
96
|
+
startListeners();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getEnrichEventProps(event: GenericCCCEvent) {
|
|
100
|
+
try {
|
|
101
|
+
const props = options?.enrichEventCb?.(event) || {};
|
|
102
|
+
if (typeof props !== 'object' || Array.isArray(props)) {
|
|
103
|
+
throw new TypeError('Enrich event callback returned invalid value');
|
|
104
|
+
}
|
|
105
|
+
return props;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error("CCC RUM event couldn't be enriched", error);
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getComponentProps(eventComponentProp: ComponentPathType) {
|
|
113
|
+
return {
|
|
114
|
+
name: eventComponentProp?.name || DEFAULT_VALUE,
|
|
115
|
+
org: eventComponentProp?.org || DEFAULT_VALUE,
|
|
116
|
+
repo: eventComponentProp?.repo || DEFAULT_VALUE,
|
|
117
|
+
versionRange: eventComponentProp?.versionRange || DEFAULT_VALUE,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getSuccessPayload(event: GenericCCCEvent): Record<string, unknown> {
|
|
122
|
+
return {
|
|
123
|
+
...getEnrichEventProps(event),
|
|
124
|
+
component: getComponentProps((event as CCCEvent).component),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function getErrorPayload(event: GenericCCCEvent): Record<string, unknown> {
|
|
129
|
+
const error = (event as ErrorEvent).error;
|
|
130
|
+
const errorName = error?.name;
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
...getEnrichEventProps(event),
|
|
134
|
+
component: getComponentProps(error?.component),
|
|
135
|
+
error: errorName || DEFAULT_VALUE,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function processEvent(event: GenericCCCEvent) {
|
|
140
|
+
// Stop processing the event if we should ignore it
|
|
141
|
+
if (options.shouldIgnoreEventCb && options.shouldIgnoreEventCb(event)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!metricsClient) {
|
|
146
|
+
console.warn("CCC Couldn't process event: ClientMetrics not initialised");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const eventType = EVENT_TYPES[event.type];
|
|
151
|
+
if (!eventType) {
|
|
152
|
+
console.warn('CCC event.type not registered');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
metricsClient.recordEvent(eventType.namespace, eventType.payloadFn(event));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function onEventReceived(_event: Event) {
|
|
160
|
+
// The Event object dispatched from the CCC has extra custom props
|
|
161
|
+
const event = _event as GenericCCCEvent
|
|
162
|
+
|
|
163
|
+
if (metricsClient) {
|
|
164
|
+
processEvent(event);
|
|
165
|
+
} else {
|
|
166
|
+
pendingEvents.push({ event: event, time: Date.now() });
|
|
167
|
+
// keep only the last MAX_PENDING_EVENTS events (FIFO)
|
|
168
|
+
pendingEvents.splice(0, pendingEvents.length - MAX_PENDING_EVENTS);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Sends all pending events waiting to be processed
|
|
174
|
+
*
|
|
175
|
+
* Notes:
|
|
176
|
+
* 1. All these events will be registered in RUM with the same time
|
|
177
|
+
* (at this moment the MetricsClient API doesn't allow to override it)
|
|
178
|
+
* 2. It's safe to process them in bulk since ClientMetrics will send them in bulk
|
|
179
|
+
* (all in 1 request)
|
|
180
|
+
*/
|
|
181
|
+
export function sendPendingEvents() {
|
|
182
|
+
pendingEvents.forEach(({ event }) => processEvent(event));
|
|
183
|
+
pendingEvents.length = 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function getPendingEventsQueue() {
|
|
187
|
+
return pendingEvents;
|
|
188
|
+
}
|