Skip to content

Commit aaf6c60

Browse files
committed
reportMetric method
1 parent 10c8152 commit aaf6c60

File tree

12 files changed

+178
-27
lines changed

12 files changed

+178
-27
lines changed

injected/src/features/duck-player.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { DuckPlayerOverlayMessages, OpenInDuckPlayerMsg, Pixel } from './duckpla
3737
import { isBeingFramed } from '../utils.js';
3838
import { initOverlays } from './duckplayer/overlays.js';
3939
import { Environment } from './duckplayer/environment.js';
40+
import { reportException } from '../../../special-pages/shared/report-metric.js';
4041

4142
/**
4243
* @typedef UserValues - A way to communicate user settings
@@ -93,19 +94,23 @@ export default class DuckPlayerFeature extends ContentFeature {
9394
throw new Error('cannot operate duck player without a messaging backend');
9495
}
9596

96-
const locale = args?.locale || args?.language || 'en';
97-
const env = new Environment({
98-
debug: this.isDebug,
99-
injectName: import.meta.injectName,
100-
platform: this.platform,
101-
locale,
102-
});
103-
const comms = new DuckPlayerOverlayMessages(this.messaging, env);
97+
try {
98+
const locale = args?.locale || args?.language || 'en';
99+
const env = new Environment({
100+
debug: this.isDebug,
101+
injectName: import.meta.injectName,
102+
platform: this.platform,
103+
locale,
104+
});
105+
const comms = new DuckPlayerOverlayMessages(this.messaging, env);
104106

105-
if (overlaysEnabled) {
106-
initOverlays(overlaySettings.youtube, env, comms);
107-
} else if (serpProxyEnabled) {
108-
comms.serpProxy();
107+
if (overlaysEnabled) {
108+
initOverlays(overlaySettings.youtube, env, comms);
109+
} else if (serpProxyEnabled) {
110+
comms.serpProxy();
111+
}
112+
} catch (e) {
113+
reportException(this.messaging, { message: 'could not initialize duck player: ' + e.toString() });
109114
}
110115
}
111116
}

special-pages/pages/duckplayer/app/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export async function init(messaging, telemetry, baseEnvironment) {
7070

7171
const didCatch = (error) => {
7272
const message = error?.message || 'unknown';
73+
messaging.reportException({ message });
74+
// TODO: Remove the following event once all native platforms are responding to 'reportMetric: exception'
7375
messaging.reportPageException({ message });
7476
};
7577

special-pages/pages/duckplayer/app/providers/UserValuesProvider.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export function UserValuesProvider({ initial, children }) {
6060
})
6161
.catch((err) => {
6262
console.error('could not set the enabled flag', err);
63+
messaging.reportException({ message: 'could not set the enabled flag: ' + err.toString() });
64+
// TODO: Remove the following event once all native platforms are responding to 'reportMetric: exception'
6365
messaging.reportPageException({ message: 'could not set the enabled flag: ' + err.toString() });
6466
});
6567
}

special-pages/pages/duckplayer/src/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createSpecialPageMessaging } from '../../../shared/create-special-page-
44
import { init } from '../app/index.js';
55
import { initStorage } from './storage.js';
66
import '../../../shared/live-reload.js';
7+
import { reportException } from '../../../shared/report-metric.js';
78

89
export class DuckplayerPage {
910
/**
@@ -115,8 +116,18 @@ export class DuckplayerPage {
115116
reportInitException(params) {
116117
this.messaging.notify('reportInitException', params);
117118
}
119+
120+
/**
121+
* This will be sent to report metrics to the native layer
122+
* @param {import('../../../shared/types/shared.ts').ReportMetricEvent['params']} params
123+
*/
124+
reportException(params) {
125+
reportException(this.messaging, params);
126+
}
118127
}
119128

129+
// TODO: Remove telemetry
130+
120131
/**
121132
* Events that occur in the client-side application
122133
*/
@@ -181,6 +192,8 @@ init(duckplayerPage, telemetry, baseEnvironment).catch((e) => {
181192
// messages.
182193
console.error(e);
183194
const msg = typeof e?.message === 'string' ? e.message : 'unknown init error';
195+
duckplayerPage.reportException({ message: msg });
196+
// TODO: Remove this event once all native platforms are responding to 'reportMetric: exception'
184197
duckplayerPage.reportInitException({ message: msg });
185198
});
186199

special-pages/pages/duckplayer/types/duckplayer.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* @module Duckplayer Messages
77
*/
88

9-
export type ReportMetricEvent = ExceptionMetric;
109
export type YouTubeError = "age-restricted" | "sign-in-required" | "no-embed" | "unknown";
1110
export type PrivatePlayerMode =
1211
| {
@@ -27,7 +26,6 @@ export interface DuckplayerMessages {
2726
| OpenInfoNotification
2827
| OpenSettingsNotification
2928
| ReportInitExceptionNotification
30-
| ReportMetricNotification
3129
| ReportPageExceptionNotification
3230
| ReportYouTubeErrorNotification
3331
| TelemetryEventNotification;
@@ -56,19 +54,6 @@ export interface ReportInitExceptionNotification {
5654
export interface ReportInitExceptionNotify {
5755
message: string;
5856
}
59-
/**
60-
* Generated from @see "../messages/reportMetric.notify.json"
61-
*/
62-
export interface ReportMetricNotification {
63-
method: "reportMetric";
64-
params: ReportMetricEvent;
65-
}
66-
export interface ExceptionMetric {
67-
metricName: "exception";
68-
params: {
69-
message: string;
70-
};
71-
}
7257
/**
7358
* Generated from @see "../messages/reportPageException.notify.json"
7459
*/
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "Report Metric Event",
4+
"type": "object",
5+
"oneOf": [
6+
{
7+
"type": "object",
8+
"title": "Exception Metric",
9+
"required": ["metricName", "params"],
10+
"properties": {
11+
"metricName": {
12+
"const": "exception"
13+
},
14+
"params": {
15+
"type": "object",
16+
"required": ["message"],
17+
"properties": {
18+
"message": {
19+
"type": "string"
20+
}
21+
}
22+
}
23+
}
24+
}
25+
]
26+
}

special-pages/shared/report-metric.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export const REPORT_METRIC_MESSAGE_ID = 'reportMetric';
2+
export const METRIC_NAME_EXCEPTION = 'exception';
3+
4+
/**
5+
* @typedef {import('../shared/types/shared.ts').SharedMessages} SharedMessages
6+
* @typedef {import('@duckduckgo/messaging/lib/shared-types.js').MessagingBase<SharedMessages>|import('@duckduckgo/messaging').Messaging} SharedMessaging
7+
*/
8+
9+
/**
10+
* Sends a standard 'reportMetric' event to the native layer
11+
* @param {SharedMessaging} messaging
12+
* @param {import('../shared/types/shared.ts').ReportMetricEvent} metricEvent
13+
*/
14+
export function reportMetric(messaging, metricEvent) {
15+
if (!messaging?.notify) {
16+
throw new Error('messaging.notify is not defined');
17+
}
18+
19+
if (!metricEvent?.metricName) {
20+
throw new Error('metricName is required');
21+
}
22+
23+
messaging.notify(REPORT_METRIC_MESSAGE_ID, metricEvent);
24+
}
25+
26+
/**
27+
* Sends a 'reportMetric' event with metric name 'exception'
28+
* @param {SharedMessaging} messaging
29+
* @param {import('../shared/types/shared.ts').ExceptionMetric['params']} params
30+
*/
31+
export function reportException(messaging, params) {
32+
reportMetric(messaging, { metricName: METRIC_NAME_EXCEPTION, params });
33+
}

special-pages/shared/types/shared.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* These types are auto-generated from schema files.
3+
* scripts/build-types.mjs is responsible for type generation.
4+
* **DO NOT** edit this file directly as your changes will be lost.
5+
*
6+
* @module Shared Messages
7+
*/
8+
9+
export type ReportMetricEvent = ExceptionMetric;
10+
11+
/**
12+
* Requests, Notifications and Subscriptions from the Shared feature
13+
*/
14+
export interface SharedMessages {
15+
notifications: ReportMetricNotification;
16+
}
17+
/**
18+
* Generated from @see "../messages/reportMetric.notify.json"
19+
*/
20+
export interface ReportMetricNotification {
21+
method: 'reportMetric';
22+
params: ReportMetricEvent;
23+
}
24+
export interface ExceptionMetric {
25+
metricName: 'exception';
26+
params: {
27+
message: string;
28+
};
29+
}

special-pages/types.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ for (const pageListElement of pageList) {
2929
};
3030
}
3131

32+
/* Shared types */
33+
specialPagesTypes.shared = {
34+
schemaDir: join(specialPagesRoot, 'shared', 'messages'),
35+
typesDir: join(specialPagesRoot, 'shared', 'types'),
36+
exclude: process.platform === 'win32',
37+
kind: 'single',
38+
resolve: (_dirname) => '../src/index.js',
39+
className: () => null,
40+
filename: `shared.ts`,
41+
};
42+
3243
if (isLaunchFile(import.meta.url)) {
3344
buildTypes(specialPagesTypes).catch((error) => {
3445
console.error(error);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, it, mock } from 'node:test';
2+
import assert from 'node:assert';
3+
import { reportMetric } from '../shared/report-metric.js';
4+
5+
// @ts-expect-error - this is a mock
6+
const messaging = /** @type {Messaging} */ ({
7+
notify: mock.fn((params) => {
8+
console.log('Notify called with', params);
9+
}),
10+
});
11+
12+
describe('reportMetric', () => {
13+
it('should throw an error if messaging is not provided', () => {
14+
const eventParams = /** @type {any} */ ({ metricName: '', params: {} });
15+
// @ts-expect-error - this is a forced error
16+
assert.throws(() => reportMetric(null, eventParams));
17+
assert.strictEqual(messaging.notify.mock.callCount(), 0);
18+
});
19+
20+
it('should throw an error if metricName is not provided', () => {
21+
const eventParams = /** @type {any} */ ({ metricName: '', params: {} });
22+
assert.throws(() => reportMetric(messaging, eventParams));
23+
assert.strictEqual(messaging.notify.mock.callCount(), 0);
24+
});
25+
26+
it('should call messaging.notify with the correct parameters', () => {
27+
const eventParams = /** @type {any} */ ({ metricName: 'exception', params: { message: 'This is a test' } });
28+
assert.strictEqual(messaging.notify.mock.callCount(), 0);
29+
reportMetric(messaging, eventParams);
30+
assert.strictEqual(messaging.notify.mock.callCount(), 1);
31+
const call = messaging.notify.mock.calls[0];
32+
assert.deepEqual(call.arguments, [
33+
'reportMetric',
34+
{
35+
metricName: 'exception',
36+
params: { message: 'This is a test' },
37+
},
38+
]);
39+
});
40+
});

0 commit comments

Comments
 (0)