import RNFS from 'react-native-fs'; import { PluginDownloadFolder } from '@utils/constants/download'; import { newer } from '@utils/compareVersion'; // packages for plugins import { load } from 'cheerio'; import dayjs from 'dayjs'; import qs from 'qs'; import { NovelStatus, Plugin, PluginItem } from './types'; import { FilterTypes } from './types/filterTypes'; import { isUrlAbsolute } from './helpers/isAbsoluteUrl'; import { fetchApi, fetchFile, fetchProto, fetchText } from './helpers/fetch'; import { defaultCover } from './helpers/constants'; import { encode, decode } from 'urlencode'; import { Parser } from 'htmlparser2'; import TextFile from '@native/TextFile'; const pluginsFilePath = PluginDownloadFolder + '/plugins.json'; const packages: Record = { 'htmlparser2': { Parser }, 'cheerio': { load }, 'dayjs': dayjs, 'qs': qs, 'urlencode': { encode, decode }, '@libs/novelStatus': { NovelStatus }, '@libs/fetch': { fetchApi, fetchFile, fetchText, fetchProto }, '@libs/isAbsoluteUrl': { isUrlAbsolute }, '@libs/filterInputs': { FilterTypes }, '@libs/defaultCover': { defaultCover }, }; const _require = (packageName: string) => { return packages[packageName]; }; const initPlugin = (rawCode: string) => { try { /* eslint no-new-func: "off", curly: "error" */ const plugin: Plugin = Function( 'require', 'module', `const exports = module.exports = {}; ${rawCode}; return exports.default`, )(_require, {}); return plugin; } catch (e) { return undefined; } }; const plugins: Record = {}; let serializedPlugins: Record; const serializePlugin = async ( pluginId: string, rawCode: string, installed: boolean, ) => { if (!serializedPlugins) { if (await RNFS.exists(pluginsFilePath)) { const content = await TextFile.readFile(pluginsFilePath); serializedPlugins = JSON.parse(content); } else { serializedPlugins = {}; } } if (installed) { serializedPlugins[pluginId] = rawCode; } else { serializedPlugins[pluginId] = undefined; } if (!(await RNFS.exists(PluginDownloadFolder))) { await RNFS.mkdir(PluginDownloadFolder); } await TextFile.writeFile(pluginsFilePath, JSON.stringify(serializedPlugins)); }; const deserializePlugins = () => { return TextFile.readFile(pluginsFilePath) .then(content => { serializedPlugins = JSON.parse(content); for (const script of Object.values(serializedPlugins)) { const plugin = initPlugin(script as string); if (plugin) { plugins[plugin.id] = plugin; } } }) .catch(() => { // nothing to read }); }; const installPlugin = async (url: string): Promise => { try { return await fetch(url, { headers: { 'pragma': 'no-cache', 'cache-control': 'no-cache' }, }) .then(res => res.text()) .then(async rawCode => { const plugin = initPlugin(rawCode); if (!plugin) { return undefined; } let currentPlugin = plugins[plugin.id]; if (!currentPlugin || newer(plugin.version, currentPlugin.version)) { plugins[plugin.id] = plugin; currentPlugin = plugin; await serializePlugin(plugin.id, rawCode, true); } return currentPlugin; }); } catch (e: any) { throw e; } }; const uninstallPlugin = async (_plugin: PluginItem) => { plugins[_plugin.id] = undefined; return serializePlugin(_plugin.id, '', false); }; const updatePlugin = async (plugin: PluginItem) => { return installPlugin(plugin.url); }; const fetchPlugins = (): Promise => { // plugins host const githubUsername = 'LNReader'; const githubRepository = 'lnreader-sources'; const pluginsTag = 'v2.1.0'; return fetch( `https://raw.githubusercontent.com/${githubUsername}/${githubRepository}/plugins/${pluginsTag}/.dist/plugins.min.json`, ).then(res => res.json()); }; const getPlugin = (pluginId: string) => plugins[pluginId]; const LOCAL_PLUGIN_ID = 'local'; export { getPlugin, installPlugin, uninstallPlugin, updatePlugin, deserializePlugins, fetchPlugins, LOCAL_PLUGIN_ID, };