Skip to content
Twilight React Playground
ثيم رائدaren

getMobileOS, resolveAppStoreUrl

functionBeginnerserverbrowserlive demo

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

The demo store's own app links. resolveAppStoreUrl() picks the one that suits the visitor's phone.Try this: choose android, then ios. Open this page on a phone (or with device emulation in developer tools) and keep "detected".
Storefront canvas · en · LTR

getMobileOS(): other (server render) · store.apps: appstore, googleplay

Get the app (App Store first)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/id1148458340
Controls
What a theme writes
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">Get our app</a>;
}

Example

app/components/layout/GetTheApp.tsx
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 without navigator.

  • resolveAppStoreUrl prefers Google Play for android and the App Store for ios and other; each falls back to the other link. It returns undefined when apps is 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) without os calls it too. Pass isClient ? getMobileOS() : 'other'.

Related

Source and docs