getMobileOS, resolveAppStoreUrl
Detect whether the visitor uses an iPhone or Android device, and pick the store's matching App Store or Google Play link.
import { getMobileOS, resolveAppStoreUrl, MobileOS } from '@salla.sa/twilight-theme-engine/utils';In plain words
Stores with their own mobile app list two links in store.apps: the App Store and Google Play. A "Get our app" button should open the one that suits the visitor's phone.
getMobileOS() reads the browser's user agent (the text a browser sends to describe itself) and returns 'ios', 'android' or 'other'. resolveAppStoreUrl(store.apps, os) returns the matching link, or the other one when the store has only one.
The server cannot see the visitor's device, so use 'other' until the component runs in the browser, as the example does.
Signature
type MobileOS = 'ios' | 'android' | 'other';
function getMobileOS(): MobileOS
function resolveAppStoreUrl(apps?: StoreApps, os?: MobileOS): string | undefined
// os defaults to getMobileOS()
interface StoreApps { // @salla.sa/twilight-theme-engine/types
appstore?: string;
googleplay?: string;
}Try it live
getMobileOS(): other (server render) · store.apps: appstore, googleplay
https://apps.apple.com/sa/app/%D8%B3%D9%84%D8%A9-%D8%AA%D8%AC%D8%A7%D8%B1%D8%A9-%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A%D8%A9-%D8%B3%D9%87%D9%84%D8%A9/id1148458340import { useIsClient } from '@salla.sa/twilight-theme-engine/hooks';
import { useStore } from '@salla.sa/twilight-theme-engine/hooks/useStore';
import { getMobileOS, resolveAppStoreUrl } from '@salla.sa/twilight-theme-engine/utils';
export function GetTheApp() {
const { apps } = useStore();
const isClient = useIsClient();
// 'other' until mounted, so the server HTML and the first browser render agree.
const url = resolveAppStoreUrl(apps, isClient ? getMobileOS() : 'other');
if (!url) return null;
return <a href={url} className="btn btn--primary">Get our app</a>;
}
Example
import { useIsClient } from '@salla.sa/twilight-theme-engine/hooks';
import { useStore } from '@salla.sa/twilight-theme-engine/hooks/useStore';
import { getMobileOS, resolveAppStoreUrl } from '@salla.sa/twilight-theme-engine/utils';
export function GetTheApp() {
const { apps } = useStore();
const isClient = useIsClient();
// 'other' until mounted, so the server HTML and the first browser render agree.
const url = resolveAppStoreUrl(apps, isClient ? getMobileOS() : 'other');
if (!url) return null;
return (
<a href={url} className="btn btn--primary" target="_blank" rel="noreferrer">
Get our app
</a>
);
}
How it behaves
getMobileOS()returns'android'when the user agent contains "android" in any case, and'ios'for iPhone, iPad and iPod, and for a "Macintosh" agent with more than one touch point (iPadOS describes itself as a Mac). Everything else, desktops included, is'other', and so is any runtime withoutnavigator.resolveAppStoreUrlprefers Google Play forandroidand the App Store foriosandother; each falls back to the other link. It returnsundefinedwhenappsis missing or both links are empty, which is common on test stores.theme-tania uses exactly this pair, gated on
useIsClient(), for its "rate the app" link.
Gotchas
Calling
getMobileOS()during render gives the server HTML'other'and an Android browser'android': different markup, a hydration mismatch.resolveAppStoreUrl(apps)withoutoscalls it too. PassisClient ? getMobileOS() : 'other'.
Related
Source and docs
- Engine source:
packages/theme-engine/src/utils/device.ts