Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / Node.js

How to Parse External Classes and js in react-native as a Package

0.00/5 (No votes)
5 Sep 2022CPOL2 min read 4.8K  
Generate and execute external full js code in react-native and nodejs

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.

JavaScript
// let's say we have the following class that we want to be an external class.
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.

JavaScript
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.

JavaScript
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))
       {
           // delete if it already exist
           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.

JavaScript
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{
     // this URL could also be a public github url, that can contain the package
     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){

     // when the app is offline or maybe the myserver is offline,
     // then load the existing parsers
     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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)