@@ -8,6 +8,9 @@ import collectAndStorePing from "../pings/maker.js";
88import type CommonPingData from "./common_ping_data.js" ;
99import { Context } from "../context.js" ;
1010
11+ type ValidatorFunction = ( reason ?: string ) => Promise < void > ;
12+ type PromiseCallback = ( value : void | PromiseLike < void > ) => void ;
13+
1114/**
1215 * Stores information about a ping.
1316 *
@@ -20,6 +23,12 @@ class PingType implements CommonPingData {
2023 readonly sendIfEmpty : boolean ;
2124 readonly reasonCodes : string [ ] ;
2225
26+ // The functions and promises required for the test API to
27+ // execute and synchronize with the submission API.
28+ private resolveTestPromiseFunction ?: PromiseCallback ;
29+ private rejectTestPromiseFunction ?: PromiseCallback ;
30+ private testCallback ?: ValidatorFunction ;
31+
2332 constructor ( meta : CommonPingData ) {
2433 this . name = meta . name ;
2534 this . includeClientId = meta . includeClientId ;
@@ -44,26 +53,84 @@ class PingType implements CommonPingData {
4453 * `ping_info.reason` part of the payload.
4554 */
4655 submit ( reason ?: string ) : void {
56+ // **** Read this before changing the following code! ****
57+ //
58+ // The Dispatcher does not allow dispatched tasks to await on
59+ // other dispatched tasks. Unfortunately, this causes a deadlock.
60+ // In order to work around that problem, we kick off validation
61+ // right before the actual submission takes place, through another
62+ // async function (and not through the dispatcher). After validation
63+ // is complete, regardless of the outcome, the ping submission is
64+ // finally triggered.
65+ if ( this . testCallback ) {
66+ this . testCallback ( reason )
67+ . then ( ( ) => {
68+ PingType . _private_internalSubmit ( this , reason , this . resolveTestPromiseFunction ) ;
69+ } )
70+ . catch ( e => {
71+ console . error ( `There was an error validating "${ this . name } " (${ reason ?? "no reason" } ):` , e ) ;
72+ PingType . _private_internalSubmit ( this , reason , this . rejectTestPromiseFunction ) ;
73+ } ) ;
74+ } else {
75+ PingType . _private_internalSubmit ( this , reason ) ;
76+ }
77+ }
78+
79+ private static _private_internalSubmit ( instance : PingType , reason ?: string , testResolver ?: PromiseCallback ) : void {
4780 Context . dispatcher . launch ( async ( ) => {
4881 if ( ! Context . initialized ) {
4982 console . info ( "Glean must be initialized before submitting pings." ) ;
5083 return ;
5184 }
5285
53- if ( ! Context . uploadEnabled && ! this . isDeletionRequest ( ) ) {
86+ if ( ! Context . uploadEnabled && ! instance . isDeletionRequest ( ) ) {
5487 console . info ( "Glean disabled: not submitting pings. Glean may still submit the deletion-request ping." ) ;
5588 return ;
5689 }
5790
5891 let correctedReason = reason ;
59- if ( reason && ! this . reasonCodes . includes ( reason ) ) {
92+ if ( reason && ! instance . reasonCodes . includes ( reason ) ) {
6093 console . error ( `Invalid reason code ${ reason } from ${ this . name } . Ignoring.` ) ;
6194 correctedReason = undefined ;
6295 }
6396
6497 const identifier = generateUUIDv4 ( ) ;
65- await collectAndStorePing ( identifier , this , correctedReason ) ;
98+ await collectAndStorePing ( identifier , instance , correctedReason ) ;
99+
100+ if ( testResolver ) {
101+ testResolver ( ) ;
102+
103+ // Finally clean up!
104+ instance . resolveTestPromiseFunction = undefined ;
105+ instance . rejectTestPromiseFunction = undefined ;
106+ instance . testCallback = undefined ;
107+ }
108+ } ) ;
109+ }
110+
111+ /**
112+ * **Test-only API**
113+ *
114+ * Runs a validation function before the ping is collected.
115+ *
116+ * TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
117+ *
118+ * @param callbackFn The asynchronous validation function to run in order to validate
119+ * the ping content.
120+ *
121+ * @returns A `Promise` resolved when the ping is collected and the validation function
122+ * is executed.
123+ */
124+ async testBeforeNextSubmit ( callbackFn : ValidatorFunction ) : Promise < void > {
125+ if ( this . testCallback ) {
126+ console . error ( `There is an existing test call for ping "${ this . name } ". Ignoring.` ) ;
66127 return ;
128+ }
129+
130+ return new Promise ( ( resolve , reject ) => {
131+ this . resolveTestPromiseFunction = resolve ;
132+ this . rejectTestPromiseFunction = reject ;
133+ this . testCallback = callbackFn ;
67134 } ) ;
68135 }
69136}
0 commit comments