this repo has no description
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}