2020-11-07 17:59:37 +02:00
|
|
|
const shim = require('@joplin/lib/shim').default;
|
2020-11-05 18:58:23 +02:00
|
|
|
const { GeolocationReact } = require('./geolocation-react.js');
|
2020-11-07 17:59:37 +02:00
|
|
|
const PoorManIntervals = require('@joplin/lib/PoorManIntervals').default;
|
2018-10-07 19:55:49 +02:00
|
|
|
const RNFetchBlob = require('rn-fetch-blob').default;
|
2019-07-29 15:43:53 +02:00
|
|
|
const { generateSecureRandom } = require('react-native-securerandom');
|
2021-01-27 19:42:58 +02:00
|
|
|
const FsDriverRN = require('./fs-driver-rn').default;
|
2018-01-30 21:01:07 +02:00
|
|
|
const urlValidator = require('valid-url');
|
2018-02-15 20:33:08 +02:00
|
|
|
const { Buffer } = require('buffer');
|
2019-09-17 22:32:00 +02:00
|
|
|
const { Linking, Platform } = require('react-native');
|
2020-11-07 17:59:37 +02:00
|
|
|
const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
|
|
|
|
const { basename, fileExtension } = require('@joplin/lib/path-utils');
|
|
|
|
const uuid = require('@joplin/lib/uuid').default;
|
2021-01-22 19:41:11 +02:00
|
|
|
const Resource = require('@joplin/lib/models/Resource').default;
|
2017-07-10 20:09:58 +02:00
|
|
|
|
2019-03-03 01:22:41 +02:00
|
|
|
const injectedJs = {
|
2020-11-07 17:59:37 +02:00
|
|
|
webviewLib: require('@joplin/lib/rnInjectedJs/webviewLib'),
|
2021-07-13 20:13:13 +02:00
|
|
|
codeMirrorBundle: require('../lib/rnInjectedJs/CodeMirror.bundle'),
|
2019-03-08 19:14:17 +02:00
|
|
|
};
|
|
|
|
|
2017-07-10 20:09:58 +02:00
|
|
|
function shimInit() {
|
|
|
|
shim.Geolocation = GeolocationReact;
|
2020-11-07 17:59:37 +02:00
|
|
|
shim.sjclModule = require('@joplin/lib/vendor/sjcl-rn.js');
|
2017-10-19 00:13:53 +02:00
|
|
|
|
2018-01-21 19:01:37 +02:00
|
|
|
shim.fsDriver = () => {
|
|
|
|
if (!shim.fsDriver_) shim.fsDriver_ = new FsDriverRN();
|
|
|
|
return shim.fsDriver_;
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2018-01-21 19:01:37 +02:00
|
|
|
|
2020-05-21 10:14:33 +02:00
|
|
|
shim.randomBytes = async count => {
|
2017-12-12 19:51:07 +02:00
|
|
|
const randomBytes = await generateSecureRandom(count);
|
2020-03-14 01:46:14 +02:00
|
|
|
const temp = [];
|
|
|
|
for (const n in randomBytes) {
|
2017-12-12 19:51:07 +02:00
|
|
|
if (!randomBytes.hasOwnProperty(n)) continue;
|
|
|
|
temp.push(randomBytes[n]);
|
|
|
|
}
|
|
|
|
return temp;
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2017-12-12 19:51:07 +02:00
|
|
|
|
2022-04-15 13:04:40 +02:00
|
|
|
// This function can be used to debug "Network Request Failed" errors. It
|
|
|
|
// uses the native XMLHttpRequest which is more likely to get the proper
|
|
|
|
// response and error message.
|
|
|
|
|
|
|
|
shim.debugFetch = async (url, options = null) => {
|
|
|
|
options = {
|
|
|
|
method: 'GET',
|
|
|
|
headers: {},
|
|
|
|
...options,
|
|
|
|
};
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.open(options.method, url, true);
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(options.headers)) {
|
|
|
|
xhr.setRequestHeader(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
xhr.onload = function() {
|
|
|
|
console.info('======================== XHR RESPONSE');
|
|
|
|
console.info(xhr.getAllResponseHeaders());
|
|
|
|
console.info('-------------------------------------');
|
|
|
|
// console.info(xhr.responseText);
|
|
|
|
console.info('======================== XHR RESPONSE');
|
|
|
|
|
|
|
|
resolve(xhr.responseText);
|
|
|
|
};
|
|
|
|
|
|
|
|
xhr.onerror = function() {
|
|
|
|
console.info('======================== XHR ERROR');
|
|
|
|
console.info(xhr.getAllResponseHeaders());
|
|
|
|
console.info('-------------------------------------');
|
|
|
|
console.info(xhr.responseText);
|
|
|
|
console.info('======================== XHR ERROR');
|
|
|
|
|
|
|
|
reject(new Error(xhr.responseText));
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: Send POST data here if needed
|
|
|
|
xhr.send();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-11-13 00:52:54 +02:00
|
|
|
shim.fetch = async function(url, options = null) {
|
2020-10-28 17:47:36 +02:00
|
|
|
// 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
|
2018-01-30 21:01:07 +02:00
|
|
|
const validatedUrl = urlValidator.isUri(url);
|
2019-09-19 23:51:18 +02:00
|
|
|
if (!validatedUrl) throw new Error(`Not a valid URL: ${url}`);
|
2018-01-25 21:01:14 +02:00
|
|
|
|
2018-01-30 21:01:07 +02:00
|
|
|
return shim.fetchWithRetry(() => {
|
2020-10-28 17:47:36 +02:00
|
|
|
// 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.
|
2020-10-16 17:26:19 +02:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2020-10-28 17:47:36 +02:00
|
|
|
// 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
|
|
|
|
|
2018-01-30 21:01:07 +02:00
|
|
|
return fetch(validatedUrl, options);
|
2017-11-13 00:52:54 +02:00
|
|
|
}, options);
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2017-11-13 00:52:54 +02:00
|
|
|
|
2017-07-10 20:09:58 +02:00
|
|
|
shim.fetchBlob = async function(url, options) {
|
|
|
|
if (!options || !options.path) throw new Error('fetchBlob: target file path is missing');
|
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const headers = options.headers ? options.headers : {};
|
|
|
|
const method = options.method ? options.method : 'GET';
|
2018-02-04 19:12:24 +02:00
|
|
|
const overwrite = 'overwrite' in options ? options.overwrite : true;
|
2017-07-10 20:09:58 +02:00
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const dirs = RNFetchBlob.fs.dirs;
|
2017-07-10 20:09:58 +02:00
|
|
|
let localFilePath = options.path;
|
2019-09-19 23:51:18 +02:00
|
|
|
if (localFilePath.indexOf('/') !== 0) localFilePath = `${dirs.DocumentDir}/${localFilePath}`;
|
2017-07-10 20:09:58 +02:00
|
|
|
|
2018-02-04 19:12:24 +02:00
|
|
|
if (!overwrite) {
|
|
|
|
if (await shim.fsDriver().exists(localFilePath)) {
|
|
|
|
return { ok: true };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-10 20:09:58 +02:00
|
|
|
delete options.path;
|
2018-02-04 19:12:24 +02:00
|
|
|
delete options.overwrite;
|
2017-07-10 20:09:58 +02:00
|
|
|
|
2017-11-13 00:52:54 +02:00
|
|
|
const doFetchBlob = () => {
|
|
|
|
return RNFetchBlob.config({
|
2019-07-29 15:43:53 +02:00
|
|
|
path: localFilePath,
|
2021-04-25 10:50:52 +02:00
|
|
|
trusty: options.ignoreTlsErrors,
|
2017-07-10 20:09:58 +02:00
|
|
|
}).fetch(method, url, headers);
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2017-11-13 00:52:54 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
const response = await shim.fetchWithRetry(doFetchBlob, options);
|
2019-07-29 15:43:53 +02:00
|
|
|
|
2017-07-10 20:09:58 +02:00
|
|
|
// Returns an object that's roughtly compatible with a standard Response object
|
2020-03-14 01:46:14 +02:00
|
|
|
const output = {
|
2017-07-10 20:09:58 +02:00
|
|
|
ok: response.respInfo.status < 400,
|
|
|
|
path: response.data,
|
|
|
|
status: response.respInfo.status,
|
|
|
|
headers: response.respInfo.headers,
|
2020-05-09 12:52:09 +02:00
|
|
|
// If response type is 'path' then calling text() or json() (or base64())
|
|
|
|
// on RNFetchBlob response object will make it read the file on the native thread,
|
|
|
|
// serialize it, and send over the RN bridge.
|
|
|
|
// For larger files this can cause the app to crash.
|
|
|
|
// For these type of responses we're not using the response text anyway
|
|
|
|
// so can override it here to return empty values
|
|
|
|
text: response.type === 'path' ? () => '' : response.text,
|
|
|
|
json: response.type === 'path' ? () => {} : response.json,
|
2017-07-10 20:09:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
return output;
|
|
|
|
} catch (error) {
|
2019-09-19 23:51:18 +02:00
|
|
|
throw new Error(`fetchBlob: ${method} ${url}: ${error.toString()}`);
|
2017-07-10 20:09:58 +02:00
|
|
|
}
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2017-07-26 20:36:16 +02:00
|
|
|
|
2017-08-01 23:40:14 +02:00
|
|
|
shim.uploadBlob = async function(url, options) {
|
|
|
|
if (!options || !options.path) throw new Error('uploadBlob: source file path is missing');
|
|
|
|
|
|
|
|
const headers = options.headers ? options.headers : {};
|
|
|
|
const method = options.method ? options.method : 'POST';
|
|
|
|
|
|
|
|
try {
|
2021-04-25 10:50:52 +02:00
|
|
|
const response = await RNFetchBlob.config({
|
|
|
|
trusty: options.ignoreTlsErrors,
|
|
|
|
}).fetch(method, url, headers, RNFetchBlob.wrap(options.path));
|
2017-08-01 23:40:14 +02:00
|
|
|
|
|
|
|
// Returns an object that's roughtly compatible with a standard Response object
|
|
|
|
return {
|
|
|
|
ok: response.respInfo.status < 400,
|
|
|
|
data: response.data,
|
|
|
|
text: response.text,
|
|
|
|
json: response.json,
|
|
|
|
status: response.respInfo.status,
|
|
|
|
headers: response.respInfo.headers,
|
|
|
|
};
|
|
|
|
} catch (error) {
|
2019-09-19 23:51:18 +02:00
|
|
|
throw new Error(`uploadBlob: ${method} ${url}: ${error.toString()}`);
|
2017-08-01 23:40:14 +02:00
|
|
|
}
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2017-08-01 23:40:14 +02:00
|
|
|
|
2017-07-26 20:36:16 +02:00
|
|
|
shim.readLocalFileBase64 = async function(path) {
|
2019-07-29 15:43:53 +02:00
|
|
|
return RNFetchBlob.fs.readFile(path, 'base64');
|
|
|
|
};
|
2018-02-15 20:33:08 +02:00
|
|
|
|
|
|
|
shim.stringByteLength = function(string) {
|
|
|
|
return Buffer.byteLength(string, 'utf-8');
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2018-03-24 21:35:10 +02:00
|
|
|
|
|
|
|
shim.Buffer = Buffer;
|
2018-03-27 01:55:44 +02:00
|
|
|
|
2020-05-21 10:14:33 +02:00
|
|
|
shim.openUrl = url => {
|
2018-03-27 01:55:44 +02:00
|
|
|
Linking.openURL(url);
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2018-06-25 19:14:57 +02:00
|
|
|
|
2020-02-27 02:14:40 +02:00
|
|
|
shim.httpAgent = () => {
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2018-06-25 19:14:57 +02:00
|
|
|
shim.waitForFrame = () => {
|
2019-09-13 00:16:42 +02:00
|
|
|
return new Promise(function(resolve) {
|
2019-07-29 15:43:53 +02:00
|
|
|
requestAnimationFrame(function() {
|
|
|
|
resolve();
|
|
|
|
});
|
2018-06-25 19:14:57 +02:00
|
|
|
});
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2019-02-05 20:11:03 +02:00
|
|
|
|
2019-09-17 22:32:00 +02:00
|
|
|
shim.mobilePlatform = () => {
|
|
|
|
return Platform.OS;
|
|
|
|
};
|
|
|
|
|
2020-01-24 22:56:44 +02:00
|
|
|
shim.appVersion = () => {
|
|
|
|
const p = require('react-native-version-info').default;
|
|
|
|
return p.appVersion;
|
|
|
|
};
|
|
|
|
|
2019-02-05 20:11:03 +02:00
|
|
|
// 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 = null) {
|
|
|
|
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;
|
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const targetPath = Resource.fullPath(resource);
|
2019-02-05 20:11:03 +02:00
|
|
|
await shim.fsDriver().copy(filePath, targetPath);
|
2019-07-29 15:43:53 +02:00
|
|
|
|
2019-02-05 20:11:03 +02:00
|
|
|
if (defaultProps) {
|
|
|
|
resource = Object.assign({}, resource, defaultProps);
|
|
|
|
}
|
|
|
|
|
2019-05-12 02:15:52 +02:00
|
|
|
const itDoes = await shim.fsDriver().waitTillExists(targetPath);
|
2019-09-19 23:51:18 +02:00
|
|
|
if (!itDoes) throw new Error(`Resource file was not created: ${targetPath}`);
|
2019-05-12 02:15:52 +02:00
|
|
|
|
2019-05-11 18:55:40 +02:00
|
|
|
const fileStat = await shim.fsDriver().stat(targetPath);
|
|
|
|
resource.size = fileStat.size;
|
2019-02-05 20:11:03 +02:00
|
|
|
|
2019-05-11 18:55:40 +02:00
|
|
|
resource = await Resource.save(resource, { isNew: true });
|
2019-02-05 20:11:03 +02:00
|
|
|
|
|
|
|
return resource;
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2019-02-05 20:11:03 +02:00
|
|
|
|
2019-03-03 01:22:41 +02:00
|
|
|
shim.injectedJs = function(name) {
|
2019-09-19 23:51:18 +02:00
|
|
|
if (!(name in injectedJs)) throw new Error(`Cannot find injectedJs file (add it to "injectedJs" object): ${name}`);
|
2019-03-08 19:14:17 +02:00
|
|
|
return injectedJs[name];
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2020-10-09 19:35:46 +02:00
|
|
|
|
|
|
|
shim.setTimeout = (fn, interval) => {
|
2020-10-16 17:26:19 +02:00
|
|
|
return PoorManIntervals.setTimeout(fn, interval);
|
2020-10-09 19:35:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
shim.setInterval = (fn, interval) => {
|
2020-10-16 17:26:19 +02:00
|
|
|
return PoorManIntervals.setInterval(fn, interval);
|
2020-10-09 19:35:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
shim.clearTimeout = (id) => {
|
2020-10-16 17:26:19 +02:00
|
|
|
return PoorManIntervals.clearTimeout(id);
|
2020-10-09 19:35:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
shim.clearInterval = (id) => {
|
2020-10-16 17:26:19 +02:00
|
|
|
return PoorManIntervals.clearInterval(id);
|
2020-10-09 19:35:46 +02:00
|
|
|
};
|
|
|
|
|
2017-07-10 20:09:58 +02:00
|
|
|
}
|
|
|
|
|
2019-07-29 15:43:53 +02:00
|
|
|
module.exports = { shimInit };
|