55import { StorageValue , Store } from "storage" ;
66import PersistentStore from "storage/persistent" ;
77import Metric , { Lifetime } from "metrics" ;
8- import { isObject , isString , isUndefined } from "utils" ;
8+ import { MetricPayload , isMetricPayload } from "metrics/payload" ;
9+ import { isObject , isUndefined } from "utils" ;
910
1011export interface PingPayload {
1112 [ aMetricType : string ] : {
12- [ aMetricIdentifier : string ] : string
13+ [ aMetricIdentifier : string ] : MetricPayload
1314 }
1415}
1516
@@ -20,14 +21,14 @@ export interface PingPayload {
2021 *
2122 * @returns Whether or not `v` is a valid PingPayload.
2223 */
23- export function isValidPingPayload ( v : StorageValue ) : v is PingPayload {
24+ export function isValidPingPayload ( v : unknown ) : v is PingPayload {
2425 if ( isObject ( v ) ) {
2526 // The root keys should all be metric types.
2627 for ( const metricType in v ) {
2728 const metrics = v [ metricType ] ;
2829 if ( isObject ( metrics ) ) {
2930 for ( const metricIdentifier in metrics ) {
30- if ( ! isString ( metrics [ metricIdentifier ] ) ) {
31+ if ( ! isMetricPayload ( metricType , metrics [ metricIdentifier ] ) ) {
3132 return false ;
3233 }
3334 }
@@ -98,16 +99,8 @@ class Database {
9899 * @param metric The metric to record to.
99100 * @param value The value we want to record to the given metric.
100101 */
101- async record ( metric : Metric , value : string ) : Promise < void > {
102- if ( metric . disabled ) {
103- return ;
104- }
105-
106- const store = this . _chooseStore ( metric . lifetime ) ;
107- const storageKey = metric . identifier ;
108- for ( const ping of metric . sendInPings ) {
109- await store . update ( [ ping , metric . type , storageKey ] , ( ) => value ) ;
110- }
102+ async record ( metric : Metric , value : MetricPayload ) : Promise < void > {
103+ await this . transform ( metric , ( ) => value ) ;
111104 }
112105
113106 /**
@@ -117,17 +110,17 @@ class Database {
117110 * @param metric The metric to record to.
118111 * @param transformFn The transformation function to apply to the currently persisted value.
119112 */
120- async transform ( metric : Metric , transformFn : ( v ?: string ) => string ) : Promise < void > {
113+ async transform ( metric : Metric , transformFn : ( v ?: MetricPayload ) => MetricPayload ) : Promise < void > {
121114 if ( metric . disabled ) {
122115 return ;
123116 }
124117
125118 const store = this . _chooseStore ( metric . lifetime ) ;
126119 const storageKey = metric . identifier ;
127120 for ( const ping of metric . sendInPings ) {
128- const finalTransformFn = ( v : StorageValue ) : string => {
129- if ( isObject ( v ) ) {
130- throw new Error ( `Unexpected value found for metric ${ metric } : ${ JSON . stringify ( v ) } .` ) ;
121+ const finalTransformFn = ( v : StorageValue ) : Exclude < StorageValue , undefined > => {
122+ if ( ! isUndefined ( v ) && ! isMetricPayload ( metric . type , v ) ) {
123+ throw new Error ( `Unexpected value found for metric ${ metric . identifier } : ${ JSON . stringify ( v ) } .` ) ;
131124 }
132125 return transformFn ( v ) ;
133126 } ;
@@ -139,17 +132,29 @@ class Database {
139132 * Gets the persisted payload of a given metric in a given ping.
140133 *
141134 * @param ping The ping from which we want to retrieve the given metric.
135+ * @param validateFn A validation function to verify if persisted payload is of the correct type.
142136 * @param metric An object containing the information about the metric to retrieve.
143137 *
144- * @returns The string encoded payload persisted for the given metric,
138+ * @returns The payload persisted for the given metric,
145139 * `undefined` in case the metric has not been recorded yet.
146140 */
147- async getMetric ( ping : string , metric : Metric ) : Promise < string | undefined > {
141+ async getMetric < T > (
142+ ping : string ,
143+ validateFn : ( v : unknown ) => v is T ,
144+ metric : Metric
145+ ) : Promise < T | undefined > {
148146 const store = this . _chooseStore ( metric . lifetime ) ;
149147 const storageKey = metric . identifier ;
150148 const value = await store . get ( [ ping , metric . type , storageKey ] ) ;
151- if ( isObject ( value ) ) {
152- console . error ( `Unexpected value found for metric ${ metric } : ${ JSON . stringify ( value ) } . Clearing.` ) ;
149+ if ( ! isUndefined ( value ) && ! validateFn ( value ) ) {
150+ // The following behaviour is not consistent with what the Glean SDK does, but this is on purpose.
151+ // On the Glean SDK we panic when we can't serialize the given,
152+ // that is because this is a extremely unlikely situation for that environment.
153+ //
154+ // Since Glean.js will run on the browser, it is easy for a user to mess with the persisted data
155+ // which makes this sort of errors plausible. That is why we choose to not panic and
156+ // simply delete the corrupted data here.
157+ console . error ( `Unexpected value found for metric ${ metric . identifier } : ${ JSON . stringify ( value ) } . Clearing.` ) ;
153158 await store . delete ( [ ping , metric . type , storageKey ] ) ;
154159 return ;
155160 } else {
0 commit comments