@@ -13,68 +13,91 @@ const inMemoryCache = new Map<
1313 }
1414> ( )
1515
16+ const inFlightRequests = new Map < string , Promise < unknown > > ( )
17+
1618export const bungiePlatformHttp = ( opts : { ttl : number } ) => ( {
1719 fetch : async < T > ( config : BungieFetchConfig ) => {
1820 const cacheKey = config . url . toString ( )
21+
22+ // Check cache first
1923 if ( inMemoryCache . has ( cacheKey ) ) {
2024 return inMemoryCache . get ( cacheKey ) ! . data as T
2125 }
2226
23- const apiKey = process . env . BUNGIE_API_KEY
24- if ( ! apiKey ) {
25- throw new Error ( "Missing Bungie API Key" )
27+ // Check if request is already in-flight
28+ if ( inFlightRequests . has ( cacheKey ) ) {
29+ return inFlightRequests . get ( cacheKey ) ! as Promise < T >
2630 }
2731
28- const headers = new Headers ( config . headers )
29- headers . append ( "X-API-KEY" , apiKey )
30- headers . append ( "User-Agent" , `RaidHub-API/${ apiVersion } (contact=admin@raidhub.io)` )
32+ // Create the request promise
33+ const requestPromise = ( async ( ) => {
34+ const apiKey = process . env . BUNGIE_API_KEY
35+ if ( ! apiKey ) {
36+ throw new Error ( "Missing Bungie API Key" )
37+ }
3138
32- const response = await fetch ( config . url , {
33- method : config . method ,
34- headers : headers ,
35- body : config . body
36- } )
39+ const headers = new Headers ( config . headers )
40+ headers . append ( "X-API-KEY" , apiKey )
41+ headers . append ( "User-Agent" , `RaidHub-API/${ apiVersion } (contact=admin@raidhub.io)` )
3742
38- if ( response . headers . get ( "content-type" ) ?. includes ( "application/json" ) ) {
39- const data = ( await response . json ( ) ) as BungieNetResponse < unknown >
43+ const response = await fetch ( config . url , {
44+ method : config . method ,
45+ headers : headers ,
46+ body : config . body
47+ } )
4048
41- if ( ! ( "ErrorCode" in data ) ) {
42- throw new Error ( "Invalid JSON response" , {
43- cause : data
44- } )
45- }
49+ if ( response . headers . get ( "content-type" ) ?. includes ( "application/json" ) ) {
50+ const data = ( await response . json ( ) ) as BungieNetResponse < unknown >
4651
47- if ( data . ErrorCode !== 1 ) {
48- throw new BungieApiError ( {
49- cause : data ,
50- url : config . url
51- } )
52- } else {
53- // This is needed because we could have fired multiple requests to the same URL simultaneously
54- if ( inMemoryCache . has ( cacheKey ) ) {
55- clearTimeout ( inMemoryCache . get ( cacheKey ) ! . timer )
52+ if ( ! ( "ErrorCode" in data ) ) {
53+ throw new Error ( "Invalid JSON response" , {
54+ cause : data
55+ } )
5656 }
57- inMemoryCache . set ( cacheKey , {
58- data,
59- timer : setTimeout ( ( ) => {
60- inMemoryCache . delete ( cacheKey )
61- } , opts . ttl )
62- } )
6357
64- return data as T
65- }
66- } else {
67- const body = await response . text ( )
68- const match = body . match ( htmlRegex )
69- if ( match ) {
70- throw new Error ( `Invalid HTML response (${ response . status } ): ${ match [ 1 ] } ` , {
71- cause : body
72- } )
58+ if ( data . ErrorCode !== 1 ) {
59+ throw new BungieApiError ( {
60+ cause : data ,
61+ url : config . url
62+ } )
63+ } else {
64+ // This is needed because we could have fired multiple requests to the same URL simultaneously
65+ if ( inMemoryCache . has ( cacheKey ) ) {
66+ clearTimeout ( inMemoryCache . get ( cacheKey ) ! . timer )
67+ }
68+ inMemoryCache . set ( cacheKey , {
69+ data,
70+ timer : setTimeout ( ( ) => {
71+ inMemoryCache . delete ( cacheKey )
72+ } , opts . ttl )
73+ } )
74+
75+ return data as T
76+ }
7377 } else {
74- throw new Error ( `Invalid response (${ response . status } ): ${ response . statusText } ` , {
75- cause : body
76- } )
78+ const body = await response . text ( )
79+ const match = body . match ( htmlRegex )
80+ if ( match ) {
81+ throw new Error ( `Invalid HTML response (${ response . status } ): ${ match [ 1 ] } ` , {
82+ cause : body
83+ } )
84+ } else {
85+ throw new Error ( `Invalid response (${ response . status } ): ${ response . statusText } ` , {
86+ cause : body
87+ } )
88+ }
7789 }
90+ } ) ( )
91+
92+ // Track the in-flight request
93+ inFlightRequests . set ( cacheKey , requestPromise )
94+
95+ try {
96+ const result = await requestPromise
97+ return result
98+ } finally {
99+ // Clean up in-flight request tracker
100+ inFlightRequests . delete ( cacheKey )
78101 }
79102 }
80103} )
0 commit comments