Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | 1x 1x 1x 1x 1x 1x 1x | // eslint-disable-next-line no-unused-vars
import { ErrorType, getErrorFromCode } from './constants/errors.js';
export interface RequestOptions extends RequestInit {
query?: string[][];
}
export interface ApiErrorDetails {
/**
* The json_name of the missing / disallowed related entity type
* (e.g. 'file', 'product', 'domain').
*/
relation?: string;
/**
* The json_name of the parent entity the relation was being
* attached to (e.g. 'variation', 'job').
*/
parentType?: string;
/**
* The id the client supplied for the missing / disallowed entity.
* May be a numeric id as a number, or a string id depending on
* the model.
*/
id?: number | string;
/**
* The form-data index the client sent the entity under (i.e. the
* ``N`` in ``parent-N-id``).
*/
index?: number;
/**
* The fields the client sent for the missing entity, with noisy
* keys (signed URLs, their expiries) stripped and long string
* values truncated. Useful when debugging uploads where the
* client knows the uploadId but the server has no matching row.
*/
provided?: { [key: string]: any };
// Backend may add new keys over time; keep the type permissive.
[key: string]: any;
}
export class ApiError extends Error {
public statusCode?: number;
public errorCode?: ErrorType;
public errorMessage: string;
/**
* Structured context the API may include alongside the error
* message (e.g. the missing entity id and payload echo). Present
* only when the backend chooses to populate it - most error
* responses still carry just ``statusCode``, ``errorCode`` and
* ``message``.
*/
public details?: ApiErrorDetails;
public original: any;
public constructor(err: any) {
const message = JSON.stringify(err);
/* istanbul ignore next */
super(message);
this.statusCode = err.statusCode;
this.errorCode = getErrorFromCode(err.errorCode);
this.errorMessage = err.message ?
err.message : 'No error message';
this.details = err.details;
this.name = 'ApiError';
this.original = err;
}
}
export const version = 'v6';
interface UriComponents {
baseUrl: string;
version: string;
}
function ensureVersionInUri(baseUrl: string): UriComponents {
// Ensure baseUrl ends with a slash
let uri = baseUrl;
Iif (!uri.endsWith('/')) {
uri += '/';
}
// Check if the URI already includes a version
const versionPattern = new RegExp('/v\\d+/?$');
const match = uri.match(versionPattern);
Iif (match) {
// Extract the version and remove it from the URI
const foundVersion = match[0].replace(/\//g, '');
const strippedUri = uri.replace(versionPattern, '/');
return {
baseUrl: strippedUri,
version: foundVersion
};
}
return {
baseUrl: uri,
version: version
};
}
export function backendFetch(baseUrl: string, resource: string, options?: RequestOptions) {
const { baseUrl: cleanBaseUrl, version: apiVersion } = ensureVersionInUri(baseUrl);
// Remove leading slash from resource if it exists to prevent double slashes
const cleanResource = resource.startsWith('/') ? resource.slice(1) : resource;
const url = new URL(cleanBaseUrl + apiVersion + '/' + cleanResource);
Iif (options && options.query) {
for (const entry of options.query) {
url.searchParams.append(entry[0], entry[1]);
}
}
return fetch(url.toString(), options);
}
export function apiFetch(
baseUrl: string,
resource: string,
options?: RequestOptions,
expectEmptyResponse?: boolean
) {
return backendFetch(baseUrl, resource, options as RequestInit | undefined).then(
function (response) {
if (response.status < 200 || response.status > 299) {
return response.json().then(function (json) {
const err = new ApiError(json);
return Promise.reject(err);
});
} else {
return expectEmptyResponse ? '' : response.json();
}
}
);
}
/* istanbul ignore next */
export function apiFetchWithProgress(
baseUrl: string,
resource: string,
options?: RequestOptions,
progressCallback?: (progress: number) => void
) {
return backendFetch(baseUrl, resource, options as RequestInit | undefined).then(
function (response) {
if (!response.body) {
const err = new ApiError('empty response');
return Promise.reject(err);
}
const reader = response.body.getReader();
let bodyText = '';
let errorText = '';
let haveError = false;
const expected = '{"loadingBar": "' + '.'.repeat(100) + '"}';
function readChunk(): any {
return reader.read().then(({done, value}) => {
if (done) {
if (response.status < 200 || response.status > 299) {
const err = new ApiError('Unknown error');
return Promise.reject(err);
} else if(haveError) {
try {
const jsonText = JSON.parse(errorText);
return Promise.reject(new ApiError(jsonText));
} catch (e) {
return Promise.reject(new ApiError(errorText));
}
} else {
return bodyText;
}
} else {
const chunk = new TextDecoder().decode(value);
if (haveError) {
errorText += chunk;
} else {
for (let i = 0; i < chunk.length; ++i) {
const char = chunk[i];
const expectedChar = expected[bodyText.length];
if (expectedChar == char && !haveError) {
bodyText += char;
} else {
haveError = true;
errorText += char;
}
}
}
if (!haveError && progressCallback) {
const progress = Math.min(Math.max(0, bodyText.length - 16), 100);
progressCallback(progress);
}
return readChunk();
}
});
}
return readChunk();
}
);
}
|