Background
I have built an app that parses external data from the website, and if you know about this, then you will also know that sometimes changes to parser are constant and I wanted to avoid republishing my app as it takes time for users to get the changes.
So I thought about a way to deliver the update to users as fast as possible.
The problem is that the parser will contain external data and also contain function, etc.
And using Eval
or Function
, you would not be able to import
those external data and libraries.
I also wanted my data to be secure even when I get it from an external source.
In this tip, you will learn how to parse and develop code packages
in react-native
.
Using the Code
Libraries You Will Need
1- crypto-js
crypto-js will encrypt the js file content.
2- js-base64
js-base64 will convert the encrypted string to base64.
3- react-native-fs
react-native-fs is only needed when creating the package.
You will need to create two applications, your Android IOS or Windows app and a windows/linux application so that you could create a package.
Let us start by building a js file that will be in a folder called myparsers - let's call it parser.js.
Of course, the myparsers can contain more than one js file.
import userClass from '../myobjects'
import http from '../http'
import Parser from '../interfaces'
export default class UserParser implements Parser{
parserName: string;
displayName: string;
constructor(){
this.parserName = "userParser_1";
this.displayName = "User Parser";
}
async getUsers(){
const data = await http.fetchJSON("https://xxxx.com/myUsres");
return data.map(x=> new userClass(x.firstName + x.lastName, x.password, x.userName))
}
}
Now making a change in the above class will require republishing the whole app.
Now let's change this class to a js class so that we will be able to convert it to a package.
export default (http, userClass)=> {
function userParser(){
this.parserName = "userParser_1";
this.displayName = "User Parser";
}
userParser.prototype = {
getUsers: async function(){
const data = await http.fetchJSON("https://xxxx.com/myUsres");
return data.map(x=> new userClass(x.firstName + x.lastName,
x.password, x.userName))
}
}
return new userParser();
}
Now that we have converted the class to a parseable js code, we will have to build the package, and to create a package, we will have to use react-native-fs
to access the js files.
import RNFS from 'react-native-fs'
import cryptoJs from 'crypto-js';
import { Base64 } from 'js-base64';
export const enCryptString = async (key: string, str: string) => {
if (str.startsWith("#"))
return str;
str = cryptoJs.AES.encrypt(str, key).toString();
str = Base64.encode(str);
return "##" + str;
}
export const deCryptString = async (key: string, str: string) => {
if (!str.startsWith("#"))
return str;
str = str.substring(2);
str = Base64.decode(str);
var bytes = cryptoJs.AES.decrypt(str, key);
return bytes.toString(cryptoJs.enc.Utf8);
}
export class PackageCreator {
jsFilePath = "D:\\Projects\\myapp\\myparsers";
packagPath = "D:\\Projects\\myapp-public\\parsers-container\\1.0.0\\mypackage.pkg";
packageKey = "my.parser.key";
async getFiles(p?: string){
console.log("gettings the js files")
const files = await RNFS.readDir(p || this.jsFilePath);
const result = [];
const serach = ".js";
for (let path of files) {
if (path.isDirectory()) {
if (path.path.endsWith(".ts"))
continue;
(await this.getFiles(path.path)).forEach(x => result.push(x));
}
else if (path.name.endsWith(serach)) result.push(path.path));
}
return result;
}
async createPackage (){
const files = await = this.getFiles();
const contents = [];
for (const file in files){
const content = await RNFS.readFile(file, 'utf8')
const eContent = enCryptString
(this.packageKey, content.replace("export default", ""));
contents.push(eContent)
}
if (await RNFS.exists(this.packagPath))
{
await RNFS.unlink(this.packagPath);
}
await RNFS.writeFile(this.packagPath, contents.join("&"), 'utf8');
}
}
Now when executing the above createPackage
in your external application, a file with name mypackage.pkg that will contain all the js files you will need.
And to your react-native
app, you will need to fetch
and read the above created package and also parse it to a useable code.
So now, let us create a class and call it OnlineParsers
.
import cryptoJs from 'crypto-js';
import { Base64 } from 'js-base64';
import Parser from '../interfaces'
import UserParser from `../MyParsers`
import userClass from '../myobjects'
import http from '../http'
export const deCryptString = async (key: string, str: string) => {
if (!str.startsWith("#"))
return str;
str = str.substring(2);
str = Base64.decode(str);
var bytes = cryptoJs.AES.decrypt(str, key);
return bytes.toString(cryptoJs.enc.Utf8);
}
import default class OnlineParsers {
async getParser(){
try{
const uri = "https://myserver/1.0.0/mypackage.pkg"
const data = await fetch(uri);
const textCode = await data.text();
const parsers = [] as Parser[];
for(const code in textCode.split("&")){
const dCode =deCryptString("my.parser.key", dCode);
const fn = eval(dCode);
parsers.push(fn(http,userClass));
}
return parsers;
} catch(e){
return [UserParser(http,userClass)]
}
}
}
Now you may ask why use Eval
and not Function
, the reason is simple. For me, running external code with eval
is faster as it runs the code in the current thread.
Using Function
will make the external code run much much slower, as it runs in an external thread/context.
Points of Interest
A smart way to create external package code and run it in react-native
which will result in avoiding republishing your app when you know that the code will have to be in constant change.
Please let me know what you think about this, as I have found no way to execute external script in react-native
.
History
- 5th September, 2022: Initial version