How to get the webshim polyfill library working in React? - reactjs

I want to access the geolocation feature from the webshim library but i have failed to figure out the right setup to get it working ?
I am already accessing the inbuilt browser geolocation feature, but want to setup the polyfill in the case of browsers that dont have the geolocation feature.
webshim
import React from "react";
import webshim from 'webshim';
import $ from 'jquery';
class PlayGround extends React.Component{
pickLocation = () => {
console.log("Inside here")
webshim.ready('geolocation', () => {
navigator.geolocation.getCurrentPosition(function(pos){
alert("Thx, you are # latitude: "+ pos.coords.latitude +"/longitude: " + pos.coords.longitude);
});
});
console.log("end inside")
}
}

Using polyfill to fill the support for Geolocations won't work. Getting location from the browser requires native support.
Almost all of the browser supports geolocation, https://caniuse.com/#feat=geolocation
Instead, you should check if a browser has support for geolocation or not. If it's not supported, fail with grace (show some error to the user)
function fetchLocation() {
var options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
navigator.geolocation.getCurrentPosition(success, error, options);
}
function success(pos) {
var crd = pos.coords;
console.log('Your current position is:');
console.log(`Latitude : ${crd.latitude}`);
console.log(`Longitude: ${crd.longitude}`);
console.log(`More or less ${crd.accuracy} meters.`);
}
function error(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}
if(window.location.protocol == "https:" && navigator.geolocation) {
fetchLocation();
} else {
// We cannot access the geolocation, show some error
}

Related

How to In App Purchases with ReactJS and Capacitor

I am using Capacitor to generate both the IOS and Android apps (not using Iconic) - this works well, but we are trying to implement IAP (for IOS only at this stage) and cannot figure it out.
I have followed various guides (https://ionicframework.com/docs/native/in-app-purchase-2 and https://purchase.cordova.fovea.cc/ and https://capacitorjs.com/docs/guides/in-app-purchases) but simply cannot get it working with React (not React Native)
Can someone point me in the right direction, or provide sample code?
You didn't describe what is going wrong, but here's a basic configuration that works for me on iOS.
I'm only including the part about the store:
index.tsx
import { IAPProduct, InAppPurchase2 } from '#ionic-native/in-app-purchase-2';
const startStoreEventListeners = () => {
if (isPlatformMobile()) {
document.addEventListener(
'deviceready',
() => {
const store = InAppPurchase2;
// Needed to use IAP + cordova plugins.
// Set debug messages.
// Default.
store.verbosity = store.QUIET;
// store.verbosity = store.DEBUG;
store.register([
{
id: subMonthly,
type: store.PAID_SUBSCRIPTION,
},
{
id: subAnnual,
type: store.PAID_SUBSCRIPTION,
},
]);
// Upon approval, verify the receipt.
store.when(subMonthly).approved((product: IAPProduct) => {
product.verify();
});
store.when(subAnnual).approved((product: IAPProduct) => {
product.verify();
});
// Upon receipt validation, mark the subscription as owned.
store.when(subMonthly).verified((product: IAPProduct) => {
product.finish();
});
store.when(subAnnual).verified((product: IAPProduct) => {
product.finish();
});
// Track all store errors
store.error((err: Error) => {
debugLog('Store Error', JSON.stringify(err));
});
// https://billing-dashboard.fovea.cc/setup/cordova
store.validator =
'https://validator.fovea.cc/v1/validate?appName=secret';
store.refresh();
startIonic();
},
false,
);
} else {
startIonic();
}
};
startStoreEventListeners();
serviceWorker.unregister();
Note that #ionic-native packages are deprecated and need to be converted.

How to activate a react route and pass data from the service worker?

I have a SPA PWA React app.
It is installed and running in standalone mode on the mobile device (Android+Chrome).
Let's say the app lists people and then when you click on a person it diplays details using /person route.
Now, I'm sending push notifications from the server and receiving them in the service worker attached to the app. The notification is about a person and I want to open that person's details when the user clicks on the notification.
The question is:
how do I activate the /person route on my app from the service worker
and pass data (e.g. person id, or person object)
without reloading the app
From what I understand, from the service worker notificationclick event handler I can:
focus on the app (but how do I pass data and activate a route)
open an url (but /person is not a physical route, and either way - I want avoid refreshing the page)
You can listen for click event for the Notification which you show to the user. And in the handler, you can open the URL for the corresponding person which comes from your server with push event.
notification.onclick = function(event) {
event.preventDefault();
// suppose you have an url property in the data
if (event.notification.data.url) {
self.clients.openWindow(event.notification.data.url);
}
}
Check these links:
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event
https://developer.mozilla.org/en-US/docs/Web/API/Clients/openWindow
To answer my own question: I've used IndexedDB (can't use localStorage as it is synchronous) to communicate between SW and PWA, though I'm not too happy about it.
This is roughly how my service worker code looks (I'm using idb library):
self.addEventListener('notificationclick', function(event) {
const notif = event.notification;
notif.close();
if (notif.data) {
let db;
let p = idb.openDB('my-store', 1, {
upgrade(db) {
db.createObjectStore(OBJSTORENAME, {
keyPath: 'id'
});
}
}).then(function(idb) {
db = idb;
return db.clear(OBJSTORENAME);
}).then(function(rv) {
return db.put(OBJSTORENAME, notif.data);
}).then(function(res) {
clients.openWindow('/');
}).catch(function(err) {
console.log("Error spawning notif", err);
});
event.waitUntil(p);
}
});
and then, in the root of my react app ie in my AppNavBar component I always check if there is something to show:
componentWillMount() {
let self = this;
let db;
idb.openDB('my-store', 1)
.then(function (idb) {
db = idb;
return db.getAll(OBJSTORENAME);
}).then(function (items) {
if (items && items.length) {
axios.get(`/some-additional-info-optional/${items[0].id}`).then(res => {
if (res.data && res.data.success) {
self.props.history.push({
pathname: '/details',
state: {
selectedObject: res.data.data[0]
}
});
}
});
db.clear(OBJSTORENAME)
.then()
.catch(err => {
console.log("error clearing ", OBJSTORENAME);
});
}
}).catch(function (err) {
console.log("Error", err);
});
}
Have been toying with clients.openWindow('/?id=123'); and clients.openWindow('/#123'); but that was behaving strangely, sometimes the app would stall, so I reverted to the IndexedDB approach.
(clients.postMessage could also be the way to go though I'm not sure how to plug that into the react framework)
HTH someone else, and I'm still looking for a better solution.
I had a similar need in my project. Using your's postMessage tip, I was able to get an event on my component every time a user clicks on service worker notification, and then route the user to the desired path.
service-worker.js
self.addEventListener("notificationclick", async event => {
const notification = event.notification;
notification.close();
event.waitUntil(
self.clients.matchAll({ type: "window" }).then(clientsArr => {
if (clientsArr[0]) {
clientsArr[0].focus();
clientsArr[0].postMessage({
type: "NOTIFICATION_CLICK",
ticketId: notification.tag,
});
}
})
);
});
On your react component, add a new listener:
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("message", message => {
if (message.data.type === "NOTIFICATION_CLICK") {
history.push(`/tickets/${message.data.ticketId}`);
}
});
}
}, [history]);

How can I utilise ApplicationInsights-JS in a service worker?

I am currently using ApplicationInsights-JS in my progressive web app. It works in my react components as I can import what I need from the relevant npm packages.
In my service worker however, I can only import logic using importScripts.
I did manage to find a CDN for ApplicationInsights-JS on their Github page however it seems that in order to initialise app insights using this library you need to have access to window in order to store the appinsights, which you cannot do from a service worker.
I tried to use the web snippet approach since the CDN seemed to be
related to that particular library, but I can't use window and am not sure how else to implement this solution.
This is a copy paste of the suggested snippet to init the app insights object from: https://github.com/Microsoft/ApplicationInsights-JS
importScripts('https://az416426.vo.msecnd.net/beta/ai.2.min.js');
const sdkInstance = 'appInsightsSDK';
window[sdkInstance] = 'appInsights';
const aiName = window[sdkInstance];
const aisdk =
window[aiName] ||
(function(e) {
function n(e) {
i[e] = function() {
const n = arguments;
i.queue.push(function() {
i[e](...n);
});
};
}
let i = { config: e };
i.initialize = !0;
const a = document;
const t = window;
setTimeout(function() {
const n = a.createElement('script');
(n.src = e.url || 'https://az416426.vo.msecnd.net/next/ai.2.min.js'),
a.getElementsByTagName('script')[0].parentNode.appendChild(n);
});
try {
i.cookie = a.cookie;
} catch (e) {}
(i.queue = []), (i.version = 2);
for (
const r = [
'Event',
'PageView',
'Exception',
'Trace',
'DependencyData',
'Metric',
'PageViewPerformance'
];
r.length;
)
n(`track${r.pop()}`);
n('startTrackPage'), n('stopTrackPage');
const o = `Track${r[0]}`;
if (
(n(`start${o}`),
n(`stop${o}`),
!(
!0 === e.disableExceptionTracking ||
(e.extensionConfig &&
e.extensionConfig.ApplicationInsightsAnalytics &&
!0 ===
e.extensionConfig.ApplicationInsightsAnalytics
.disableExceptionTracking)
))
) {
n(`_${(r = 'onerror')}`);
const s = t[r];
(t[r] = function(e, n, a, t, o) {
const c = s && s(e, n, a, t, o);
return (
!0 !== c &&
i[`_${r}`]({
message: e,
url: n,
lineNumber: a,
columnNumber: t,
error: o
}),
c
);
}),
(e.autoExceptionInstrumented = !0);
}
return i;
})({ instrumentationKey: 'xxx-xxx-xxx-xxx-xxx' });
(window[aiName] = aisdk),
aisdk.queue && aisdk.queue.length === 0 && aisdk.trackPageView({});
I get window is not defined which is expected, but I'm not sure how else I can make use of this library from the service worker.
Has anyone else had a similar implementation in which they successfully logged telemetry using ApplicationInsights from a service worker?
I realised that I was over complicating this.
Since I only needed to track a custom event, and didn't need all the automated page tracking etc that appInsights does, I ended up doing a fetch from my service worker.
I just copied the header and body format from the requests that I made using my react pages.
The below successfully logged telemetry to my app insights dashboard:
fetch(url, {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify([
{
time: '2019-05-02T15:56:37.589Z',
iKey: 'INSTRUMENTATION_KEY',
name:
'Microsoft.ApplicationInsights.INSTRUMENTATION_KEY.Event',
tags: {
'ai.user.id': 'l6Tey',
'ai.session.id': 'TL+Ry',
'ai.device.id': 'browser',
'ai.device.type': 'Browser',
'ai.operation.id': 'HUfNE',
SampleRate: '100',
// eslint-disable-next-line no-script-url
'ai.internal.sdkVersion': 'javascript:2.0.0-rc4'
},
data: {
baseType: 'EventData',
baseData: {
ver: 2,
name: 'Testing manual event',
properties: {},
measurements: {}
}
}
}
])
})
.then(json)
.then(function(data) {
})
.catch(function(error) {
});
I've almost managed to use Microsoft Application Insights in our app's service worker.
The key parts are:
Using the lightweight version of appInsights (see this small remark at 4th step) with importScripts('https://az416426.vo.msecnd.net/next/aib.2.min.js').
Initialize an appInsights object:
appInsights = new Microsoft.AppInsights.AppInsights({ instrumentationKey: "[replace with your own key]" });
when track needed (during onpush event or onnotificationclick), go for appInsight.track({ eventItemFields }) then appInsights.flush().
I've said "almost" because the flush part seems to not working, I've got: "Sender was not initialized" internal error after enabling debugging.
I will publish here a working sample code if I successfully manage this issue.
References:
https://github.com/Azure-Samples/applicationinsights-web-sample1/blob/master/testlightsku.html
This response to the question: How to add analytics for Push notifications.
Using the Web SDK in a service worker is troublesome. The full version depends on a window object, while the basic SDK depends on Beacon or XmlHttpRequest for sending the messages (in file https://github.com/microsoft/ApplicationInsights-JS/blob/master/channels/applicationinsights-channel-js/src/Sender.ts):
if (!_self._senderConfig.isBeaconApiDisabled() && Util.IsBeaconApiSupported()) {
_self._sender = _beaconSender;
} else {
if (typeof XMLHttpRequest !== undefined) {
const xhr:any = getGlobalInst("XMLHttpRequest");
if(xhr) {
const testXhr = new xhr();
if ("withCredentials" in testXhr) {
_self._sender = _xhrSender;
_self._XMLHttpRequestSupported = true;
} else if (typeof XDomainRequest !== undefined) {
_self._sender = _xdrSender; // IE 8 and 9
}
}
}
}
At the moment Application Insights SDK does not seem to support service workers. Rajars solution seems to be the best option for now.
Update: There is an issue in the Github Repo about this: https://github.com/microsoft/ApplicationInsights-JS/issues/1436
A suggestion that works is by using the basic/lightweight version of Application Insights (as mentioned by Rajar) and adding a XMLHttpRequest polyfill (that uses the fetch api) before inititializing Application Insights. After that you can use the lightweight version.
An example can be found here: https://github.com/Pkiri/pwa-ai
I was trying to use AppInsightsSDK in E2E tests environment (pupeteer) and when I tried to log event or metric I got with "Sender was not initialized" error.
As #Pkiri mentioned one would need XMLHttpRequest polyfill to solve the issue. Although my scenario is not directly related to Service worker I wanted to mention that #Pkiri answer is not entirely true, because one can also use globalThis, self, window or global to get the same result according to SDK source code function getGlobalInst("XMLHttpRequest"); resolves to
function getGlobal() {
if (typeof globalThis !== strShimUndefined && globalThis) {
return globalThis;
}
if (typeof self !== strShimUndefined && self) {
return self;
}
if (typeof window !== strShimUndefined && window) {
return window;
}
if (typeof global !== strShimUndefined && global) {
return global;
}
return null;
}
And for my scenario this was a valid solution
const appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'AppInsights_InstrumentationKey',
},
});
global.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
global.appInsights = appInsights.loadAppInsights();

Option to “Select Image From Gallery or Camera”

I want to make an option to "Select Image From Gallery or Camera". I have tried many modules but they are only providing access to the gallery directly. I am using expo tool for creating a react native application. First I want a popup to open then then the user has to pick an option then the user is redirected according to that option. If you have any suggestion, please help me.
I´ve seen it done with React Native Image Picker, look for it in github:
https://github.com/react-community/react-native-image-picker
Add dependencies:
dependencies {
compile project(':react-native-image-picker')
}
Add permissions:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Usage:
var ImagePicker = require('react-native-image-picker');
// More info on all the options is below in the README...just some common use cases shown here
var options = {
title: 'Select Avatar',
customButtons: [
{name: 'fb', title: 'Choose Photo from Facebook'},
],
storageOptions: {
skipBackup: true,
path: 'images'
}
};
/**
* The first arg is the options object for customization (it can also be null or omitted for default options),
* The second arg is the callback which sends object: response (more info below in README)
*/
ImagePicker.showImagePicker(options, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
}
else if (response.error) {
console.log('ImagePicker Error: ', response.error);
}
else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
}
else {
let source = { uri: response.uri };
// You can also display the image using data:
// let source = { uri: 'data:image/jpeg;base64,' + response.data };
this.setState({
avatarSource: source
});
}
});
If you would like to directly start just the camera or the gallery, use it like this:
// Launch Camera:
ImagePicker.launchCamera(options, (response) => {
// Same code as in above section!
});
// Open Image Library:
ImagePicker.launchImageLibrary(options, (response) => {
// Same code as in above section!
});
Hope it helps.

React Native FBSDK onLoginFinished push to new screen

I'm somewhat new to using react native and I'm trying to implement Facebook login with my app using the react native fbsdk.
I've gotten it to the point where it will ask for permissions from the user's Facebook profile, then return to the app with a success alert message. Instead of displaying the alert onLoginFinished, I want to push to a new screen.
This is my login button code in the main constructor class:
constructor(props){
super(props);
this.goToNewPage = this.goToNewPage.bind(this);
const infoRequest = new GraphRequest(
'/me',
{parameters: {
fields: {
string: 'email,first_name,last_name,id,gender' // what you want to get
}
}},
this._responseInfoCallback,
);
Login = React.createClass({
render: function() {
return (
<View >
<LoginButton
navigator={this.props.navigator}
style = {{marginLeft:55, marginRight:55, height:50, width:300}}
onLoginFinished={
(error, result) => {
if (error) {
alert("Login failed with error: " + result.error);
} else if (result.isCancelled) {
alert("Login was cancelled");
} else {
alert("Login was successful with permissions: " + result.grantedPermissions)
new GraphRequestManager().addRequest(infoRequest).start();
this.goToNewPage;
}
}
}
onLogoutFinished={() => alert("User logged out")}
/>
</View>
);
}
});
} //closes constructor
_responseInfoCallback(error: ?Object, result: ?Object) {
if (error) {
alert('Error fetching data: ' + error.toString());
} else {
alert('Success fetching data: ' + result.toString());
console.log(result);
}
}
goToNewPage(){
console.log("Hello from go to new page function");
this.props.navigator.push({
id: 'newPage',
name: 'Going To New Page',
});
}
But after the success alerts are displayed, it does not push to the new page. It seems like this.props.navigator is not recognized in my Login class, and I'm not sure why. Any help at all would be greatly appreciated
Turns out the problem was that I declared the login class in my constructor. Moving it to a componentWillMount function fixed it for me!
Currently you are calling goToNewpage() method in error, result block of LoginButton component and in any kind of block we can't access current class reference "this" directly because it loses its reachability.
Then you should create a global variable for this component as
var _this;
and assign current reference of class in component constructor like:
_this = this;
and should use _this.props.navigator.push in place of this.props.navigator.push within your goToNewpage() method. Thanks!

Resources