this repo has no description
at main 117 lines 3.8 kB view raw
1import type { Network, FetchRequest, FetchResponse } from '@jet/environment'; 2import { fromEntries } from '@amp/web-apps-utils'; 3 4import { 5 shouldUseSearchJWT, 6 makeSearchJWTAuthorizationHeader, 7} from '~/config/media-api'; 8 9const CORRELATION_KEY_HEADER = 'x-apple-jingle-correlation-key'; 10 11type FetchFunction = typeof window.fetch; 12 13// TODO: these URLs are also referenced in `bag` definition; we should have a single 14// source-of-truth for these domains 15const MEDIA_API_ORIGINS = [ 16 'https://amp-api.apps.apple.com', 17 'https://amp-api-edge.apps.apple.com', 18 'https://amp-api-search-edge.apps.apple.com', 19]; 20 21export interface FeaturesCallbacks { 22 getITFEValues(): string | undefined; 23} 24 25export class Net implements Network { 26 private readonly underlyingFetch: FetchFunction; 27 private readonly getITFEValues: () => string | undefined = () => undefined; 28 29 constructor( 30 underlyingFetch: FetchFunction, 31 featuresCallbacks?: FeaturesCallbacks, 32 ) { 33 this.underlyingFetch = underlyingFetch; 34 this.getITFEValues = 35 featuresCallbacks?.getITFEValues ?? this.getITFEValues; 36 } 37 38 async fetch(request: FetchRequest): Promise<FetchResponse> { 39 const requestStartTime = getTimestampMs(); 40 const requestURL = new URL(request.url); 41 42 request.headers = request.headers ?? {}; 43 44 if (MEDIA_API_ORIGINS.includes(requestURL.origin)) { 45 // Need to fake this for the server due to Kong origin checks. 46 // Has no effect clientside. 47 request.headers['origin'] = 'https://apps.apple.com'; 48 49 const itfe = this.getITFEValues?.(); 50 51 if (itfe) { 52 // Add ITFE value as query string when set 53 requestURL.searchParams.set('itfe', itfe); 54 } 55 } 56 57 // The App Store Client will have already injected the JWT from the 58 // `media-token-service` ObjectGraph dependency into the headers. However, 59 // some endpoints need a different JWT. Here we determine if that's the 60 // case and override the existing JWT if necessary. 61 if (shouldUseSearchJWT(requestURL)) { 62 request.headers = { 63 ...request.headers, 64 ...makeSearchJWTAuthorizationHeader(), 65 }; 66 } 67 68 // TODO: rdar://78158575: timeout 69 const response = await this.underlyingFetch(requestURL.toString(), { 70 ...request, 71 cache: request.cache ?? undefined, 72 credentials: 'include', 73 headers: request.headers ?? undefined, 74 method: request.method ?? undefined, 75 }); 76 77 const responseStartTime = getTimestampMs(); 78 79 const { ok, redirected, status, statusText, url } = response; 80 81 const headers = fromEntries(response.headers); 82 const body = await response.text(); 83 84 const responseEndTime = getTimestampMs(); 85 86 return { 87 ok, 88 headers, 89 redirected, 90 status, 91 statusText, 92 url, 93 body, 94 // TODO: rdar://78158575: redirect: 'manual' to get all metrics? 95 metrics: [ 96 { 97 clientCorrelationKey: response.headers.get( 98 CORRELATION_KEY_HEADER, 99 ), 100 pageURL: response.url, 101 requestStartTime, 102 responseStartTime, 103 responseEndTime, 104 // TODO: rdar://78158575: responseWasCached? 105 // TODO: rdar://78158575: parseStartTime/parseEndTime 106 }, 107 ], 108 }; 109 } 110} 111 112/** 113 * Returns the current UTC timestamp in milliseconds. 114 */ 115function getTimestampMs(): number { 116 return Date.now(); 117}