Why does the Digital Goods API's getService method reject with clientAppUnavailable in my TWA? - trusted-web-activity

We have a TWA/PWA in the Play store. We want to start selling Play subscriptions through our app.
We followed the Chrome Developer guides to implementing the necessary steps in the Android project and the PWA. We also checked our code against the Android Browser Helper demo and the code generated by the latest Bubblewrap version. (We did not use Bubblewrap to generate our project, however.) As far as we can see, our functionality is equivalent. We tried bundling the same versions of the androidbrowserhelper:androidbrowserhelper and androidbrowserhelper:billing dependencies as Bubblewrap. We also tried downgrading to those used in TWA Play Billing Demo.
With these prerequisites (hopefully) in place in the Android wrapper, we configured a subscription product (with a base plan) in the Play console.
Now we are trying to test fetching product details using the Digitial Goods API, following the examples linked above.
We can successfully detect the availability of the API in our context. We also find the call to window.getDigitalGoodsService('https://play.google.com/billing') successfully resolves.
We await the service object before calling service.getDetails([ 'our_sku' ]), but this call rejects with an error clientAppUnavailable.
We cannot find any clarification related to this error, either in resources about Android Browser Helper nor the (Android) Google Play Billing Library.
The following snippets illustrate the relevant code (IMO) in our codebases.
build.gradle
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.4.0'
implementation 'com.google.androidbrowserhelper:billing:1.0.0-alpha09'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'io.sentry:sentry-android:6.1.2'
implementation platform('com.google.firebase:firebase-bom:30.1.0')
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-firestore'
}
AndroidManifest.xml
<service
android:name=".DigitalGoodsDelegationService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
DigitalGoodsDelegationService.java
import com.google.androidbrowserhelper.playbilling.digitalgoods.DigitalGoodsRequestHandler;
import com.google.androidbrowserhelper.trusted.DelegationService;
public class DigitalGoodsDelegationService extends DelegationService{
#Override
public void onCreate() {
super.onCreate();
registerExtraCommandHandler(new DigitalGoodsRequestHandler(getApplicationContext()));
}
}
PWA
const SERVICE_ID = 'https://play.google.com/billing';
window.getDigitalGoodsService(SERVICE_ID)
.then((service) => {
if (!service) throw new Error(ERROR_MESSAGES.noPlayBilling);
// eslint-disable-next-line no-console
console.log('Got service');
return service.getDetails(SKUS);
})
.then(products => {
if (!products || !products.length) {
throw new Error(ERROR_MESSAGES.noProducts);
}
...
})
.catch(err => {
...
});

We resolved the error by compiling against an upgraded version of the Android API. (Before, compileSdkVersion was set to 30 in our module-level build.gradle. We upgraded this to 31.)

Related

PWA InjectManifest workbox console / debug output suddenly disabled (Vue.js / Quasar Framework)

I'm building a PWA with Vue.js / Quasar Framework and recently added the PWA capability. I changed the "workboxPluginMode" property to "InjectManifest" and at first Workbox gave me debug / console as expected.
Also, the "custom-service-worker.js" definetly gets picked up by the process because it displays an error when i remove this line:
precacheAndRoute(self.__WB_MANIFEST)
So the file is recognized and actively using the defined caching strategies (i think), but it won't provide me any debug info or console.log's on console anymore. I really don't know what i have changed to do that.
My "custom-service-worker.js" looks like this:
import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'
console.log('custom service worker active')
// Use with precache injection
precacheAndRoute(self.__WB_MANIFEST)
// Caching strategies
registerRoute(
({url}) => {
console.log(url)
// url.pathname.startsWith('/images')
},
new StaleWhileRevalidate()
);
self.addEventListener('fetch', function(event) {
console.log(event)
event.respondWith(fetch(event.request));
})
I have no clue why, but the console debug output of workbox was only displayed in my default browser (Vivaldi). I wanted to work on plain Chromium because I couldn't install my PWA with Vivaldi (no installation prompt popped up), but that also works now. Have literally no explanation for this, but this is PWA development I guess, it is what it is. Anyways, problem solved for me.

Where can I find my MainActivity.java when running a Android simulator

I just mad my first React native project and I'm trying to lock my screen orientation to landscape. I installed this package
https://github.com/yamill/react-native-orientation#configuration
with npm install react-native link react-native-orientation and now I need to
Implement onConfigurationChanged method in MainActivity.java
So this code
import android.content.Intent; // <--- import
import android.content.res.Configuration; // <--- import
public class MainActivity extends ReactActivity {
......
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
......
}
But where can I find that? From what I've seen is that you can find it in a android studio project. But I don't have a project, I just go to the AVD manager to run my virtual device. So where am I supposed to add this code?
The whole aim of expo is to handle everything related to android or ios for you.
Thus, you won't have access to these two folders unless you eject your app from expo as it's explained here on the official doc.
However, if your goal is to handle screen orientation, expo has a built-in API for you that you may find here.
Or even simpler, you could just update your app.json and add this line:
"expo": {
...
"orientation": "landscape"
...
}
Have a look at the official doc if you want to see everything that you can handle with this configuration file.

Vue Cli 3 how to use the official PWA plugin ( Service Worker )

on my first vue project attempting to wrestle with the official PWA plugin ( https://github.com/yyx990803/register-service-worker ).
My specific problem: capturing the registered service worker and using it for anything. The github readme shows the exact file that is produced, and there seems to be zero documentation about how to work with this service worker once it is instantiated ( do I capture the registration instance? if so, how? )
I found this issue: https://github.com/vuejs/vue-cli/issues/1481
and am providing a better place to talk about this, as I haven't been able to find any example code or clear documentation about how to work with this.
If anyone has some sample code, please share. Vue and the new cli are incredible tools, documenting things like this is a necessary step forward to increasing the adoption of the platform
As already pointed out, it's more of a "service workers" issue than a "vue cli" one.
First of all, to make sure we're on the same page, here's what the boilerplate content of registerServiceWorker.js should look like (vue cli 3, official pwa plugin):
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n'
)
},
cached () {
console.log('Content has been cached for offline use.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}
If you haven't changed the BASE_URL variable in your .env file, then it should correspond to the root of your vue app. You have to create a file named service-worker.js in the public folder (so that it's copied into your output directory on build).
Now, it is important to understand that all the code in the registerServiceWorker.js file does is register a service worker and provide a few hooks into its lifecycle. Those are typically used for debugging purposes and not to actually program the service worker. You can understand it by noticing that the registerServiceWorker.js file will be bundled into the app.js file and run in the main thread.
The vue-cli 3 official PWA plugin is based on Google's workbox, so to use the service worker, you'll have to first create a file named vue.config.js at the root of your project and copy the following code in it:
// vue.config.js
module.exports = {
// ...other vue-cli plugin options...
pwa: {
// configure the workbox plugin
workboxPluginMode: 'InjectManifest',
workboxOptions: {
// swSrc is required in InjectManifest mode.
swSrc: 'public/service-worker.js',
// ...other Workbox options...
}
}
}
If you already have created a vue.config.js file, then you just have to add the pwa attribute to the config object. Those settings will allow you to create your custom service worker located at public/service-worker.js and have workbox inject some code in it: the precache manifest. It's a .js file where a list of references to your compiled static assets is stored in a variable typically named self.__precacheManifest. You have to build your app in production mode in order to make sure that this is the case.
As it is generated automatically by workbox when you build in production mode, the precache manifest is very important for caching your Vue app shell because static assets are usually broken down into chunks at compile time and it would be very tedious for you to reference those chunks in the service worker each time you (re)build the app.
To precache the static assets, you can put this code at the beginning of your service-worker.js file (you can also use a try/catch statement):
if (workbox) {
console.log(`Workbox is loaded`);
workbox.precaching.precacheAndRoute(self.__precacheManifest);
}
else {
console.log(`Workbox didn't load`);
}
You can then continue programming your service worker normally in the same file, either by using the basic service worker API or by using workbox's API. Of course, don't hesitate to combine the two methods.
I hope it helps !
as an addition to the answer above: I wrote a small guide on how to go further and add some functionality to the custom service-worker, using the setup above. You can find it here.
Four main things to keep in mind:
configure Workbox in vue.config.js to InjectManifest mode, pointing the swSrc key to a custom service-worker file in /src
In this custom service-worker, some lines will be added automatically in the Build process for importing the precache-manifest and workbox CDN. Following lines need to be added in the custom service-worker.js file to actually precache the manifest files:
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
Listen to registration events in the registerServiceWorker.js file. You can use the registration object that is passed as first argument to the event handlers to post messages to the service-worker.js file:
...
updated(registration) {
console.log("New content is available; please refresh.");
let worker = registration.waiting
worker.postMessage({action: 'skipWaiting'})
},
...
Subscribe to messages in the service-worker.js file and act accordingly:
self.addEventListener("message", (e)=>{
if (e.data.action=='skipWaiting') self.skipWaiting()
})
Hope this helps someone.

Cannot get current position without GPS on react-native with navigator.geolocation

Brief summary after discussion and answers:
using EXPO sdk you cannot get the device location without grant FINE_LOCATION in android. FINE_LOCATION is the only method to get location, so, you cannot get the hardware.network.location. That means: with android > 6 you cannot get the current location using WIFI/mobile networks, you must enable Location.
Expo github discussion: https://github.com/expo/expo/issues/1339
The initial problem:
im working on a react-native application, using expo and react-native-maps, and i need to get the latitude and longitud of the user current position.
Im using navigator.geolocation API for that
with the GPS turned on i have no problems, but i need to get the current position without GPS, based on the network provider.
The problem is that when the application runs with expo on androiod > 6 i get this error:
21:50:53: Finished building JavaScript bundle in 449ms 21:50:55:
Location services are disabled
- node_modules\react-native\Libraries\BatchedBridge\NativeModules.js:80:57
in
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:347:19
in __invokeCallback
- ... 4 more stack frames from framework internals
In IOs and android <=5 it works great.
Here is the code of the component:
class MyComponent extends Component {
componentWillMount = () => {
this.getCurrentLocation();
}
getCurrentLocation = () =>
navigator.geolocation.getCurrentPosition(
(position) => {
let currentUserPosition = position.coords;
alert(JSON.stringify(currentUserPosition));
},
(error) => {
console.log(error);
},
{
enableHighAccuracy: false,
timeout: 20000,
maximumAge: 0,
distanceFilter: 10
}
);
}
And this are my package.json depencendies:
"dependencies": {
"expo": "23.0.4",
"native-base": "^2.3.5",
"react": "16.0.0",
"react-native": "0.50.4",
"react-native-extend-indicator": "^0.1.2",
"react-native-maps": "^0.19.0",
"react-native-maps-directions": "^1.3.0",
"react-native-router-flux": "4.0.0-beta.21",
"react-navigation": "1.0.0-beta.21",
"react-navigation-redux-debouncer": "^0.0.2",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
}
I expect that navigator.geolocation get the location based on the network provider in that situation (without gps), the specification saids that..
i also tried with the Geolocation API of expo (tried this example: https://snack.expo.io/#schazers/expo-map-and-location-example) , and with the GPS turned OFF i cant get my location..
so.. is there a way to achieve what i want? i am missing something?
EDIT (EXPO CONTRIBUTOR ANSWER):
I have posted the same at expo github (https://github.com/expo/expo/issues/1339), according to them it is imposible to get the current position using navigator.geolocation without any level of Location enabled in a android device.. so .. the only thing that could happen is that android versions older than 5 has location enabled by default and you can turn on just the GPS, and the versions 6 and forward you must specify the location level ..
any ideas?
EDIT2 (IMPORTANT !!):
I have confirmed this:
this is security settings of a Android 6 device, by default it uses GPS, i think that android 5 or lower doesnt, so thats the thing.. when i use the 2nd option it gets me the location !
the fact is that forcing a user to enable Location is like "hey, use your GPS!", and there are a lot of applications that gives you a aproximated position without turning on Location (like Uber for example), so the question is, is there a way that ONLY using wifi get the location with this api?
The problem you are dealing with here is with EXPO, because the location request API has changed after Android 5(API 21).
Before API 21, you needed to add to ACCESS_FINE_LOCATION (Both Wifi/Network and GPS providers) and ACCESS_COARSE_LOCATION (only GPS permission) location permissions. However, since Android 5, the apis changes to android.hardware.location.network and android.hardware.location.gps.
When your target users include both Android 5 +/-. you need to add both permission types to your android manifest, for example:
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- for Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
<uses-feature android:name="android.hardware.location.network" />
</manifest>
However, it seems that the people in expo, only included, the new permission in the android manifest. So what you need to do is to change android manifest. However, expo only gives access to pure JS part of the program and not to native codes. As a result, you need to detach from EXPO to add native modules or changes.
In expo documentation, the detachment process is explained in this link, the procedure is simple:
install expo package globally using npm:
npm install -g exp
Inside your project directory, run detachment code, it will build android and ios folders in your project directory:
exp detach
Then you can make the changes you need in android manifest file.
Common bug in Android. Try without using the third parameter:
getCurrentLocation = () =>
navigator.geolocation.getCurrentPosition(
(position) => {
let currentUserPosition = position.coords;
alert(JSON.stringify(currentUserPosition));
},
(error) => {
console.log(error);
}
);
So, I just digged a little into the code and basically all the LocationModule does is send the request directly to Android.
It just calls this method:
https://developer.android.com/reference/android/location/LocationManager.html#getLastKnownLocation(java.lang.String)
Now, if you read that, the location says FINE_LOCATION or COARSE_LOCATION.
Usually one just add the fine location permissions, but I am thinking that maybe to access the Wifi location you need to add the coarse permissions into the app. Do you have both?
https://developer.android.com/reference/android/Manifest.permission.html#ACCESS_COARSE_LOCATION
If not, I would add that permissions into the app and try again.
I was having the same issue and tried a lot of libraries to get the user location. navigator.geolocation API did not gave me location every time on android instead it just give mostly timeout error only. So then I tried react native background geolocation that start using the gps continuously then I find react native geolocation service and the author clearly mentioned that:
Since this library was meant to be a drop-in replacement for the RN's
Geolocation API, the usage is pretty straight forward, with some extra
error cases to handle.
thats what I needed always and I think you should also try this.
Have a look on this AndroidManifest file, the author is using all type of location.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.agontuk.RNFusedLocation">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
for the test example you'll simply follow the same instruction that RN Geolocation API suggesting. Here is the simple example using react native geolocation service:
import Geolocation from 'react-native-geolocation-service';
componentDidMount() {
// Instead of navigator.geolocation, just use Geolocation.
if (hasLocationPermission) {
Geolocation.getCurrentPosition(
(position) => {
console.log(position);
},
(error) => {
// See error code charts below.
console.log(error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
}
}
So in the third parameter of the getCurrentPosition use enableHighAccuracy false to use network to get user location and true to get more accurate location using gps. See this option documentation.
Author of this repo also provided the example project you can try it with enableHighAccuracy false and turn location off for the first time but when you run the app it will ask for the permission of the location it will show app want to access wifi or cell network provided location.
As you are using expo you cannot use react native geolocation service for that you need to follow Mojtaba answer for detachment and get the android and iOS project so that you can configure both projects. Let me know if you need more information.
In my case, I solved my issue by adding the following in AndroidManifest.xml.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
First of all give permissions in your app
For Ios: You need to include the NSLocationWhenInUseUsageDescription key in Info.plist
For android:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
From here we get lat and longitude
CODE ==>
navigator.geolocation.getCurrentPosition(
(position) => {
const initialPosition = JSON.stringify(position);
// console.log(initialPosition);
this.setState({ initialPosition });
},
//(error) => alert(error.message),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
this.watchID = navigator.geolocation.watchPosition((position) => {
const lastPosition = JSON.stringify(position);
//this.setState({ longitude: position.coords.longitude, latitude: position.coords.latitude });
//this._getWheaterData();
AsyncStorage.setItem('latitude',position.coords.latitude.toString())
AsyncStorage.setItem('longitude',position.coords.longitude.toString())
},
// (error) => alert(error.message),
// console.log(error),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 });

How can i check app installed in react native code

i tried to check other app install in my react native project, I'm used module like: https://www.npmjs.com/package/react-native-check-app-install
But always got this error:
Cannot read property 'pkgName' of undefined
Here is my code:
AppInstalledChecker
.isAppInstalledAndroid('com.skype.raider')
.then((isInstalled) => {
// isInstalled is true if the app is installed or false if not
console.log('App Skype status: ', isInstalled);
});
Anyone can suggest me one way so check app install in react native (both: iOS/android)
install this
https://github.com/KjellConnelly/react-native-shared-group-preferences
and
async check() {
try {
await SharedGroupPreferences.isAppInstalledAndroid("com.farsitel.bazaar")
// IF IS INSTALL
} catch (e) {
// IF IS NOT INSTALL
}
}
Google Play considers the list of installed apps to be personal and sensitive user data.
As we are using
AppInstalledChecker
.isAppInstalledAndroid()
method for checking app installed check, for that we have to white-list the queries in manifest.xml
Reference : https://developer.android.com/training/package-visibility
<queries>
<package android:name="com.instagram.android"/>
…
</queries>
For adding Queries need to upgrade build gradle version:
new default settings and features for package visibility in Android 11 that need to add  you must update your android gradle plugin version
Reference: How to fix "unexpected element <queries> found in <manifest>" error?
I have updated from 3.5.2 to 4.0.2
Now react-native-check-app-install module working as expected
Hope this is resolved!
Android
I. For app's which has deep links like 'waze://', 'mapsme://' you can use:
import { Linking } from 'react-native'
...
Linking.canOpenURL('waze://ul?ll=${latitude},${longitude}&navigate=yes')
OR
II. You can use for absolutely all apps (for example with deep links like "https://...")
https://github.com/KjellConnelly/react-native-shared-group-preferences
iOS
import { Linking } from 'react-native'
...
Linking.canOpenURL(iOS_app_URL_Scheme)
...
where iOS_app_URL_Scheme you can find via Google for each separate app. Like "waze://", "comgooglemaps://", "osmandmaps://" etc