navigate to a react native page from webview - javascript

I have a react native app that uses webView to render an html code , i used postmessage for communicating between them. webview will send message to react native to open another react native page ( for example send message 'open login page' ) so i will recieve it in onMessage (event) , then when i want to use this.props.navigateTo it says 'can not read property navigateTo of undefined' .
i used console.log(this) and understood 'this' is DedicatedWorkerGlobalScope, that i can't use navigatTo() . how can i use navigatTo in this case (while im in DedicatedWorkerGlobalScope) or how can i close or stop rendering webview to do this?
onMessage( event ) {
let post = JSON.parse(event.nativeEvent.data);
console.log(event)
switch(post.message){
case 'open login page':
console.log(this); // 'DedicatedWorkerGlobalScope'
this.props.navigateTo('login'); //'can not read property navigation of undefined'
break;
}
}
this is the webview :
<WebView
// onNavigationStateChange={async (e) => {
// console.log(e);
// if (async() => await getKey('isLogged') === false)
// this.props.navigateTo('login') //its ok here
// }}
source={{uri: isAndroid ?'file:///android_asset/html/index.html'
:'./html/index.html'}}
ref={ webView => this.webView = webView }
onMessage={this.onMessage}
/>
i use this.props.navigatTo() for navigating and its ok but in this case state doesnt change to run this here ,
and this is my stacknavigator :
login: {
screen: Login
},
app: {
screen: App // webview defined here
},

I solve the problem myself :)) I should use bind,
OnMessage = { this.onMessage.bind (this) }
I just forgot it ...

Related

Dynamic link through scan QR code open the app then won't navigate to different screen

My App which created using React Native require functionality to scan QR code with default camera app then open specific screen in the app, in order to achieve this I setup Firebase dynamic links by also using React Native Firebase library.
The setup were pretty simple, a dynamic link using Firebase provided domain, the link also contain deep link in url format https://example.page.link/abc-xyz.
After the app scan the QR it use deep link url to extract the abc-xyz part and navigate to different screen, here is my implementation.
// App.js
const handleDynamicLink = link => {
const linkCheck = new RegExp('^https://example.page.link/.*$');
let title;
if (linkCheck.test(link.url)) {
title = link.url.substring(link.url.lastIndexOf('/') + 1).split('-');
RootNavigation.navigate(Screens.offer, { title: title });
}
};
...
React.useEffect(() => {
// Handler for background/quit events
dynamicLinks().getInitialLink().then(link => {
handleDynamicLink(link);
});
// Handler for foreground events
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
return () => unsubscribe();
}, []);
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
All necessary setup already configured both for iOS and Android, doing test with Android device by scanning the QR code, it recognise the link and navigate to intended screen, but not for iOS even though it understand the link and it only open the initial screen then stop there.
Strange thing is if I open the link directly in device browser it will open the preview page, then if I click the "open" button it open the app and navigate to target screen.
Wondering is this has something to do with navigation stuff in the iOS native side?
Turns out I need to update AppDelegate.m and add link handler for iOS.
Added below code above #end line in AppDelegate.m.
// AppDelegate.m
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
Handling when app in foreground state or already open.
// App.js
if (Platform.OS === 'ios') {
Linking.addEventListener('url', handleDynamicLink);
}
Handling when app is fully closed/initiated by dynamic link.
// App.js
if (Platform.OS === 'ios') {
Linking.getInitialURL()
.then(link => {
handleDynamicLink({ url: link });
})
.catch(error => {
// Error handling
});
} else {
// This part for Android
dynamicLinks().getInitialLink().then(link => {
handleDynamicLink(link);
});
}

display custom error message in a live Player

I have a react code as shown below which renders player on page load. The code goes inside the if block only if the condition is true.
const player = ()=> {
if(condition) {
return (
<ReactJWPlayer
playlist={[props.playlist]}
/>
)
}
}
Problem Statement: For the error code 232011, I am seeing the following error message:
[![enter image description here][1]][1]
This video file cannot be played
(Error Code: 232011)
I am wondering what changes I need to make in the react code above so that I can replace the above error message with the following one in the player.
Video will be available soon
You have to use intl.{lang}.errors object. This object localizes the error messages displayed in the player.
In order to configure intl.{lang}.errors, you will have to use customProps option exposed by react-jw-player to be applied directly to the JW Player instance.
You can stick with en only, or add additional language support depending on your use-case.
import { useRef } from "react";
import ReactJWPlayer from "react-jw-player";
import ReactDOM from "react-dom";
export default function App() {
const jwPlayerRef = useRef();
const myErrorHandler = (err) => {
console.log(err);
// Find the Node where error message is shown
const errorNode = ReactDOM.findDOMNode(
jwPlayerRef.current
).getElementsByClassName("jw-error-text");
// If the error node exists, replace both message and code
if (errorNode && errorNode.length)
errorNode[0].innerText = "Custom Error Message";
};
return (
<div className="App">
<div
className="jw-video-container"
data-mediaid="TAITbudl"
style={{ height: "100%", width: "100%" }}
>
<ReactJWPlayer
ref={jwPlayerRef}
playerId="TAITbudl"
playerScript="https://content.jwplatform.com/libraries/j9BLvpMc.js"
playlist="https://cdn.jwplayer.com/v2/media/123"
onError={myErrorHandler}
onSetupError={myErrorHandler}
customProps={{ // <= Official way to override the error message
intl: {
en: {
errors: {
badConnection:
"This video cannot be played because of a problem with your internet connection.",
cantLoadPlayer: "Sorry, the video player failed to load.",
cantPlayInBrowser:
"The video cannot be played in this browser.",
cantPlayVideo: "This is my custom error Message",
errorCode: "Code - ",
liveStreamDown:
"The live stream is either down or has ended.",
protectedContent:
"There was a problem providing access to protected content.",
technicalError:
"This video cannot be played because of a technical error."
}
}
}
}}
/>
</div>
</div>
);
}
The intl object allows you to add new language translations, [...] - Docs
Note that getElementsByClassName("jw-error-text") is a hack, and if JW Player decided to change the class name, or obfuscate it, this hack will break.

React Native: AWS amplify requires that I tap `federatedSignIn` twice for google to succeed

My app is requiring that google oauth (via federatedSignIn) be tapped twice in iOS devices, prior to actually signing the user in.
Process:
Upon the first tap, inapp browser opens up and you select which account you're intending to sign in with. Inapp browser closes and seems like all the rest of my logic is not being hit.
Upon the second tap, the inapp browser re-opens up again for a split second (screen is blank), and then closes and THEN the user is actually signed in.
On the iOS simulator/android, however, it seems like it works as expected. Another strange thing is that it works as expected for oauth'ing in with Apple on all devices.
Wondering if anyone else has run into this issue and if y'all have a suggestion?
Where I instantiate the hub listener:
useEffect(() => {
// NOTE: amplify hub listener
const listener = async (data: any) => {
switch (data.payload.event) {
case "signIn":
case "cognitoHostedUI":
await signInUser();
break;
case "signOut":
setUser(null);
break;
default:
break;
}
};
Hub.listen("auth", listener);
}, []);
My google oauth button component:
export function GoogleSignInButton({ title }: GoogleSignInButtonProps) {
return (
<SocialIcon
button
type="google"
title={title}
style={{ padding: 50, marginBottom: 10 }}
onPress={() =>
Auth.federatedSignIn({
provider: "Google" as any,
}).catch(federatedSignInError => {
console.log({ federatedSignInError });
throw new Error(federatedSignInError);
})
}
/>
);
}
I'm also using the react-native-inappbrowser-reborn npm package to have an internal webview when signing in, if that's relevant:
async function urlOpener(url: string, redirectUrl: string) {
await InAppBrowser.isAvailable();
const { type, url: newUrl } = (await InAppBrowser.openAuth(url, redirectUrl, {
showTitle: false,
enableUrlBarHiding: true,
enableDefaultShare: false,
ephemeralWebSession: false,
})) as RedirectResult;
if (type === "success") {
Linking.openURL(newUrl);
}
}
const appsyncAuthenticationTypeOverride = {
...config,
oauth: {
...config.oauth,
urlOpener,
},
aws_appsync_authenticationType: "AWS_IAM",
};
Amplify.configure(appsyncAuthenticationTypeOverride);
i had the same issue.
It seems to be related to Cookies in navigator, you seem to be loading the during the first logging attempt, and using the during the second one.
Also it seems to be sometimes related to redirection errors in Cognito Auth Flow.
I managed to solve it by finding this issue :
https://github.com/aws-amplify/amplify-js/issues/7468
Especially this comment :
https://github.com/aws-amplify/amplify-js/issues/7468#issuecomment-816853703

OAuth popup cross-domain security React.js

I'm interested in how to implement OAuth in React using popup (window.open).
For example I have:
mysite.com — this is where I open the popup.
passport.mysite.com/oauth/authorize — popup.
The main question is how to create connection between window.open (popup) and window.opener (as it's known the window.opener is null due to cross-domain security therefore we can't use it anymore).
⇑ window.opener is removed whenever you navigate to a different host (for security reasons), there is no way around it. The only option should be doing the payment in a frame if it is possible. The top document needs to stay on the same host.
Scheme:
Possible solutions:
Check an opened window using setInterval described here.
Using cross-storage (not worth it imho ).
So what's the best recommended approach in 2019?
Wrapper for React - https://github.com/Ramshackle-Jamathon/react-oauth-popup
Suggested by Khanh TO. OAuth popup with localStorage. Based on react-oauth-popup.
Scheme:
Code:
oauth-popup.tsx:
import React, {PureComponent, ReactChild} from 'react'
type Props = {
width: number,
height: number,
url: string,
title: string,
onClose: () => any,
onCode: (params: any) => any,
children?: ReactChild,
}
export default class OauthPopup extends PureComponent<Props> {
static defaultProps = {
onClose: () => {},
width: 500,
height: 500,
url: "",
title: ""
};
externalWindow: any;
codeCheck: any;
componentWillUnmount() {
if (this.externalWindow) {
this.externalWindow.close();
}
}
createPopup = () => {
const {url, title, width, height, onCode} = this.props;
const left = window.screenX + (window.outerWidth - width) / 2;
const top = window.screenY + (window.outerHeight - height) / 2.5;
const windowFeatures = `toolbar=0,scrollbars=1,status=1,resizable=0,location=1,menuBar=0,width=${width},height=${height},top=${top},left=${left}`;
this.externalWindow = window.open(
url,
title,
windowFeatures
);
const storageListener = () => {
try {
if (localStorage.getItem('code')) {
onCode(localStorage.getItem('code'));
this.externalWindow.close();
window.removeEventListener('storage', storageListener);
}
} catch (e) {
window.removeEventListener('storage', storageListener);
}
}
window.addEventListener('storage', storageListener);
this.externalWindow.addEventListener('beforeunload', () => {
this.props.onClose()
}, false);
};
render() {
return (
<div onClick={this.createPopup)}>
{this.props.children}
</div>
);
}
}
app.tsx
import React, {FC} from 'react'
const onCode = async (): Promise<undefined> => {
try {
const res = await <your_fetch>
} catch (e) {
console.error(e);
} finally {
window.localStorage.removeItem('code'); //remove code from localStorage
}
}
const App: FC = () => (
<OAuthPopup
url={<your_url>}
onCode={onCode}
onClose={() => console.log('closed')}
title="<your_title>">
<button type="button">Enter</button>
</OAuthPopup>
);
export default App;
I once encounter an issue on my oauth login flow with window.open/window.opener bug on ms-edge
My flow before this issue was
On login button click open a popup
After successful login the oauth app redirect to my domain's page
Then i call a function of the parent window from with in the popup (window.opener.fn) with data from oauth response and the parent window then close the child popup window
My flow after this issue was
On login button click open a popup
Create a setinterval in case (window.opener is undefined)
After successful login the oauth app redirect to my domain's page
Check if window.opener is available then do #3 from the above flow and clearInterval
If window.opener is not available then since i am on my domains page i try to set localstorage and try to read the localstorage from inside the setInterval function in parent window then clear the localstorage and setInterval and proceed.
(for backward compatibility) If localstorage is also not available then set a client side cookie with the data with a short expiry (5-10 sec) time and try to read the cookie (document.cookie) inside the setInterval function in parent window and proceed.

Initialising Okta Signin Widget a second time in a single-page webapp throws exception

We are integrating the Okta Sign-in Widget into our React-based webapp.
The example snippet:
var oktaSignIn = new OktaSignIn({baseUrl: baseUrl});
oktaSignIn.renderEl(...)
Works fine for us when rendering the widget for the first time, but after the user logs in and logs out again, the webapp renders the login component a second time and would attempt to execute the renderEl again to render the widget. This causes the following exception to be thrown:
Backbone.history has already been started
I have created this jsfiddle to demonstrate the problem. It just instantiates a signin widget twice (the second time after a wait). You can see that the second invocation causes the exception to be thrown.
https://jsfiddle.net/nudwcroo/6/
At the moment my workaround is to reload the entire webapp when going to the login component but that is undesirable for a single page app.
Is this a known issue? Is there any way to initialise the sign-in widget twice in a single javascript session?
Since the widget can only be instantiated once per page, it is best to hide/show the element for all Single Page Applications.
<div id="okta-login-container"></div>
<script type="text/javascript">
var oktaSignIn = new OktaSignIn(/* config */);
oktaSignIn.renderEl(
{ el: '#okta-login-container' },
function (res) {
if (res.status === 'SUCCESS') {
// Hide element
$("#okta-login-container").hide();
}
}
);
</script>
When you create your logout() function, make sure to show() the element instead of rendering it again.
function logout() {
$('#okta-login-container').show();
// Do more logic
}
For those experiencing similar problems after following the Okta example provided here: (https://github.com/okta/samples-js-react/blob/master/custom-login/src/Login.jsx)
The problem is with attempting to initialize the widget multiple times within a single page application. I fixed this by only initializing the widget once at the App level, and then rendering it and removing it from the DOM when a child component mounts/unmounts.
Example:
//App.js
class App extends Component {
constructor() {
super()
this.signIn = new OktaSignIn({...})
}
render() {
return <SignInPage widget={this.signIn} />
}
}
--
//SignInPage.js
...
componentDidMount() {
let { redirectUri } = this.state
let { widget } = this.props
widget.renderEl(
{ el: '#sign-in-widget' },
(response) => {
response.session.setCookieAndRedirect(redirectUri)
},
(error) => {
throw error;
},
);
}
componentWillUnmount() {
let { widget } = this.props
widget.remove()
}
render() {
return <div id="sign-in-widget"/></div>
}

Categories