UNPKG

@abl-solutions/wifi-connect

Version:

The WiFi Connect React Native SDK enables the use of WiFi WPA2-Enterprise networks on mobile devices that are managed and operated by abl solutions GmbH

420 lines (352 loc) 13.4 kB
import { EmitterSubscription, NativeEventEmitter } from 'react-native'; import { throwOnInvliadResponse } from './http'; export interface WifiConnectOptions { /** * An access token that allows access to the WiFi Connect API for the current user. */ accessToken: string; /** * Usually, network and WiFi support is limited running on a simulator. Therefore, * the WiFi configuration can not be stored on a simulated device. In such a case * `WifiConnect.connectToWifi()` will reject with the error code `E_NOT_SUPPORTED_ON_SIMULATOR`. * If you don't want to reject with this error but instead resolve successfully, set this * value to `true`. Defaults to `false`. */ ignoreNetworkErrorOnSimulator?: boolean; /** * Optionally set a different REST API endpoint that should be used by this library. */ wifiApiEndpoint?: string; /** * Optional Android specific options. */ android?: { /** * This value is only used for `connectToWifi` on Android 10 devices. * * This time span is used to wait for granting/declining the permission dialog which * is shown by the Operating System. If the user declines the permission dialog within * the time span, `connectToWifi` will reject with the corresponding error code. * If the user grants the permissions or the dialog is still opened, `connectToWifi` will * resolve after the time span. * * This value defaults to 10 seconds if not specified. */ timeSpanToWaitForPermissionDialogConfirmationInSeconds?: number; }; /** * Specifies whether connection represents a hidden network. * * This value defaults to false if not specified. */ isHiddenSSID?: boolean; } export interface LegalTerms { /** * Legal terms content */ legalTerms: string; /** * Version of legal terms. */ version: string; /** * Minimum legal terms version which must be accepted to access the API. */ minimumVersion: string; /** * The date at which the minimal version of legal terms will be enforced. */ dateMinLegalTermsActive: string; } export interface User { /** * The e-mail address of the user. */ email: string; /** * The preferred locale that should be used to interact with the user. */ preferredLocale: string; } export interface WifiConnectService { /** * Register this device to get direct access to abl's WiFi networks. The device will be configured * to use this WiFi connection. This operation is an asynchrnous process and might take a few seconds * to complete. * @param deviceId A string that uniquely identifies this device. * @param user Details about the user that owns the device. * @returns A void promise that resolves if the devices registration was successful and the WiFi * configuration is stored in the device settings. Rejects if configuring the WiFi network failed * for any reason. */ connectToWifi(deviceId: string, user: User): Promise<void>; /** * Gets the latest legal terms that must be accepted by the end-user to use the WiFi network. * @returns A promise that resolves the legal terms or rejects if loading the legal terms failed. */ getLatestLegalTerms(): Promise<LegalTerms>; /** * Checks whether the specified user already accepted the legal terms or not. * @returns `true` if the user already accepted the legal terms, otherwise false. */ legalTermsAccepted(): Promise<boolean>; /** * Checks the version of legal terms accepted by the user. * @returns version of the legal terms that was accepted by the user. If the user didn't accepted any legal terms yet - returns 'undefined' */ legalTermsAcceptedVersion(): Promise<string>; /** * Accept the specified version of legal terms. This method needs to be called before a user * can register to connect to a WiFi network. * @param legalTermsVersion The version of the legal terms. */ acceptLegalTerms(legalTermsVersion: string): Promise<void>; /** * Deletes the WiFi settings from the device and unregisters the device * from abl servers. * Basically, it just reverts the changes that were made by `connectToWifi()`. * @param deviceId A string that uniquely identifies this device. */ deleteWifiConfiguration(deviceId: string): Promise<void>; /** * Check if the device is configured to connect to abl's WiFi network. * @returns `true` if the device is configured; otherwise `false`. */ isWifiConfigured(): Promise<boolean>; /** * Check if device is connected to abl's WiFi network. * Method should be invoked after connectToWifi() to check connection by received SSID, othervise resolves with `false`. * @returns `true` if the device is connected; otherwise `false`. */ isConnectedToWifi(): Promise<boolean>; /** * Registers a callback that will be invoked if the required permissions are * revoked after `connectToWifi` fullfilled. This callback can be used to handle * the case if a user rejects the permission dialog after the confirmation time span. * * This callback should be unregistered (using `unregisterOnPermissionRejectedListener`) * after usage to free internal resources. * * This callback will only be invoked on Android 10 devices. * @param callback The callback to invoke. */ registerOnPermissionRejectedListener(callback: () => void): void; /** * Unregister the callback that was previously registered using * `registerOnPermissionRejectedListener`. */ unregisterOnPermissionRejectedListener(): void; } export interface NativeWifiConnect { connectToWifi(args: ConnectToWifiArgs): Promise<void>; deleteConfiguration(args: DeleteConfigurationArgs): Promise<void>; isWifiConfigured(): Promise<boolean>; isConnectedToWifi(ssid: string): Promise<boolean>; } export class WifiConnectServiceImpl implements WifiConnectService { private readonly options: Required<WifiConnectOptions>; private legalTerms: LegalTerms | null = null; private permissionRejectedListener: EmitterSubscription | null = null; private SSID: string | null = null; constructor( private readonly nativeWifiConnect: NativeWifiConnect, wifiConnectionOptions: WifiConnectOptions ) { this.throwIfInvalidOptions(wifiConnectionOptions); this.options = this.mergeWithDefaultOptions(wifiConnectionOptions); } public async connectToWifi(deviceId: string, user: User): Promise<void> { const endpoint = `${this.options.wifiApiEndpoint}/api/v1/devices/${deviceId}`; var response = await fetch(endpoint, { method: 'PUT', headers: { 'Authorization': `Bearer ${this.options.accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ email: user.email, preferredLocale: user.preferredLocale, }), }); const body: CreateDeviceResponse = await response.json(); throwOnInvliadResponse(response.status, body); this.SSID = body.wifiCredentials.authorizedNetwork.ssid; const args: ConnectToWifiArgs = { caCertificate: body.wifiCredentials.authorizedNetwork.caCertificate, domainSuffix: body.wifiCredentials.authorizedNetwork.wpa2Domain, ignoreNetworkErrorOnSimulator: this.options.ignoreNetworkErrorOnSimulator, password: body.wifiCredentials.password, ssid: body.wifiCredentials.authorizedNetwork.ssid, username: body.wifiCredentials.username, timeSpanToWaitForPermissionDialogConfirmationInSeconds: this.options.android .timeSpanToWaitForPermissionDialogConfirmationInSeconds ?? 10, isHiddenSSID: this.options.isHiddenSSID, }; await this.nativeWifiConnect.connectToWifi(args); } public async getLatestLegalTerms(): Promise<LegalTerms> { if (this.legalTerms) { return this.legalTerms; } const endpoint = `${this.options.wifiApiEndpoint}/api/v1/legal-terms`; var response = await fetch(endpoint, { method: 'GET', headers: { Authorization: `Bearer ${this.options.accessToken}`, }, }); if (response.status !== 200) { throw new Error( `Could not load legal terms. Received status ${response.status} from API.` ); } const body: LegalTerms = await response.json(); this.legalTerms = body; return body; } public async legalTermsAccepted(): Promise<boolean> { const legalTerms = await this.getLatestLegalTerms(); const endpoint = `${this.options.wifiApiEndpoint}/api/v1/legal-terms/acceptedVersion`; var response = await fetch(endpoint, { method: 'GET', headers: { Authorization: `Bearer ${this.options.accessToken}`, }, }); // The API will return a 404 if the current user did not accept any legal terms version at all. if (response.status === 404) { return false; } if (response.status !== 200) { throw new Error( `Could not load the currently accepted version. Received status ${response.status} from API.` ); } const body: AcceptedLegalTermsResponse = await response.json(); return body.version === legalTerms.version; } public async legalTermsAcceptedVersion(): Promise<string> { const endpoint = `${this.options.wifiApiEndpoint}/api/v1/legal-terms/acceptedVersion`; var response = await fetch(endpoint, { method: 'GET', headers: { Authorization: `Bearer ${this.options.accessToken}`, }, }); // The API will return a 404 if the current user did not accept any legal terms version at all. if (response.status === 404) { return 'undefined'; } if (response.status !== 200) { throw new Error( `Could not load the currently accepted version. Received status ${response.status} from API.` ); } const body: AcceptedLegalTermsResponse = await response.json(); return body.version; } public async acceptLegalTerms(legalTermsVersion: string): Promise<void> { const endpoint = `${this.options.wifiApiEndpoint}/api/v1/legal-terms/${legalTermsVersion}/accept`; var response = await fetch(endpoint, { method: 'PUT', headers: { Authorization: `Bearer ${this.options.accessToken}`, }, }); if (response.status !== 200 && response.status !== 202) { throw new Error( `Accepting the legal terms failed. Received status ${response.status} from API.` ); } } public async deleteWifiConfiguration(deviceId: string): Promise<void> { const endpoint = `${this.options.wifiApiEndpoint}/api/v1/devices/${deviceId}`; var response = await fetch(endpoint, { method: 'DELETE', headers: { Authorization: `Bearer ${this.options.accessToken}`, }, }); throwOnInvliadResponse(response.status, null, true); const args: DeleteConfigurationArgs = { ignoreNetworkErrorOnSimulator: this.options.ignoreNetworkErrorOnSimulator, }; await this.nativeWifiConnect.deleteConfiguration(args); } public async isWifiConfigured(): Promise<boolean> { return await this.nativeWifiConnect.isWifiConfigured(); } public registerOnPermissionRejectedListener(callback: () => void): void { const eventEmitter = new NativeEventEmitter(this.nativeWifiConnect as any); this.permissionRejectedListener = eventEmitter.addListener( 'PermissionRejected', () => callback() ); } public unregisterOnPermissionRejectedListener(): void { const listener = this.permissionRejectedListener; if (listener) { listener.remove(); this.permissionRejectedListener = null; } } public isConnectedToWifi = (): Promise<boolean> => this.SSID ? this.nativeWifiConnect.isConnectedToWifi(this.SSID) : Promise.resolve(false); private mergeWithDefaultOptions( options: WifiConnectOptions ): Required<WifiConnectOptions> { return { accessToken: options.accessToken, ignoreNetworkErrorOnSimulator: options.ignoreNetworkErrorOnSimulator ?? false, wifiApiEndpoint: options?.wifiApiEndpoint || 'https://api.wifi.connectivity.abl-solutions.io', android: options.android ?? {}, isHiddenSSID: options?.isHiddenSSID || false, }; } private throwIfInvalidOptions(options: WifiConnectOptions) { if (!options) { throw new Error('WifiConnectOptions must be not null.'); } if (!options.accessToken) { throw new Error('WifiConnectOptions.accessToken must be not null.'); } if (options.accessToken === '') { throw new Error('WifiConnectOptions.accessToken must be not empty.'); } } } interface AcceptedLegalTermsResponse { version: string; } interface CreateDeviceResponse { deviceId: string; wifiCredentials: { username: string; password: string; authorizedNetwork: { caCertificate: string; ssid: string; wpa2Domain: string; }; }; } interface ConnectToWifiArgs extends CommonNativeArgs { caCertificate: string; domainSuffix: string; password: string; ssid: string; username: string; timeSpanToWaitForPermissionDialogConfirmationInSeconds: number; isHiddenSSID: boolean; } interface DeleteConfigurationArgs extends CommonNativeArgs {} interface CommonNativeArgs { ignoreNetworkErrorOnSimulator: boolean; }