I'm developing a Trusted Web Activity (TWA) app using PWABuilder and am facing a challenge with user permissions and notifications. I need to differentiate between users accessing the app through the native TWA and those using the web app directly. This is crucial for:
Requesting notification permissions appropriately (APN for iOS, WebPush for Android). Saving subscription details to the database accurately. I've explored setting a custom user agent or custom headers within the TWA to achieve this, but haven't found a reliable solution yet. I've tried modifying the manifest.json without success.
Here's a sample React snippet illustrating my approach:
{isIOSApp() && (
<APNPermission />
)}
{isAndroidApp() && (
<WebPush />
)}
I've been able to set a custom user agent in my iOS code using the WebView component, but I haven't found a similar direct solution for Android within the TWA context. I've explored the following approaches without success:
Relying on document.referrer: This proved unreliable, especially when loading the app from external links or after Google login.
Modifying manifest.json: Appending a query parameter to start_url isn't feasible due to existing query parameters in links from emails and other sources.
Tweaking LauncherActivity: I investigated overriding methods within the com.google.androidbrowserhelper.trusted.LauncherActivity class, but I'm unsure which method would be appropriate for setting custom data.
I've analyzed the com.google.androidbrowserhelper.trusted.LauncherActivity class and experimented with overriding methods to potentially set custom data. Here are my findings:
Overridden Methods:
onCreate(Bundle savedInstanceState): I added log statements to track intent extras and saved instance state, but this method doesn't seem directly relevant for setting headers or user agents.
getLaunchingUrl(): I logged the launching URL, but this method primarily retrieves it rather than modifying it.
createTwaLauncher(): I logged the TWA launcher creation process, but I haven't found a way to customize headers within this method.
launchTwa(): I logged the launch process, but this method doesn't offer direct control over headers.
getCustomTabsCallback(): I created a custom CustomTabsCallback implementation (MyQualityEnforcer) to explore potential interactions, but I haven't successfully sent messages to the web app yet.
Below are the functions which i wrote to test the sequences.
@Override
protected void onCreate(Bundle savedInstanceState) {
Intent i = super.getIntent();
i.putExtra("my app", "company app 2");
Bundle iExtras = i.getExtras();
if (savedInstanceState == null) {
Log.d(tag, "2.onCreate Intent.getExtras " + i.getExtras().toString());
super.onCreate(iExtras);
} else {
Log.d(tag, "2.onCreate savedInstanceState " + savedInstanceState.toString());
super.onCreate(savedInstanceState);
}
Log.d(tag, "2.onCreate launcher activity last line");
}
@Override
protected Uri getLaunchingUrl() {
Log.d(tag, "4.getLaunchingUrl");
// Get the original launch Url.
Uri uri = super.getLaunchingUrl();
Log.d(tag, "4.getLaunchingUrl Launching URL: " + uri);
return uri;
}
@Override
protected TwaLauncher createTwaLauncher() {
Log.d(tag, "5.createTwaLauncher twa launcher created");
TwaLauncher l = super.createTwaLauncher();
Log.d(tag, "5.createTwaLauncher twa launcher created " + l.getProviderPackage());
return l;
}
@Override
protected void launchTwa() {
Log.d(tag, "3.launchTwa main fn starting");
super.launchTwa();
Log.d(tag, "3.launchTwa executed");
}
@Override
protected CustomTabsCallback getCustomTabsCallback() {
Log.d(tag, "6.CustomTabsCallback ");
CustomTabsCallback q = new MyQualityEnforcer();
Bundle b = new Bundle();
b.putString("myapp", "company app");
q.onMessageChannelReady(b);
Log.d(tag, "6.CustomTabsCallback " + q.toString());
return q;
}
I also tried to override the QualityEnforcer to check if I can send a message to the webapp javascript, but couldn't figure it out how yet. Below is the class
public class MyQualityEnforcer extends QualityEnforcer {
private final String tag = "MyLauncherActivity";
@Override
public void onMessageChannelReady(Bundle extras) {
// Handle the URL update event here
Log.i(tag, "URL updated: " + extras.toString());
}
}
Logs
---------------------------- PROCESS STARTED (9972) for package com.company.app.twa ----------------------------
2024-01-10 16:29:56.370 9972-9972 MyLauncherActivity com.company.app.twa D 1. hello worldWed Jan 10 16:29:56 GMT+11:00 2024
2024-01-10 16:29:56.604 9972-9972 MyLauncherActivity com.company.app.twa D 2.onCreate Intent.getExtras Bundle[{my app=company app 2}]
2024-01-10 16:29:56.617 9972-9972 MyLauncherActivity com.company.app.twa D 3.launchTwa main fn starting
2024-01-10 16:29:56.621 9972-9972 MyLauncherActivity com.company.app.twa D 4.getLaunchingUrl
2024-01-10 16:29:56.622 9972-9972 MyLauncherActivity com.company.app.twa D 4.getLaunchingUrl Launching URL: https://amazing-domain.example.com/
2024-01-10 16:29:56.623 9972-9972 MyLauncherActivity com.company.app.twa D 5.createTwaLauncher twa launcher created
2024-01-10 16:29:56.639 9972-9972 MyLauncherActivity com.company.app.twa D 5.createTwaLauncher twa launcher created com.android.chrome
2024-01-10 16:29:56.639 9972-9972 MyLauncherActivity com.company.app.twa D 6.CustomTabsCallback
2024-01-10 16:29:56.641 9972-9972 MyLauncherActivity com.company.app.twa I URL updated: Bundle[{myapp=company app}]
2024-01-10 16:29:56.650 9972-9972 MyLauncherActivity com.company.app.twa D 6.CustomTabsCallback com.company.app.twa.MyQualityEnforcer@a6145b7
2024-01-10 16:29:57.339 9972-9972 MyLauncherActivity com.company.app.twa D 3.launchTwa executed
2024-01-10 16:29:57.347 9972-9972 MyLauncherActivity com.company.app.twa D 2.onCreate launcher activity last line
I will be very happy if someone can give me some clue to detect the android device running as Trusted Web Activity using JavaScript.
Thanks.
One way you could do this is:
This way you don't need to modify any of PWABuilder's Android code and instead you keep the logic in your app's code.