Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CFDS] shontzu/FEQ-2199/adding-mobileOsDetect-utils-and-constants #55

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
```
Loading