webview_flutter javascript channel can't communicate in release mode - reactjs

I am using webview_flutter with version 3.0.0 in my app. I have heavy usage of two-way communication between flutter and javascript. Everything works on debug mode nicely. But, after I build the APK started to get some errors in the javascript channel. I tried with flutter run --release and got the same error.
In my web application (ReactJs), I am using channels in this way:
index.html
<div id="root">
<script>
function sendToFlutter(message) {
if (flutterChannel) {
flutterChannel.postMessage(message);
}
}
</script>
</div>
calling is from React component like this:
window.sendToFlutter("hello-world");
My Webview setup from Flutter end:
Completer<WebViewController> webViewCompleter = Completer<WebViewController>();
WebView(
debuggingEnabled: false,
initialUrl: "https://example.com",
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
webViewCompleter.complete(webViewController);
},
javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: "flutterChannel",
onMessageReceived: (JavascriptMessage message) {
if (message.message == "hello-world") {
// Do something
}
})
},
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
gestureNavigationEnabled: true,
zoomEnabled: false,
userAgent: Platform.isAndroid ? kAndroidUserAgent : kIosUserAgent,
);
When I call the channel from ReactJs, then I am getting this error:
TypeError: flutterChannel.postMessage is not a function

According to alexbatalov's research in https://github.com/flutter/flutter/issues/92548, the current workaround is to do the following:
Create android/app/proguard-rules.pro. At minimum you need to have a
rule for JavascriptInterface, but I recommend to copy entire
proguard-android.txt, given the fact that you don’t have these rules.
# Preserve annotated Javascript interface methods.
-keepclassmembers class * {
#android.webkit.JavascriptInterface <methods>;
}

Related

Fetching a github repo in react gives a "Module "stream" has been externalized for browser compatibility and cannot be accessed in client code" error

I am currently stuck with a problem trying to fetch github repo data using the octokit npm package.
I use vite to run a dev server and when I try to make a request, the error that i get is:
Uncaught Error: Module "stream" has been externalized for browser compatibility and cannot be accessed in client code.
My React .tsx file looks like this:
import { Octokit, App } from 'octokit'
import React from 'react'
const key = import.meta.env.GITHUB_KEY
const octokit = new Octokit({
auth: key
})
await octokit.request('GET /repos/{owner}/{repo}', {
owner: 'OWNER',
repo: 'REPO'
})
export default function Repos() {
return (
<>
</>
)
}
I have redacted the information for privacy purposes.
If anyone knows how to resolve this issue with vite, please let me know!
Check first if this is similar to octokit/octokit.js issue 2126
I worked around this problem by aliasing node-fetch to isomorphic-fetch. No idea if it works for all usages within octokit, but works fine for my project.
You'll need to install the isomorphic-fetch dependency before making this config change.
// svelte.config.js
const config = { // ... kit: {
// ...
vite: {
resolve: {
alias: {
'node-fetch': 'isomorphic-fetch',
},
},
},
},
};
export default config;
Note: there are still questions about the support/development of octokit: issue 620.

Window.OneSignal showing 404 error when i am trying to use it with next.js

I am trying to implement OneSignal web push notifications with the next.js web app. I followed this article
to implement it. But it is not implementing properly as it shows an error. I have doubt that where should I place the window.OnseSignal code shown in step 7.
What I did?
I built a component name NewOneSignal instead of pasting it in App.js (because there is no App.js file in next.js) whose code is given below:
import React, { useEffect } from "react";
const NewOneSignal=()=>{
useEffect(()=>{
window.OneSignal = window.OneSignal || [];
const OneSignal = window.OneSignal;
},[]);
return (
OneSignal.push(()=> {
OneSignal.init(
{
appId: "i pasted my app id here", //STEP 9
promptOptions: {
slidedown: {
enabled: true,
actionMessage: "We'd like to show you notifications for the latest Jobs and updates about the following categories.",
acceptButtonText: "OMG YEEEEESS!",
cancelButtonText: "NAHHH",
categories: {
tags: [
{
tag: "governmentJobs",
label: "Government Jobs",
},
{
tag: "PrivateJobs",
label: "Private Jobs",
}
]
}
}
},
welcomeNotification: {
"title": "The website",
"message": "Thanks for subscribing!",
}
},
//Automatically subscribe to the new_app_version tag
OneSignal.sendTag("new_app_version", "new_app_version", tagsSent => {
// Callback called when tag has finished sending
console.log('new_app_version TAG SENT', tagsSent);
})
);
})
)
}
export default NewOneSignal;
And imported this component in the document.js file. According to this article, I have to put step 8 code in the useEffect but didn't work also, I have tried that also
I am very much sure that the problem is in this file. I paste the OneSignalsdk script in head section of the _document.js file.Also, i moved the two service worker files in a public folder as shown in the article. Please help me to make this code work

In-Context Checkout: Uncaught SecurityError: Blocked a frame with origin: checkout.js:4734 throws error

I'm implementing PayPal's In-Context Checkout and am using Advanced In-Context JavaScript settings (https://developer.paypal.com/docs/classic/express-checkout/in-context/javascript_advanced_settings)
My app is a React app. So I can't use PP API as they suggest it, that is just throw a code between <script> ... </script> tags somewhere in the page under their buttons. My React components have state and data that I need to send to server within PP function calls. So I placed PP code in componentDidMount method. And for some reason PP throws this error:
checkout.js:4734 Uncaught SecurityError: Blocked a frame with origin "http://example.com:3000" from accessing a frame with origin "https://www.sandbox.paypal.com". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "https". Protocols must match.
(anonymous function) # checkout.js:4734
checkout.js:4515 GET http://www.paypalobjects.com/api/oneTouch.html net::ERR_EMPTY_RESPONSE
Here is the code:
componentDidMount() {
window.paypalCheckoutReady = function() {
paypal.checkout.setup(config[NODE_ENV].ppMerchantID, {
locale: 'en_US',
environment: 'sandbox',
buttons: [
{
container: 'checkoutBtnContainer',
type: 'checkout',
color: 'gold',
size: 'medium',
shape: 'pill',
click: (ev)=>{
paypal.checkout.initXO();
$.post('/checkout', {
checkoutData: this.props.checkoutData,
})
.done(res => {
paypal.checkout.startFlow(res.link);
})
.fail(err => {
paypal.checkout.closeFlow();
});
}
}
],
});
};
},
I know about cross-origin policy. I don't understand why it is the case here. Why the code works fine if I throw it on the page between <script> ... </script> tags, but PP throws an error if I use it in my React component. What is the cause of that? Is it React fault or PayPal's?
UPD: No, below is not a solution for the problem. Occasionally Paypal's checkout.js script throws the error.
However, it solves this issue
Apparently,
1) there was no this:
window.paypalCheckoutReady = function() {
// wrong this is here
}
I changed to:
window.paypalCheckoutReady = () => {
// correct this is here now
}
I don't like .bind(this).
2) I removed <form /> tag and set plain <div> instead:
// before
<form id="checkoutBtnContainer" method="post" action="/checkout"></form>
// after
<div id="checkoutBtnContainer"></div>
I don't know why and how, but all works fine now.

Ionic 2 File Plugin usage examples

Does anyone have complete examples about how to use the Cordova Native File Plugin in a Ionic 2/Angular 2 project?
I installed this plugin but the documentation don't seems to make much sense to me due the fact it is fragmented and lacks of a complete example, including all needed imports.
For example, the following example don't shows where objects like LocalFileSystem or window came from.
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) {
console.log('file system open: ' + fs.name);
fs.root.getFile("newPersistentFile.txt", { create: true, exclusive: false }, function (fileEntry) {
console.log("fileEntry is file?" + fileEntry.isFile.toString());
// fileEntry.name == 'someFile.txt'
// fileEntry.fullPath == '/someFile.txt'
writeFile(fileEntry, null);
}, onErrorCreateFile);
}, onErrorLoadFs);
For example, I need to crate a property file. First I need to check if a file exists on app sandbox storage area, if don't exists I must create it. Then I must open the file write data and save it . How could I do that?
Ionic 2 comes with a Cordova file plugin wrapper:
http://ionicframework.com/docs/v2/native/file/.
The necessary file system paths (e.g. cordova.file.applicationDirectory) you can find here at the documentation of the original plugin:
https://github.com/apache/cordova-plugin-file#where-to-store-files. Note that not all platforms support the same storage paths.
I even managed to build a file browser with it. Use it like so:
import {Component} from '#angular/core';
import {File} from 'ionic-native';
...
File.listDir(cordova.file.applicationDirectory, 'mySubFolder/mySubSubFolder').then(
(files) => {
// do something
}
).catch(
(err) => {
// do something
}
);
Here is an example using IonicNative for an app I am working on where I want
to send an email with a csv file attachment.
import {EmailComposer} from '#ionic-native/email-composer';
import {File} from '#ionic-native/file';
class MyComponent {
constructor(private emailComposer: EmailComposer, private file: File) {
}
testEmail() {
this.file.writeFile(this.file.dataDirectory, 'test.csv', 'hello,world,', {replace: true})
.then(() => {
let email = {
to: 'email#email',
attachments: [
this.file.dataDirectory + 'test.csv'
],
subject: 'subject',
body: 'body text...',
isHtml: true
};
this.emailComposer.open(email);
})
.catch((err) => {
console.error(err);
});
}
}
This was tested with ionic 3.7.0 on IOS.

React Native + React (for web) seed project

Is it possible to setup a project which has code for both React Native(Mobile app) + React(web), having the code shred between platforms except for the UI part.
Have done something similar with Angular + NativeScript using this seed, which enables code sharing between native app and web application(Except for the UI layer). Looking for something similar for React + React Native.
Please share if you know any such seed for React Native + Angular as well, if available.
Jonathan Kaufman has a good article on how to set this up: http://jkaufman.io/react-web-native-codesharing/
The basic strategy is to have a different entry point (index.js) for each platform (android/ios/web). Then the majority of your non-rendering code can live in a shared app or common folder. You'll still need to segregate your rendering code (i.e. uses of View, div, etc.), though, as that will differ by platform.
Pay attention to the comments on that article as well, as there's some good discussion on the pitfalls of this approach. Example:
By sharing a common package.json between native and web, you've glued them together by their common dependencies, the most important one being react. Let's say you upgrade to a version of react-native that depends on >= react#16, but your web app depends on some other library which depends on =< react#15. --timtas
You can give a try to React-Native-Web, but imho you should create 2 different projects, isolate and copy what can be used on both (like api requests and util functions). Your code will be easier to debug and maintain.
Yes, absolutely possible. We've done it before using this lib react-native-web. https://github.com/necolas/react-native-web
beside index.ios.js and index.android.js, you will need create index.web.js, the content should be similar like this.
import { AppRegistry } from 'react-native';
import App from './app/Containers/App/App.container';
AppRegistry.registerComponent('ReactNativeWeb', () => App);
AppRegistry.runApplication('ReactNativeWeb', { rootTag: document.getElementById('react-app') });
also you need to create your own nodejs code to serve up the bundle. full reference
I do it this way.
1) Create a React Native project.
2) Add react-dom and react-scripts dependencies to package.json and install them.
3) All the component code is separated this way:
My regular React Native component:
// MyComponent.js
class MyComponent extends React.Component {
costructor(props) {
...
}
someMethod() {
...
}
render() {
return (
<View>
...
</View>
)
}
}
Changed for using in web:
// MyComponentController.js
class MyComponentController extends React.Component {
costructor(props) {
...
}
someMethod() {
...
}
}
// MyComponent.js
const MyComponentController = require('./MyComponentController')
class MyComponent extends MyComponentController {
render() {
return (
<div>
...
</div>
)
}
}
// MyComponent.native.js
const MyComponentController = require('./MyComponentController')
class MyComponent extends MyComponentController {
render() {
return (
<View>
...
</View>
)
}
}
And then I use in it in all the platforms:
const MyComponent = require('./MyComponent')
For this to work nicely with an old project I had to implement some dummies, but it all can be done better by your own layer of abstraction. Part of my example:
const ReactNative = {
Platform: {
OS: 'web'
},
AppRegistry: {
registerComponent: (name, provider) => {
const Component = provider()
ReactDOM.render(
<Component />,
document.getElementById('root')
);
}
},
AsyncStorage: {
setItem: (key, value, callback) => {
localStorage.setItem(key, value)
callback()
},
getItem: key => {
const val = localStorage.getItem(key) || null
return new Promise(ok => ok(val))
}
},
StyleSheet: {
create: dict => dict
},
Dimensions: {
get: function() {
// http://stackoverflow.com/questions/3437786/get-the-size-of-the-screen-current-web-page-and-browser-window
const w = window
const d = document
const e = d.documentElement
const g = d.getElementsByTagName('body')[0]
const x = w.innerWidth || e.clientWidth || g.clientWidth
const y = w.innerHeight|| e.clientHeight|| g.clientHeight
return {
width: x,
height: y
}
}
},
Linking: {
openURL: (url) => {
window.open(url)
}
},
// etc, I add dummies as soon as I need them
}
But, as I said, this was necessary only because I did not have much time and had not known in advance that I would have to port to web.
You can try the repo that I tried to prepare:
https://mehmetkaplan.github.io/react-spa-jwt-authentication-boilerplate/
This has a step by step guideline that enables to share common logic between react and react-native applications.
It aims to differentiate only in the presentation layer. Other than that all logic is compiled to be shared between applications.
It also comes with facebook and google logins, database (mysql) integration, WebView task generation, etc.
And also it gives the fundamental know-how on "single page applications", "JWT (json web token) based security", etc..
Once read the README, you can simply clone the repo and set your environment (Database) and start developing business logic on top of the shared code structure and security baseline.
You can create 3 stand-alone applications - React, React-native & Server.
Both React & React-native will use the same services from your back-end app.
Else go with a single app where on loading home page, app will understand the device and render React / React-native code as per the device.

Resources