Skip to content

Commit

Permalink
Merge pull request #55 from shontzu-deriv/shontzu/FEQ-2199/adding-mob…
Browse files Browse the repository at this point in the history
…ileOsDetect-utils-and-constants

[CFDS] shontzu/FEQ-2199/adding-mobileOsDetect-utils-and-constants
  • Loading branch information
niloofar-deriv authored May 23, 2024
2 parents f7513e1 + 7abd37b commit db07935
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as LocalStorageConstants from "./localstorage.constants";
import * as URLConstants from "./url.constants";
import * as ValidationConstants from "./validation.constants";
import * as BrandConstants from "./brand.constants";
import * as MobileDevicesConstants from "./mobile-devices.constants";

export {
AppIDConstants,
Expand All @@ -14,4 +15,5 @@ export {
URLConstants,
ValidationConstants,
BrandConstants,
MobileDevicesConstants,
};
12 changes: 12 additions & 0 deletions src/constants/mobile-devices.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @description
* This pattern matches any string that contains a sequence of three uppercase letters followed by a hyphen.
* The sequence must be a word on its own (i.e., it must be surrounded by word boundaries).
* The 'g' flag is used for global search (to find all matches rather than stopping after the first match), and the 'i' flag is used for case-insensitive search.
* @example huaweiDevicesRegex.test("AMN-") // returns true
* @example huaweiDevicesRegex.test("ANA-") // returns true
* @example huaweiDevicesRegex.test("ANE-") // returns true
* Source of list is from: https://gist.github.com/megaacheyounes/e1c7eec5c790e577db602381b8c50bfa
*/
export const huaweiDevicesRegex =
/(ALP-|AMN-|ANA-|ANE-|ANG-|AQM-|ARS-|ART-|ATU-|BAC-|BLA-|BRQ-|CAG-|CAM-|CAN-|CAZ-|CDL-|CDY-|CLT-|CRO-|CUN-|DIG-|DRA-|DUA-|DUB-|DVC-|ELE-|ELS-|EML-|EVA-|EVR-|FIG-|FLA-|FRL-|GLK-|HMA-|HW-|HWI-|INE-|JAT-|JEF-|JER-|JKM-|JNY-|JSC-|LDN-|LIO-|LON-|LUA-|LYA-|LYO-|MAR-|MED-|MHA-|MLA-|MRD-|MYA-|NCE-|NEO-|NOH-|NOP-|OCE-|PAR-|PIC-|POT-|PPA-|PRA-|RNE-|SEA-|SLA-|SNE-|SPN-|STK-|TAH-|TAS-|TET-|TRT-|VCE-|VIE-|VKY-|VNS-|VOG-|VTR-|WAS-|WKG-|WLZ-|JAD-|MLD-|RTE-|NAM-|NEN-|BAL-|JLN-|YAL-|MGA-|FGD-|XYAO-|BON-|ALN-|ALT-|BRA-|DBY2-|STG-|MAO-|LEM-|GOA-|FOA-|MNA-|LNA-)/;
36 changes: 36 additions & 0 deletions src/utils/__test__/mobileOSDetectAsync.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, test } from "vitest";
import { mobileOSDetectAsync } from "../os-detect.utils";

describe("mobileOSDetectAsync", () => {
test('should return "Windows Phone" for Windows Phone user agent', async () => {
Object.defineProperty(window.navigator, "userAgent", {
value: "windows phone",
configurable: true,
});
expect(await mobileOSDetectAsync()).toBe("Windows Phone");
});

test('should return "Android" for Android user agent', async () => {
Object.defineProperty(window.navigator, "userAgent", {
value: "android",
configurable: true,
});
expect(await mobileOSDetectAsync()).toBe("Android");
});

test('should return "iOS" for iOS user agent', async () => {
Object.defineProperty(window.navigator, "userAgent", {
value: "iPhone",
configurable: true,
});
expect(await mobileOSDetectAsync()).toBe("iOS");
});

test('should return "unknown" for unknown user agent', async () => {
Object.defineProperty(window.navigator, "userAgent", {
value: "unknown",
configurable: true,
});
expect(await mobileOSDetectAsync()).toBe("unknown");
});
});
13 changes: 12 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,16 @@ import * as PromiseUtils from "./promise.utils";
import * as URLUtils from "./url.utils";
import * as WebSocketUtils from "./websocket.utils";
import * as BrandUtils from "./brand.utils";
import * as OSDetectionUtils from "./os-detect.utils";

export { ImageUtils, FormatUtils, LocalStorageUtils, ObjectUtils, PromiseUtils, URLUtils, WebSocketUtils, BrandUtils };
export {
ImageUtils,
FormatUtils,
LocalStorageUtils,
ObjectUtils,
PromiseUtils,
URLUtils,
WebSocketUtils,
BrandUtils,
OSDetectionUtils,
};
88 changes: 88 additions & 0 deletions src/utils/os-detect.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { huaweiDevicesRegex } from "../constants/mobile-devices.constants";

/**
* This file contains utility functions and types for detecting the mobile operating system.
* It uses the User-Agent string and the User-Agent Client Hints API to determine the OS.
*/

type ExtendedWindow = Window & {
// MSStream is specific to IE and Edge browsers
MSStream?: {
msClose: () => void;
msDetachStream: () => void;
readonly type: string;
};
// opera is specific to Opera browser
opera?: string;
};

type ExtendedNavigator = Navigator & {
// userAgentData is part of the User-Agent Client Hints API
userAgentData?: NavigatorUAData;
};

/**
* Type representing the User-Agent Client Hints API.
*/
type NavigatorUAData = {
brands: { brand: string; version: string }[];
getHighEntropyValues(hints: string[]): Promise<HighEntropyValues>;
mobile: boolean;
};

/**
* Type representing the high entropy values that can be obtained from the User-Agent Client Hints API.
*/
type HighEntropyValues = {
model?: string;
platform?: string;
platformVersion?: string;
uaFullVersion?: string;
};

/**
* It checks if the input string contains any of the valid Huawei device codes.
*
* @param {string} inputString - The string to check for Huawei device codes.
* @returns {boolean} Returns true if the input string contains a valid Huawei device code, false otherwise.
*/
const validateHuaweiCodes = (inputString: string) => {
return huaweiDevicesRegex.test(inputString);
};

/**
* It uses the User-Agent string and the User-Agent Client Hints API to detects the mobile operating system asynchronously.
*
* @returns {Promise<string>} Returns a promise that resolves to the name of the detected mobile OS.
*/
export const mobileOSDetectAsync = async () => {
const extendedWindow = window as ExtendedWindow;
const extendedNavigator = navigator as ExtendedNavigator;

const userAgent = extendedNavigator.userAgent ?? extendedWindow.opera ?? "";

// Windows Phone must come first because its UA also contains "Android"
if (/windows phone/i.test(userAgent)) {
return "Windows Phone";
}

if (/android/i.test(userAgent)) {
// Check if navigator.userAgentData is available for modern browsers
// userAgent only returns a string, while userAgentData returns an object with more detailed information
if (extendedNavigator.userAgentData) {
const ua = await extendedNavigator.userAgentData.getHighEntropyValues(["model"]);
if (validateHuaweiCodes(ua?.model || "")) {
return "huawei";
}
} else if (validateHuaweiCodes(userAgent) || /huawei/i.test(userAgent)) {
return "huawei";
}
return "Android";
}

if (/iPad|iPhone|iPod/.test(userAgent) && !extendedWindow.MSStream) {
return "iOS";
}

return "unknown";
};
31 changes: 31 additions & 0 deletions utils-docs/docs/Constants/mobile-devices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
sidebar_position: 2
---

# mobile-devices

This utility module provides a regular expression and a set of valid codes to detect and validate Huawei device codes in a string.

### `huaweiDevicesRegex`

This regex matches standalone sequences of three uppercase letters followed by a hyphen, using global and case-insensitive search.

```typescript
import { huaweiDevicesRegex } from "@deriv-com/utils";

const isValid = huaweiDevicesRegex.test("ALP-"); // returns true
```

### `validCodes`

This is a set of valid Huawei device codes. It can be used to check if a detected code is a valid Huawei device code.

```typescript
import { validCodes } from "@deriv-com/utils";

const isValidCode = validCodes.has("ALP-"); // returns true
```

#### Note

These utilities can be used in conjunction with the `mobileOSDetectAsync` function to detect if a user is on a Huawei device running Android. If `mobileOSDetectAsync` returns "huawei", you can use `huaweiDevicesRegex` and `validCodes` to further validate the device code.
34 changes: 34 additions & 0 deletions utils-docs/docs/Utils/os-detect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
sidebar_position: 2
---

# os-detect

This utility module provides functions to detect mobile operating systems and extract information from user agent strings.

### `mobileOSDetectAsync`

This function asynchronously detects the mobile operating system based on the user agent string.

#### Returns

- `"Windows Phone"` if the user agent string indicates a Windows Phone device.
- `"huawei"` if the user agent string indicates a Huawei device running Android.
- `"Android"` if the user agent string indicates an Android device.
- `"iOS"` if the user agent string indicates an iOS device (iPad, iPhone, or iPod).
- `"unknown"` if the mobile operating system cannot be determined.

#### Usage

```typescript
import { mobileOSDetectAsync } from "@deriv-com/utils";

const os = await mobileOSDetectAsync();

if (os === "iOS") {
console.log("client on iOS");
} else if (os === "huawei") {
console.log("client on huawei");
}
console.log("client on android");
```

0 comments on commit db07935

Please sign in to comment.