import { Linking, Platform } from 'react-native'; import injectedJs from './injectedJs'; import PoorManIntervals from '@joplin/lib/PoorManIntervals'; import makeShowMessageBox from '../makeShowMessageBox'; import { Buffer } from 'buffer'; import { basename, fileExtension } from '@joplin/utils/path'; import uuid from '@joplin/lib/uuid'; import * as mimeUtils from '@joplin/lib/mime-utils'; import Resource from '@joplin/lib/models/Resource'; import { getLocales } from 'react-native-localize'; import type Setting from '@joplin/lib/models/Setting'; import shim from '@joplin/lib/shim'; import { closestSupportedLocale, defaultLocale, setLocale } from '@joplin/lib/locale'; const shimInitShared = () => { shim.sjclModule = require('@joplin/lib/vendor/sjcl-rn.js'); shim.stringByteLength = function(string) { return Buffer.byteLength(string, 'utf-8'); }; shim.Buffer = Buffer; shim.stringByteLength = function(string) { return Buffer.byteLength(string, 'utf-8'); }; shim.httpAgent = () => null; shim.fetch = async function(url, options = null) { // The native fetch() throws an uncatchable error that crashes the // app if calling it with an invalid URL such as '//.resource' or // "http://ocloud. de" so detect if the URL is valid beforehand and // throw a catchable error. Bug: // https://github.com/facebook/react-native/issues/7436 let validatedUrl = ''; try { // Check if the url is valid validatedUrl = new URL(url).href; } catch (error) { // If the url is not valid, a TypeError will be thrown throw new Error(`Not a valid URL: ${url}`); } return shim.fetchWithRetry(() => { // If the request has a body and it's not a GET call, and it // doesn't have a Content-Type header we display a warning, // because it could trigger a "Network request failed" error. // https://github.com/facebook/react-native/issues/30176 if (options?.body && options?.method && options.method !== 'GET' && !options?.headers?.['Content-Type']) { console.warn('Done a non-GET fetch call without a Content-Type header. It may make the request fail.', url, options); } // Among React Native `fetch()` many bugs, one of them is that // it will truncate strings when they contain binary data. // Browser fetch() or Node fetch() work fine but as always RN's // one doesn't. There's no obvious way to fix this so we'll // have to wait if it's eventually fixed upstream. See here for // more info: // https://github.com/laurent22/joplin/issues/3986#issuecomment-718019688 return fetch(validatedUrl, options); }, options); }; shim.openUrl = url => { return Linking.openURL(url); }; shim.showMessageBox = makeShowMessageBox(null); shim.waitForFrame = () => { return new Promise((resolve) => { requestAnimationFrame(() => { resolve(); }); }); }; shim.mobilePlatform = () => { return Platform.OS; }; shim.injectedJs = function(name) { if (!(name in injectedJs)) throw new Error(`Cannot find injectedJs file (add it to "injectedJs" object): ${name}`); return injectedJs[name as keyof typeof injectedJs]; }; shim.setTimeout = (fn, interval) => { return PoorManIntervals.setTimeout(fn, interval); }; shim.setInterval = (fn, interval) => { return PoorManIntervals.setInterval(fn, interval); }; shim.clearTimeout = (id) => { return PoorManIntervals.clearTimeout(id); }; shim.clearInterval = (id) => { return PoorManIntervals.clearInterval(id); }; // NOTE: This is a limited version of createResourceFromPath - unlike the Node version, it // only really works with images. It does not resize the image either. shim.createResourceFromPath = async function(filePath, defaultProps = undefined) { defaultProps = defaultProps ? defaultProps : {}; const resourceId = defaultProps.id ? defaultProps.id : uuid.create(); const ext = fileExtension(filePath); let mimeType = mimeUtils.fromFileExtension(ext); if (!mimeType) mimeType = 'image/jpeg'; let resource = Resource.new(); resource.id = resourceId; resource.mime = mimeType; resource.title = basename(filePath); resource.file_extension = ext; const targetPath = Resource.fullPath(resource); await shim.fsDriver().copy(filePath, targetPath); if (defaultProps) { resource = { ...resource, ...defaultProps }; } const itDoes = await shim.fsDriver().waitTillExists(targetPath); if (!itDoes) throw new Error(`Resource file was not created: ${targetPath}`); const fileStat = await shim.fsDriver().stat(targetPath); resource.size = fileStat.size; resource = await Resource.save(resource, { isNew: true }); return resource; }; shim.detectAndSetLocale = (settings: typeof Setting) => { // [ // { // "countryCode": "US", // "isRTL": false, // "languageCode": "fr", // "languageTag": "fr-US" // }, // { // "countryCode": "US", // "isRTL": false, // "languageCode": "en", // "languageTag": "en-US" // } // ] const locales = getLocales(); let locale = locales.length ? locales[0].languageTag : defaultLocale(); locale = closestSupportedLocale(locale); settings.setValue('locale', locale); setLocale(locale); return locale; }; }; export default shimInitShared;