@@ -175,34 +175,37 @@ export function calculateNextReconnectDelay(context: ReconnectDelayContext) : nu
175175class ParsedUsername {
176176 prefix : string = "" ;
177177 queryParams : [ string , string ] [ ] = new Array < [ string , string ] > ;
178- // metadata: [string, string][] = new Array<[string, string]> ;
178+ metadata ? : [ string , string ] [ ] = undefined ;
179179}
180180
181181const METADATA_KEY : string = "Metadata" ;
182182const SDK_KEY : string = "SDK" ;
183183const PLATFORM_KEY : string = "Platform" ;
184184const BROWSER_PLATFORM_VALUE : string = "Browser" ;
185185const BROWSER_METADATA_KEY : string = "Browser" ;
186-
187- function parseDelimitedKeyValueString ( input : string , pairDelimiter : string , kvDelimeter : string ) : Array < [ string , string ] > | null {
186+ const QUERY_PARAM_START_DELIMITER : string = "?" ;
187+ const QUERY_PAIR_DELIMITER : string = "&" ;
188+ const METADATA_PAIR_DELIMITER : string = ";" ;
189+ const KEY_VALUE_SEPARATOR : string = "=" ;
190+ const METADATA_PREFIX : string = "(" ;
191+ const METADATA_SUFFIX : string = ")" ;
192+
193+ function parseDelimitedKeyValueString ( input : string , pairDelimiter : string , kvDelimiter : string ) : Array < [ string , string ] > {
188194 let kvPairs = new Array < [ string , string ] > ;
189195
190196 let pairs = input . split ( pairDelimiter ) ;
191197 for ( let pair of pairs ) {
192- let kvDelimeterIndex = pair . indexOf ( kvDelimeter ) ;
193- if ( kvDelimeterIndex < 0 ) {
198+ let kvDelimiterIndex = pair . indexOf ( kvDelimiter ) ;
199+ // just a key, no value?
200+ if ( kvDelimiterIndex < 0 ) {
194201 kvPairs . push ( [ pair , "" ] ) ;
195202 }
196203
197- let value = pair . substring ( kvDelimeterIndex + 1 ) ;
198- kvPairs . push ( [ pair . substring ( 0 , kvDelimeterIndex ) , value ] ) ;
199- }
200-
201- if ( kvPairs . length > 0 ) {
202- return kvPairs ;
204+ let value = pair . substring ( kvDelimiterIndex + 1 ) ;
205+ kvPairs . push ( [ pair . substring ( 0 , kvDelimiterIndex ) , value ] ) ;
203206 }
204207
205- return null ;
208+ return kvPairs ;
206209}
207210
208211function parseUsername ( username ?: string ) : ParsedUsername {
@@ -212,80 +215,120 @@ function parseUsername(username?: string) : ParsedUsername {
212215 return parsed ;
213216 }
214217
215- let queryIndex = username . lastIndexOf ( '?' ) ;
218+ let queryIndex = username . lastIndexOf ( QUERY_PARAM_START_DELIMITER ) ;
216219 if ( queryIndex < 0 ) {
220+ // no '?'
217221 parsed . prefix = username ;
218222 return parsed ;
219223 }
220224
221225 parsed . prefix = username . substring ( 0 , queryIndex ) ;
222226
223227 let remaining = username . substring ( queryIndex + 1 ) ;
224- let topLevelPairs = parseDelimitedKeyValueString ( remaining , "&" , "=" ) ;
225- if ( topLevelPairs ) {
226- parsed . queryParams = topLevelPairs ;
227- /*
228- let metadataValue = topLevelPairs.get(METADATA_KEY);
229- if (metadataValue !== undefined && metadataValue.length >= 2 && metadataValue[0] == '(' && metadataValue[metadataValue.length - 1] == ')') {
230- metadataValue = metadataValue.substring(1, metadataValue.length - 2);
231- let metadataMap = parseDelimitedKeyValueString(metadataValue, ";", "=");
232- if (metadataMap) {
233- parsed.metadata = metadataMap;
234- parsed.queryParams.delete(METADATA_KEY);
228+ parsed . queryParams = parseDelimitedKeyValueString ( remaining , QUERY_PAIR_DELIMITER , KEY_VALUE_SEPARATOR ) ;
229+ let seenMetadata = false ;
230+ for ( let pair of parsed . queryParams ) {
231+ if ( pair [ 0 ] === METADATA_KEY && ! seenMetadata ) {
232+ // only process the first instance of metadata
233+ seenMetadata = true ;
234+ let metadataValue = pair [ 1 ] ;
235+ if ( metadataValue !== undefined && metadataValue . length >= 2 && metadataValue . startsWith ( METADATA_PREFIX ) && metadataValue . endsWith ( METADATA_SUFFIX ) ) {
236+ metadataValue = metadataValue . substring ( METADATA_PREFIX . length , metadataValue . length - ( METADATA_PREFIX . length + METADATA_SUFFIX . length ) ) ;
237+ parsed . metadata = parseDelimitedKeyValueString ( metadataValue , METADATA_PAIR_DELIMITER , KEY_VALUE_SEPARATOR ) ;
235238 }
236- }*/
239+ break ;
240+ }
237241 }
238242
239243 return parsed ;
240244}
241245
242- function addTopLevelPairIfNonexistent ( parsed : ParsedUsername , key : string , value : string ) {
243- if ( parsed . queryParams . has ( key ) ) {
244- return ;
246+ // if this is ever public, we need to handle attempting to set metadata in a special way
247+ function addTopLevelPair ( parsed : ParsedUsername , key : string , value : string ) {
248+ // no need to check for duplicates; when we build the final username we use the first value only
249+ parsed . queryParams . push ( [ key , value ] ) ;
250+ }
251+
252+ function addMetadataPair ( parsed : ParsedUsername , key : string , value : string ) {
253+ let hasMetadataKey = false ;
254+ for ( let pair of parsed . queryParams ) {
255+ if ( pair [ 0 ] === METADATA_KEY ) {
256+ hasMetadataKey = true ;
257+ break ;
258+ }
245259 }
246260
247- parsed . queryParams . set ( key , value ) ;
248- }
261+ // If we have malformed metadata (top level entry, but no parsed pairs), then don't do anything
262+ if ( ! parsed . metadata ) {
263+ if ( hasMetadataKey ) {
264+ return ;
265+ }
249266
250- function addMetadataPairIfNonExistent ( parsed : ParsedUsername , key : string , value : string ) {
251- // can't add metadata pairs if there's an existing top-level metadata entry that is malformed
252- if ( parsed . queryParams . has ( METADATA_KEY ) ) {
253- return ;
267+ parsed . metadata = new Array < [ string , string ] > ( ) ;
254268 }
255269
256- let metadataValue = parsed . metadata . get ( key ) ;
257- if ( metadataValue !== undefined ) {
258- return ;
270+ // no need to check for duplicates; when we build the final username we use the first value only
271+ let strippedValue = value . replace ( METADATA_PAIR_DELIMITER , "" ) ;
272+ parsed . metadata . push ( [ key , strippedValue ] ) ;
273+ }
274+
275+ function buildOrderedKeyValues ( pairs ?: Array < [ string , string ] > ) : [ Array < string > , Map < string , string > ] {
276+ let keys : Array < string > = new Array < string > ( ) ;
277+ let kvMap = new Map < string , string > ( ) ;
278+ if ( ! pairs ) {
279+ return [ keys , kvMap ] ;
259280 }
260281
261- parsed . metadata . set ( key , value ) ;
282+ pairs . forEach ( ( pair ) => {
283+ if ( ! kvMap . has ( pair [ 0 ] ) ) {
284+ kvMap . set ( pair [ 0 ] , pair [ 1 ] ) ;
285+ keys . push ( pair [ 0 ] ) ;
286+ }
287+ } ) ;
288+
289+ return [ keys , kvMap ] ;
262290}
263291
264292function buildUsernameFromQueryParse ( parsed : ParsedUsername ) : string {
265- let metadataValue = undefined ;
266- if ( ! parsed . queryParams . get ( METADATA_KEY ) && parsed . metadata . size > 0 ) {
267- let innerValue = Array . from ( parsed . metadata . entries ( ) ) . map ( ( pair ) => pair . join ( "=" ) ) . join ( ";" ) ;
268- metadataValue = "(" + innerValue + ")" ;
293+ let [ queryKeys , queryValues ] = buildOrderedKeyValues ( parsed . queryParams ) ;
294+ let [ metadataKeys , metadataValues ] = buildOrderedKeyValues ( parsed . metadata ) ;
295+
296+ if ( parsed . queryParams . length == 0 && ! parsed . metadata ) {
297+ return parsed . prefix ;
298+ }
299+
300+ // if we have valid metadata pairs, build the final metadata value
301+ let metadataValue : string | undefined = undefined ;
302+ if ( parsed . metadata && metadataKeys . length > 0 ) {
303+ let innerValue = metadataKeys . map ( ( key ) => {
304+ return `${ key } ${ KEY_VALUE_SEPARATOR } ${ metadataValues . get ( key ) ?? "" } ` ;
305+ } ) . join ( METADATA_PAIR_DELIMITER ) ;
306+ metadataValue = METADATA_PREFIX + innerValue + METADATA_SUFFIX ;
269307 }
270308
271- let topLevelParamArray = Array . from ( parsed . queryParams . entries ( ) ) ;
309+ // if we have a constructed metadata value, replace the value and make sure it's in the key list
272310 if ( metadataValue !== undefined ) {
273- topLevelParamArray . push ( [ METADATA_KEY , metadataValue ] ) ;
311+ if ( queryValues . get ( METADATA_KEY ) === undefined ) {
312+ queryKeys . push ( METADATA_KEY ) ;
313+ }
314+ queryValues . set ( METADATA_KEY , metadataValue ) ;
274315 }
275316
276- let queryParamValue = topLevelParamArray . map ( ( pair ) => pair . join ( "=" ) ) . join ( ":" ) ;
317+ let queryParamValue = queryKeys . map ( ( key ) => {
318+ return `${ key } ${ KEY_VALUE_SEPARATOR } ${ queryValues . get ( key ) ?? "" } ` ;
319+ } ) . join ( QUERY_PAIR_DELIMITER ) ;
277320
278- return parsed . prefix + "?" + queryParamValue ;
321+ return parsed . prefix + QUERY_PARAM_START_DELIMITER + queryParamValue ;
279322}
280323
281324export function buildFinalUsernameFromMetrics ( metrics : mqtt_shared . AwsIoTDeviceSDKMetrics , username ?: string ) : string {
282325 let parsed = parseUsername ( username ) ;
283326
284- addTopLevelPairIfNonexistent ( parsed , SDK_KEY , metrics . libraryName ) ;
285- addTopLevelPairIfNonexistent ( parsed , PLATFORM_KEY , BROWSER_PLATFORM_VALUE ) ;
327+ addTopLevelPair ( parsed , SDK_KEY , metrics . libraryName ) ;
328+ addTopLevelPair ( parsed , PLATFORM_KEY , BROWSER_PLATFORM_VALUE ) ;
286329
287330 let browserInfo = window ?. navigator ?. userAgent ?? "unknown" ;
288- addMetadataPairIfNonExistent ( parsed , BROWSER_METADATA_KEY , browserInfo ) ;
331+ addMetadataPair ( parsed , BROWSER_METADATA_KEY , browserInfo ) ;
289332
290333 return buildUsernameFromQueryParse ( parsed ) ;
291334}
0 commit comments