/** * @typedef {Object} MarkerData * @property {Blob} background - фон * @property {Array} images - изображения * @property {Array} slider - слайды */ /** Путь к php-прослойке, работающая с бд и внутренним хранилищем */ const db_get_filepath = '../db_get.php' /** Дефолтный срок годности кэша : 2 часа */ const CACHE_DEFAULT_TTL_MS = 2 * 60 * 60 * 1000; /** * Излечь данные из ответа * @param {string} type - тип извлекаемых данных * @param {Response} response - ответ * @returns {Promise} */ const extract_file = recovery(async (type, response) => { switch(type) { case 'file': return await response.blob() case 'json': return await response.json() default: throw new Error('Не определен type') } }, (...e) => {throw Error(`Ошибки при извлечении (extract_file): ${e.join(', ')}`)} ) /** * @param {string} path - путь (либо локальный или https) * @param {object} options - параметры fetch * @param {int} timeout - секундомер */ const fetch_with_timeout = recovery(async (path, options = {}, timeout = 10 * 1000) => { /** Штука для преждевременного завершения fetch */ const controller = new AbortController() /** Как я понял, якорь для fetch */ const signal = controller.signal /** Сработает завершение после истечения timeout */ const timeoutId = setTimeout(() => controller.abort(), timeout) /** Запрос с привязкой якоря */ const response = await fetch(path, {...options, signal}) try {return response} finally {clearTimeout(timeoutId)} }, (e) => { if(e.name === 'AbortError') console.log('Запрос прерван по таймауту:', e) else console.log(e) }, (...e) => {throw Error(`Ошибки при запросе с таймером (fetch_with_timeout): ${e.join(', ')}`)} ) /** * Вернуть данные из ответа, кэшируя перед этим * @param {string} type - тип извлекаемых данных * @param {string} path - путь (либо локальный или https) * @param {Object} body_req - тело запроса * @returns {Promise} */ const return_fetch = recovery(async (type, path, cache_key, body_req) => { const cache = await caches.open('cache') /** Запрос по пути (если есть тело, то локальное обращение к файлу) */ const response = !body_req ? await fetch_with_timeout(path) : await fetch_with_timeout(path, { method: 'POST', headers: { 'Content-Type': 'application/json'}, body: JSON.stringify(body_req) }) /** Клонирование, т.к blob/json необратимый процесс. */ const cloned = response.clone() const blob = await cloned.blob() const headers = new Headers(cloned.headers) /** Добавление даты создания в заголовок 'date' кешируемого ответа. */ headers.set('date', new Date().toUTCString()) /** Процесс кэширования */ await cache.put( cache_key, new Response(blob, { status: cloned.status, statusText: cloned.statusText, headers }) ) /** Возвращаем оригинальный (первый) ответ */ return await extract_file(type, response) }, (...e) => {throw Error(`Ошибки при запросе и кэшировании (return_fetch): ${e.join(', ')}`)} ) /** * Получить данные из пути(локальный или https) * @param {*} type - тип извлекаемых данных * @param {*} path - путь (либо локальный или https) * @param {*} body_req - тело запроса * @returns {Promise} */ const get = recovery(async (type, path, body_req = null) => { const cache = await caches.open('cache') /** Ключ для добавления или получения кэш-данных. */ const cache_key = `${path}?type=${type}&file=${encodeURIComponent(JSON.stringify(body_req || {}))}` /** Результат поиска ответа в кэше */ const cached_response = await cache.match(cache_key) if (cached_response) { /** Срок годности кэша (если задан) */ const expires_value = cached_response.headers.get('expires') /** Дата создания кэша */ const date_value = cached_response.headers.get('date') /** До какого числа годен */ const exp = expires_value && expires_value !== '-1' ? Date.parse(expires_value) : Date.parse(date_value ?? '') + CACHE_DEFAULT_TTL_MS /** Если exp больше, вернуть ответ из кэша, иначе удалить протухшие данные */ if (Date.now() < exp) { return await extract_file(type, cached_response ) } await cache.delete(cache_key) } /** Формирование нового кэша, т.к предыдущий протух или его нет */ return await return_fetch(type, path, cache_key, body_req) }, (...e) => {throw Error(`Ошибки при получении (get): ${e.join(', ')}`)} ) /** * Получить данные на диске (абсолютный/относительный путь) * @param {string} filepath - путь к файлу * @returns {Promise} - без json */ const get_local = recovery(async (filepath) => { return await get('file', db_get_filepath, { mode: 'file', path: filepath }) }, (...e) => {throw Error(`Ошибки при локальном получении (get_local): ${e.join(', ')}`)} ) /** * Получить данные без кэширования * @param {string} type - тип извлекаемых данных * @param {string} path - путь (либо локальный или https) * @returns {Promise} */ const get_no_cached = recovery(async (type, path) => { const file = await fetch_with_timeout(path) return await extract_file(type, file) }, (...e) => {throw Error(`Ошибки при получении без кэширования (get_no_cached): ${e.join(', ')}`)} ) /** * Получить метериалы поселения для инфо. страницы * @param {MarkerData} properties * - нужные свойства (пути) из свойств feature. * @returns {Promise<{ background: Blob, images: Blob[], slider: Blob[]}>} */ const get_for_info_page = recovery(async (properties) => { /** Фон инфо. страницы */ const background = await get_local(properties.background) /** Фотографи инфо. страницы */ const images = await Promise.all( (properties.images || []).map(i_path => get_local(i_path)) ) /** Слайды инфо. страницы (если есть) */ const slider = Array.isArray(properties.slider) ? await Promise.all(properties.slider.map(s_path => get_local(s_path))) : [] return { background, images, slider, } }, (...e) => {throw Error(`Ошибки при получении материалов для страницы (get_for_info_page): ${e.join(', ')}`)} ) /** * Получить иконки для стилизации страниц. * @returns {Promise} */ const get_icons = recovery(async () => await get_no_cached ('json', './data/icons.json') , (...e) => {throw Error(`Ошибки при получении данных для стилизации (get_icons): ${e.join(', ')}`)} ) /** * Получить все geojson'ы из базы данных * @returns {Promise} */ const get_all_geojsons = recovery(async () => await get('json', db_get_filepath, {'mode': 'geojsons'}) , (...e) => {throw Error(`Ошибки при получении geojson'ов из бд (get_all_geojsons): ${e.join(', ')}`)} )