const fs = require('fs-extra'); const { shim } = require('lib/shim.js'); const { GeolocationNode } = require('lib/geolocation-node.js'); const { FileApiDriverLocal } = require('lib/file-api-driver-local.js'); const { time } = require('lib/time-utils.js'); const { setLocale, defaultLocale, closestSupportedLocale } = require('lib/locale.js'); function fetchRequestCanBeRetried(error) { if (!error) return false; // Unfortunately the error 'Network request failed' doesn't have a type // or error code, so hopefully that message won't change and is not localized if (error.message == 'Network request failed') return true; // request to https://public-ch3302....1fab24cb1bd5f.md failed, reason: socket hang up" if (error.code == 'ECONNRESET') return true; // OneDrive (or Node?) sometimes sends back a "not found" error for resources // that definitely exist and in this case repeating the request works. // Error is: // request to https://graph.microsoft.com/v1.0/drive/special/approot failed, reason: getaddrinfo ENOTFOUND graph.microsoft.com graph.microsoft.com:443 if (error.code == 'ENOTFOUND') return true; // network timeout at: https://public-ch3302...859f9b0e3ab.md if (error.message && error.message.indexOf('network timeout') === 0) return true; // name: 'FetchError', // message: 'request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443', // type: 'system', // errno: 'EAI_AGAIN', // code: 'EAI_AGAIN' } } reason: { FetchError: request to https://api.ipify.org/?format=json failed, reason: getaddrinfo EAI_AGAIN api.ipify.org:443 // // It's a Microsoft error: "A temporary failure in name resolution occurred." if (error.code == 'EAI_AGAIN') return true; return false; } function shimInit() { shim.fs = fs; shim.FileApiDriverLocal = FileApiDriverLocal; shim.Geolocation = GeolocationNode; shim.FormData = require('form-data'); shim.detectAndSetLocale = function (Setting) { let locale = process.env.LANG; if (!locale) locale = defaultLocale(); locale = locale.split('.'); locale = locale[0]; locale = closestSupportedLocale(locale); Setting.setValue('locale', locale); setLocale(locale); return locale; } const nodeFetch = require('node-fetch'); shim.fetch = async function(url, options = null) { if (!options) options = {}; if (!options.timeout) options.timeout = 1000 * 120; // ms if (!('maxRetry' in options)) options.maxRetry = 5; let retryCount = 0; while (true) { try { const response = await nodeFetch(url, options); return response; } catch (error) { if (fetchRequestCanBeRetried(error)) { retryCount++; if (retryCount > options.maxRetry) throw error; await time.sleep(retryCount * 3); } else { throw error; } } } } shim.fetchBlob = async function(url, options) { if (!options || !options.path) throw new Error('fetchBlob: target file path is missing'); if (!options.method) options.method = 'GET'; if (!('maxRetry' in options)) options.maxRetry = 5; const urlParse = require('url').parse; url = urlParse(url.trim()); const http = url.protocol.toLowerCase() == 'http:' ? require('follow-redirects').http : require('follow-redirects').https; const headers = options.headers ? options.headers : {}; const method = options.method ? options.method : 'GET'; if (method != 'GET') throw new Error('Only GET is supported'); const filePath = options.path; function makeResponse(response) { return { ok: response.statusCode < 400, path: filePath, text: () => { return response.statusMessage; }, json: () => { return { message: response.statusCode + ': ' + response.statusMessage }; }, status: response.statusCode, headers: response.headers, }; } const requestOptions = { protocol: url.protocol, host: url.host, port: url.port, method: method, path: url.path + (url.query ? '?' + url.query : ''), headers: headers, }; const doFetchOperation = async () => { return new Promise((resolve, reject) => { try { // Note: relative paths aren't supported const file = fs.createWriteStream(filePath); const request = http.get(requestOptions, function(response) { response.pipe(file); file.on('finish', function() { file.close(() => { resolve(makeResponse(response)); }); }); }) request.on('error', function(error) { fs.unlink(filePath); reject(error); }); } catch(error) { fs.unlink(filePath); reject(error); } }); }; let retryCount = 0; while (true) { try { const response = await doFetchOperation(); return response; } catch (error) { if (fetchRequestCanBeRetried(error)) { retryCount++; if (retryCount > options.maxRetry) throw error; await time.sleep(retryCount * 3); } else { throw error; } } } } } module.exports = { shimInit };