/ shared / utils / src / platform.ts
platform.ts
  1  import {
  2      parseUserAgent,
  3      flagsExtension,
  4      compareExtension,
  5  } from '@amp/runtime-detect';
  6  import { launchClient, type LaunchCallback } from './launch/launch-client';
  7  
  8  type NavigatorLike = {
  9      userAgent: string;
 10      maxTouchPoints?: number;
 11  };
 12  
 13  /**
 14   * Detect a Platform descriptor from the browsers user agent.
 15   */
 16  function detectDescriptor(options?: {
 17      window?: Window;
 18      navigator?: NavigatorLike;
 19  }) {
 20      const defaultNavigator: NavigatorLike =
 21          typeof options?.window?.navigator !== 'undefined'
 22              ? options.window.navigator
 23              : {
 24                    userAgent: '',
 25                    maxTouchPoints: 0,
 26                };
 27  
 28      return parseUserAgent(options?.navigator ?? defaultNavigator, {
 29          extensions: [flagsExtension, compareExtension],
 30      });
 31  }
 32  
 33  export type PlatformDescriptor = ReturnType<typeof detectDescriptor>;
 34  
 35  export class Platform {
 36      static detect(
 37          this: typeof Platform,
 38          options?: { window?: Window; navigator?: NavigatorLike },
 39      ) {
 40          const window = options?.window ?? globalThis?.window;
 41          return new this({
 42              window: window,
 43              descriptor: detectDescriptor({
 44                  window: window,
 45                  navigator: options?.navigator,
 46              }),
 47          });
 48      }
 49  
 50      /**
 51       * Descriptor from detecting platform data.
 52       */
 53      readonly descriptor: PlatformDescriptor;
 54  
 55      /**
 56       * Navigator value used to create the platform descriptor.
 57       */
 58      readonly navigator: NavigatorLike;
 59  
 60      /**
 61       * Reference to the platform Window object. This might be `undefined` in some
 62       * environments.
 63       */
 64      readonly window: Window | undefined;
 65  
 66      /**
 67       * User Agent string the platform descriptor was parsed from.
 68       */
 69      readonly ua: string;
 70  
 71      /**
 72       * Browser descriptor for the Platform.
 73       */
 74      readonly browser: PlatformDescriptor['browser'];
 75  
 76      /**
 77       * Browser Engine descriptor for the Platform.
 78       */
 79      readonly engine: PlatformDescriptor['engine'];
 80  
 81      /**
 82       * Operating System descriptor for the Platform.
 83       */
 84      readonly os: PlatformDescriptor['os'];
 85  
 86      constructor(config: {
 87          descriptor: PlatformDescriptor;
 88          window?: Window;
 89          navigator?: NavigatorLike;
 90      }) {
 91          const { descriptor } = config;
 92          this.descriptor = descriptor;
 93          this.navigator = config.navigator ?? descriptor.navigator;
 94          this.window = config.window;
 95  
 96          this.ua = descriptor.ua;
 97          this.browser = descriptor.browser;
 98          this.engine = descriptor.engine;
 99          this.os = descriptor.os;
100      }
101  
102      /**
103       * Check if Apple native applications can be opened on the Platform.
104       */
105      canOpenNative(): boolean {
106          return this.ismacOS() || this.isiOS();
107      }
108  
109      /**
110       * Check if the Platform is running a mobile browser.
111       */
112      isMobile(): boolean {
113          return this.browser.isMobile;
114      }
115  
116      /**
117       * Check if the Platform registers as running the Android operating system.
118       */
119      isAndroid(): boolean {
120          return this.os.isAndroid;
121      }
122  
123      /**
124       * Check if the Platform registers as running the iOS operating system.
125       */
126      isiOS(): boolean {
127          return this.os.isIOS;
128      }
129  
130      /**
131       * Check if the Platform registers as running the iPadOS operating system.
132       */
133      isiPadOS(): boolean {
134          return this.os.isIPadOS;
135      }
136  
137      /**
138       * Check if the Platform registers as running the macOS operating system.
139       */
140      ismacOS(): boolean {
141          return this.os.isMacOS;
142      }
143  
144      /**
145       * Check if the Platform registers as running the Windows operating system.
146       */
147      isWindows(): boolean {
148          return this.os.isWindows;
149      }
150  
151      /**
152       * Check if the Platform registers as running a Linux operating system.
153       */
154      isLinux(): boolean {
155          return this.os.isLinux;
156      }
157  
158      /**
159       * Check if the Platform is running the Apple Safari browser.
160       */
161      isSafari(): boolean {
162          return this.browser.isSafari;
163      }
164  
165      /**
166       * Check if the Platform is running the Google Chrome browser.
167       */
168      isChrome(): boolean {
169          return this.browser.isChrome;
170      }
171  
172      /**
173       * Check if the Platform is running the Mozilla Firefox browser.
174       */
175      isFirefox(): boolean {
176          return this.browser.isFirefox;
177      }
178  
179      /**
180       * Check if the Platform is running the Microsoft Edge browser.
181       */
182      isEdge(): boolean {
183          return this.browser.isEdge;
184      }
185  
186      /**
187       * Get name for the Platform browser.
188       * @deprecated Use `platform.browser.name` directly
189       */
190      clientName(): string {
191          return this.browser.name[0].toUpperCase() + this.browser.name.slice(1);
192      }
193  
194      /**
195       * Get the Platform browser major version number.
196       * @deprecated Use `platform.browser.major` directly
197       */
198      majorVersion(): number {
199          return this.browser.major ?? 0;
200      }
201  
202      /**
203       * Get the Platform browser minor version number.
204       * @deprecated Use `platform.browser.minor` directly
205       */
206      minorVersion(): number {
207          return this.browser.minor ?? 0;
208      }
209  
210      /**
211       * Get the name for the Platform operating system.
212       * @deprecated Use `platform.os.name` directly
213       */
214      osName(): string {
215          return this.os.name;
216      }
217  
218      /**
219       * Attempt to launch a native client for the given web URL.
220       *
221       * The callback is called with a report if the attempt was successful.
222       *
223       * @example
224       * ```javascript
225       * platform.launchClient(
226       *   'https://music.apple.com/browse',
227       *   function ({ link, success }) {
228       *     if (success) {
229       *       console.log(`Opened client with ${link}`);
230       *     } else {
231       *       console.log(`Failed to open client with ${link}`);
232       *     }
233       *   }
234       * );
235       * ```
236       */
237      launchClient(url: string, callback?: LaunchCallback): void {
238          launchClient(url, this, callback);
239      }
240  
241      /**
242       * Check if the platform has full support for playing encrypted HLS content.
243       */
244      hasEncryptedPlaybackSupport(): boolean {
245          return !this.os.isIOS || this.os.gte('17.5');
246      }
247  }
248  
249  export const platform = Platform.detect();