UNPKG

@loaders.gl/core

Version:

The core API for working with loaders.gl loaders and writers

138 lines (121 loc) 4.37 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {isResponse} from '../../javascript-utils/is-type'; import {FetchError} from '../fetch/fetch-error'; import {getResourceContentLength, getResourceUrl, getResourceMIMEType} from './resource-utils'; import {shortenUrlForDisplay} from './url-utils'; /** * Returns a Response object * Adds content-length header when possible * * @param resource */ export async function makeResponse(resource: unknown): Promise<Response> { if (isResponse(resource)) { return resource as Response; } // Add content-length header if possible const headers: {[header: string]: string} = {}; const contentLength = getResourceContentLength(resource); if (contentLength >= 0) { headers['content-length'] = String(contentLength); } // `new Response(File)` does not preserve content-type and URL // so we add them here const url = getResourceUrl(resource); const type = getResourceMIMEType(resource); if (type) { headers['content-type'] = type; } // Add a custom header with initial bytes if available const initialDataUrl = await getInitialDataUrl(resource); if (initialDataUrl) { headers['x-first-bytes'] = initialDataUrl; } // TODO - is this the best way of handling strings? // Maybe package as data URL instead? if (typeof resource === 'string') { // Convert to ArrayBuffer to avoid Response treating it as a URL resource = new TextEncoder().encode(resource); } // Attempt to create a Response from the resource, adding headers and setting url const response = new Response(resource as any, {headers}); // We can't control `Response.url` via constructor, use a property override to record URL. Object.defineProperty(response, 'url', {value: url}); return response; } /** * Checks response status (async) and throws a helpful error message if status is not OK. * @param response */ export async function checkResponse(response: Response): Promise<void> { if (!response.ok) { const error = await getResponseError(response); throw error; } } /** * Checks response status (sync) and throws a helpful error message if status is not OK. * @param response */ export function checkResponseSync(response: Response): void { if (!response.ok) { let message = `${response.status} ${response.statusText}`; message = message.length > 60 ? `${message.slice(0, 60)}...` : message; throw new Error(message); } } // HELPERS async function getResponseError(response: Response): Promise<Error> { const shortUrl = shortenUrlForDisplay(response.url); let message = `Failed to fetch resource (${response.status}) ${response.statusText}: ${shortUrl}`; message = message.length > 100 ? `${message.slice(0, 100)}...` : message; const info = { reason: response.statusText, url: response.url, response }; // See if we got an error message in the body try { const contentType = response.headers.get('Content-Type'); info.reason = !response.bodyUsed && contentType?.includes('application/json') ? await response.json() : await response.text(); } catch (error) { // eslint forbids return in a finally statement, so we just catch here } return new FetchError(message, info); } async function getInitialDataUrl( resource: string | Blob | ArrayBuffer | unknown ): Promise<string | null> { const INITIAL_DATA_LENGTH = 5; if (typeof resource === 'string') { return `data:,${resource.slice(0, INITIAL_DATA_LENGTH)}`; } if (resource instanceof Blob) { const blobSlice = resource.slice(0, 5); return await new Promise((resolve) => { const reader = new FileReader(); reader.onload = (event) => resolve(event?.target?.result as string); reader.readAsDataURL(blobSlice); }); } if (resource instanceof ArrayBuffer) { const slice = resource.slice(0, INITIAL_DATA_LENGTH); const base64 = arrayBufferToBase64(slice); return `data:base64,${base64}`; } return null; } // https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string function arrayBufferToBase64(buffer: ArrayBuffer): string { let binary = ''; const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); }