xref: /aosp_15_r20/external/perfetto/ui/src/base/http_utils.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2019 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {assertTrue} from './logging';
16
17export function fetchWithTimeout(
18  input: RequestInfo,
19  init: RequestInit,
20  timeoutMs: number,
21) {
22  return new Promise<Response>((resolve, reject) => {
23    const timer = setTimeout(
24      () =>
25        reject(new Error(`fetch(${input}) timed out after ${timeoutMs} ms`)),
26      timeoutMs,
27    );
28    fetch(input, init)
29      .then((response) => resolve(response))
30      .catch((err) => reject(err))
31      .finally(() => clearTimeout(timer));
32  });
33}
34
35export function fetchWithProgress(
36  url: string,
37  onProgress?: (percentage: number) => void,
38): Promise<Blob> {
39  return new Promise((resolve, reject) => {
40    const xhr = new XMLHttpRequest();
41
42    xhr.open('GET', url, /* async= */ true);
43    xhr.responseType = 'blob';
44
45    xhr.onprogress = (event) => {
46      if (event.lengthComputable) {
47        const percentComplete = Math.round((event.loaded / event.total) * 100);
48        onProgress?.(percentComplete);
49      }
50    };
51
52    xhr.onload = () => {
53      if (xhr.status >= 200 && xhr.status < 300) {
54        resolve(xhr.response); // Resolve with the Blob response
55      } else {
56        reject(
57          new Error(`Failed to download: ${xhr.status} ${xhr.statusText}`),
58        );
59      }
60    };
61
62    xhr.onerror = () => {
63      reject(new Error(`Network error in fetchWithProgress(${url})`));
64    };
65
66    xhr.send();
67  });
68}
69
70/**
71 * NOTE: this function can only be called from synchronous contexts. It will
72 * fail if called in timer handlers or async continuations (e.g. after an await)
73 * Use assetSrc(relPath) which caches it on startup.
74 * @returns the directory where the app is served from, e.g. 'v46.0-a2082649b'
75 */
76export function getServingRoot() {
77  // Works out the root directory where the content should be served from
78  // e.g. `http://origin/v1.2.3/`.
79  const script = document.currentScript as HTMLScriptElement;
80
81  if (script === null) {
82    // Can be null in tests.
83    assertTrue(typeof jest !== 'undefined');
84    return '';
85  }
86
87  let root = script.src;
88  root = root.substring(0, root.lastIndexOf('/') + 1);
89  return root;
90}
91