Customizing code at build-time in React Native with Create React Native App and Expo - react-native

Generally, is there a way to customize the app code during build step?
Specifically, there is the typical need to make requests from the application to the local backend ([1], [2], [3], [4]).
localhost doesn't work since the server and the app are on different hosts (when using Android emulator or an actual physical device).
The actual IP address of the host in the same network works but in a multi-developer project it's a hassle for everyone to constantly change the constant IP in the code to that of their development machine.
With Webpack a case like that could be solved with DefinePlugin replacing a placeholder with the IP address of the machine the build is happening on.

We ended up using somewhat hacky approach inspired by transformers like react-native-typescript-transformer or react-native-sass-transformer. It's idea is pretty much equivalent to the mentioned DefinePlugin of Webpack.
First, some transformer files in the project directory (you can name them however you like, just update the references):
configBuildReplacements.js
// whatever logic you need
module.exports = {
API_HOST_PLACEHOLDER: `http://${getLocalNetworkAddress()}:3000`,
SOME_OTHER_DYNAMIC_VALUE: someFun(),
}
configBuildReplaceTransformer.js
const semver = require('semver')
let upstreamTransformer = null
const reactNativeVersionString = require('react-native/package.json').version
const reactNativeMinorVersion = semver(reactNativeVersionString).minor
if (reactNativeMinorVersion >= 56) {
upstreamTransformer = require('metro/src/reactNativeTransformer')
}
else if (reactNativeMinorVersion >= 52) {
upstreamTransformer = require('metro/src/transformer')
}
else if (reactNativeMinorVersion >= 47) {
upstreamTransformer = require('metro-bundler/src/transformer')
}
else if (reactNativeMinorVersion === 46) {
upstreamTransformer = require('metro-bundler/build/transformer')
}
else {
// handle RN <= 0.45
const oldUpstreamTransformer = require('react-native/packager/transformer')
upstreamTransformer = {
transform({ src, filename, options }) {
return oldUpstreamTransformer.transform(src, filename, options)
},
}
}
module.exports.transform = function (src, filename, options) {
// handle RN >= 0.46
if (typeof src === 'object') {
({ src, filename, options } = src)
}
const replacements = require('./configBuildReplacements')
const modifiedSrc = Object.keys(replacements).reduce(
(src, replacementKey) => src.replace(
new RegExp(replacementKey, 'g'),
replacements[replacementKey],
),
src,
)
return upstreamTransformer.transform({
src: modifiedSrc,
filename,
options,
})
}
The exported transform function uses the exported object from the previous file configBuildReplacements.js as a dictionary to replace key substrings with value substrings in the source code before handing this code to the default (upstream) transformer.
And to connect this new transformer to the project:
with Expo, add the transformer packager option to app.json:
{
"expo": {
"packagerOpts": {
"transformer": "configBuildReplaceTransformer.js"
}
}
}
without Expo, add getTransformModulePath() to rn-cli.config.js (which is the default path to the optional config file for React Native CLI, which is extremely poorly documented at the moment of this writing):
module.exports = {
getTransformModulePath() {
return require.resolve('./configBuildReplaceTransformer')
},
}
After this is done, just like with DefinePlugin, code like
get('API_HOST_PLACEHOLDER/info')
will become something like
get('http://192.168.42.23:3000/info')

Related

Shopify Storage Redis Issue with Node React App

I have added session storage in serve.js as follows :-
import SessionHandler from "./SessionHandler";
const sessionStorage = new SessionHandler();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
API_VERSION: ApiVersion.October21,
IS_EMBEDDED_APP: false,
// This should be replaced with your preferred storage strategy
//SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
sessionStorage.storeCallback,
sessionStorage.loadCallback,
sessionStorage.deleteCallback
),
});
My router get function is
router.get("(.*)", async (ctx) => {
const shop = ctx.query.shop;
let documentQuery = { shop: shop };
let data = await SessionStorage.findOne(documentQuery); //this finds the store in the session table
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
if (data == null) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
} else {
await handleRequest(ctx);
}
});
and than in the SessionHandler file added code as attached in file ,
but when I run install the app it goes to the storeCallback , loadcallback and deletecallback function multiple times
StoreCallback Function Code
Load and delete callback function code
sorry I have edited my answer as I think its incorrect . all I can say for now is to look at this example:https://github.com/Shopify/shopify-api-node/blob/main/docs/usage/customsessions.md
if you havent already..

How we can use facebook AppEventsLogger add to cart event in react native

I am trying to implement facebook SDK to track Add to cart event in my application but it was not working here is my code
First, I import facebook SDK in my application react-native-fbsdk which was installed successfully and login function are working well.
Now in product screen, I import facebook AppEventsLogger and AppEventsConstants from SDK
const FBSDK = require('react-native-fbsdk');
const {
AppEventsLogger,
AppEventsConstants
} = FBSDK;
And when user add product successfully to its carts i need to track this event in log, I try below code but it was not working
var params = {};
params[AppEventsConstants.CONTENT] = "Sample Product 1";
params[AppEventsConstants.CONTENT_ID] = "1";
params[AppEventsConstants.CONTENT_TYPE] = "Product";
params[AppEventsConstants.CURRENCY] = "INR";
AppEventsLogger.logEvent(AppEventsConstants.EVENT_NAME_ADDED_TO_WISHLIST, 40, params)
Above code give me an undefined error. Please help me how to implement this.
Unfortunately, react-native-fbsdk doesn't provide constants of standard event names and parameter names. After some research, I found actual names are listed in Marketing API documentation (why not App Events?).
I don't know if it is the right place, but you can refer to https://developers.facebook.com/docs/marketing-api/app-event-api/ for actual standard event names and parameter names. At least it worked in my case. Here's my Add to Cart event code:
function logAddToCart(totalPrice, contentType, contentId, currency) {
const params = {
'fb_content_type': contentType,
'fb_content_id': contentId,
'fb_currency': currency
};
AppEventsLogger.logEvent('fb_mobile_add_to_cart', totalPrice, params);
}
u can read from the docs react-native-fbsdk
https://github.com/facebook/react-native-fbsdk/blob/17af77b48ad6ea62fa3c0f552cd8f6a699ef6a64/src/FBAppEventsLogger.js#L92
and u can find constant from this
https://github.com/facebook/facebook-android-sdk/blob/master/facebook-core/src/main/java/com/facebook/appevents/AppEventsConstants.java#L33-L312
// helpers
import { AppEventsLogger } from 'react-native-fbsdk'
export const FB_ADDED_TO_CART = 'fb_mobile_add_to_cart'
export async function logAdsEvent(eventName, ...args) {
let valueToSum = 0
if (typeof args[0] === 'number') {
valueToSum = args.shift()
}
let parameters = null
if (typeof args[0] === 'object') {
parameters = toSnakeCase(args[0])
}
AppEventsLogger.logEvent(eventName, valueToSum, parameters)
}
implementation
import helpers
logAdsEvent(FB_ADDED_TO_CART, result.stores[0].subTotal || 0, {
fbContent: JSON.stringify(result.stores[0]),
fbCurrency: 'IDR'
})

Whatsapp Web - how to access data now?

It used to be possible to access http://web.whatsapp.com/ with the Store object in JavaScript. A few hours ago, this stopped working. How does it update chat data now? It must save the data somewhere.
I'm using this to get the Store again:
setTimeout(function() {
// Returns promise that resolves to all installed modules
function getAllModules() {
return new Promise((resolve) => {
const id = _.uniqueId("fakeModule_");
window["webpackJsonp"](
[],
{
[id]: function(module, exports, __webpack_require__) {
resolve(__webpack_require__.c);
}
},
[id]
);
});
}
var modules = getAllModules()._value;
// Automatically locate modules
for (var key in modules) {
if (modules[key].exports) {
if (modules[key].exports.default) {
if (modules[key].exports.default.Wap) {
store_id = modules[key].id.replace(/"/g, '"');
}
}
}
}
}, 5000);
function _requireById(id) {
return webpackJsonp([], null, [id]);
}
// Module IDs
var store_id = 0;
var Store = {};
function init() {
Store = _requireById(store_id).default;
console.log("Store is ready" + Store);
}
setTimeout(function() {
init();
}, 7000);
Just copy&paste on the console and wait for the message "Store is ready".
Enjoy!
To explain Pablo's answer in detail, initially we load all the Webpack modules using code based on this How do I require() from the console using webpack?.
Essentially, the getAllModules() returns a promise with all the installed modules in Webpack. Each module can be required by ID using the _requireById(id) which uses the webpackJsonp(...) function that is exposed by Webpack.
Once the modules are loaded, we need to identify which id corresponds to the Store. We search for a module containing exports.default.Wap and assign it's id as the Store ID.
You can find more details on my github wiki here
A faster method:
I grab the source of the "app" and find the store object then
I save it in ZStore global variable. :D
!function(){for(var t of document.getElementsByTagName("script"))t.src.indexOf("/app.")>0&&fetch(t.src,{method:"get"}).then(function(t){return t.text().then(function(t){var e=t.indexOf('var a={};t["default"]')-89;window.ZStore=window.webpackJsonp([],null,JSON.stringify(t.substr(e,10))).default})})}();
window.ZStore will contain the object.
Non minified version:
(function() {
function getStore(url) {
fetch(url, {
"method": 'get'
}).then(function(response) {
return response.text().then(function(data) {
var offset = data.indexOf('var a={};t["default"]') - 89;
window.ZStore = window.webpackJsonp([], null, JSON.stringify(data.substr(offset, 10))).default
});
});
}
for (var e of document.getElementsByTagName("script")) {
if (e.src.indexOf("/app.") > 0) getStore(e.src);
}
})();

React-Native - Can't find variable : Proxy

i'm using Proxy for my react native app
import Setting from "./Setting";
const lang = { ar : {...} , en : {...} , fr : {...} };
export const string = new Proxy(lang, {get: function (object, name){return object[Setting.settings.lang][name]}});
export default string;
but throws cannot find variable Proxy
I can't add a comment, but i think your question is linked to the same question:-
Proxy ES6
In otherwords, you'll need to import a polyfill.
--
Edited to add the codes:
import 'proxy-polyfill';
.
.
_samplePolyfill = () => {
function observe(o, callback) {
return new Proxy(o, {
set(target, property, value) {
callback(property, value);
target[property] = value;
},
});
}
const x = {'name': 'BB-8'};
const p = observe(x, (property, value) => console.warn(property, value));
p.name = 'BB-9';
}
.
.
componentDidMount() {
this._samplePolyfill();
}
For a weird reason if you enable RemoteJs Debugger it works
Ctrl/CMD + M
if you had issue connecting to remote debugger follow this :
Unable to connect with remote debugger

Aurelia CLI font-awesome

I've tried several different solutions but not a single one have worked for me.
I'm using.NET Core, latest Aurelia/Aurelia CLI and Font-Awesome installed using npm install font-awesome --save.
Solution 1:
New file: prepare-font-awesome.js in folder \aurelia_project\tasks
import gulp from 'gulp';
import merge from 'merge-stream';
import changedInPlace from 'gulp-changed-in-place';
import project from '../aurelia.json';
export default function prepareFontAwesome() {
const source = 'node_modules/font-awesome';
const taskCss = gulp.src(`${source}/css/font-awesome.min.css`)
.pipe(changedInPlace({ firstPass: true }))
.pipe(gulp.dest(`${project.platform.output}/css`));
const taskFonts = gulp.src(`${source}/fonts/*`)
.pipe(changedInPlace({ firstPass: true }))
.pipe(gulp.dest(`${project.platform.output}/fonts`));
return merge(taskCss, taskFonts);
}
Updated build.js\aurelia_project\tasks
import prepareFontAwesome from './prepare-font-awesome'; // Our custom task
export default gulp.series(
readProjectConfiguration,
gulp.parallel(
transpile,
processMarkup,
processCSS,
prepareFontAwesome // Our custom task
),
writeBundles
);
Included font-awesome in html
<link rel="stylesheet" href="scripts/css/font-awesome.min.css">
Error:
GET http://localhost:9000/scripts/css/font-awesome.min.css
Solution 2:
Updated aurelia.json
{
"name": "font-awesome",
"path": "../node_modules/font-awesome/",
"main": "",
"resources": [
"css/font-awesome.min.css"
]
}
Added font files in root/font-awesome/fonts
Included font-awesome in html
<require from="font-awesome/css/font-awesome.min.css"></require>
Error: No error but icons are not shown
Solution 3:
Updated build.js:
import gulp from 'gulp';
import transpile from './transpile';
import processMarkup from './process-markup';
import processCSS from './process-css';
import { build } from 'aurelia-cli';
import project from '../aurelia.json';
import fs from 'fs';
import readline from 'readline';
import os from 'os';
export default gulp.series(
copyAdditionalResources,
readProjectConfiguration,
gulp.parallel(
transpile,
processMarkup,
processCSS
),
writeBundles
);
function copyAdditionalResources(done){
readGitIgnore();
done();
}
function readGitIgnore() {
let lineReader = readline.createInterface({
input: fs.createReadStream('./.gitignore')
});
let gitignore = [];
lineReader.on('line', (line) => {
gitignore.push(line);
});
lineReader.on('close', (err) => {
copyFiles(gitignore);
})
}
function copyFiles(gitignore) {
let stream,
bundle = project.build.bundles.find(function (bundle) {
return bundle.name === "vendor-bundle.js";
});
// iterate over all dependencies specified in aurelia.json
for (let i = 0; i < bundle.dependencies.length; i++) {
let dependency = bundle.dependencies[i];
let collectedResources = [];
if (dependency.path && dependency.resources) {
// run over resources array of each dependency
for (let n = 0; n < dependency.resources.length; n++) {
let resource = dependency.resources[n];
let ext = resource.substr(resource.lastIndexOf('.') + 1);
// only copy resources that are not managed by aurelia-cli
if (ext !== 'js' && ext != 'css' && ext != 'html' && ext !== 'less' && ext != 'scss') {
collectedResources.push(resource);
dependency.resources.splice(n, 1);
n--;
}
}
if (collectedResources.length) {
if (gitignore.indexOf(dependency.name)< 0) {
console.log('Adding line to .gitignore:', dependency.name);
fs.appendFile('./.gitignore', os.EOL + dependency.name, (err) => { if (err) { console.log(err) } });
}
for (let m = 0; m < collectedResources.length; m++) {
let currentResource = collectedResources[m];
if (currentResource.charAt(0) != '/') {
currentResource = '/' + currentResource;
}
let path = dependency.path.replace("../", "./");
let sourceFile = path + currentResource;
let destPath = './' + dependency.name + currentResource.slice(0, currentResource.lastIndexOf('/'));
console.log('Copying resource', sourceFile, 'to', destPath);
// copy files
gulp.src(sourceFile)
.pipe(gulp.dest(destPath));
}
}
}
}
}
function readProjectConfiguration() {
return build.src(project);
}
function writeBundles() {
return build.dest();
}
Updated aurelia.json
{
"name": "font-awesome",
"main":"",
"path": "../node_modules/font-awesome",
"resources": [
"css/font-awesome.css",
"/fonts/fontawesome-webfont.woff2",
"/fonts/FontAwesome.otf",
"/fonts/fontawesome-webfont.eot",
"/fonts/fontawesome-webfont.svg",
"/fonts/fontawesome-webfont.ttf"
]
}
Included font-awesome in html
<require from="font-awesome/css/font-awesome.css"></require>
Error:
get:
http://localhost:9000/font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0
(same with woff and ttf)
It's really strange because the files are copied and the url is correct..
Folder structure:
Tested a couple of different sources
What am I missing?
I would prefer a less implementation so I can import Font-Awesome in my master less file.
Based off of the discussion, since you are hosting your project inside the wwwroot folder, you must base your "gets" for files from there.
So, if you move your font files into wwwroot/fonts/font-name.woff (or somewhere thereabouts), you should be golden.
If you are on a webpack based project generated with latest (2019ish) aurelia cli , then adding fontawesome or bootstrap is pretty simple.
step 1: install fontawesome
check the official docs here. Just for completeness, here is the npm or yarn way for free version
//with npm
npm install --save-dev #fortawesome/fontawesome-free
// yarn
yarn add --dev #fortawesome/fontawesome-free
step 2: import the font
in your main.js or main.ts or app.js or app.ts , all of them will work equally well, which is better? ask google.
import '#fortawesome/fontawesome-free/css/all.min.css';
And an even simpler method would be to add the CDN version into the head of your index.esj or index.html file
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.1/css/all.css" />
all of the above work equally well, personally for public apps, I prefer CDN solution due to browser cache.