Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | 7x 7x 7x 7x 7x 7x 7x 3x 1x 3x 7x 3x 3x 2x 1x 3x 7x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 1x 5x 2x 2x 2x 5x 3x 3x 3x 5x 7x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 3x 7x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 2x 7x 1x | import { HubDevice } from "~/types/HubDevice";
import {
ApiResponse,
PairResponseData,
ControlDeviceResponseData,
} from "~/types/ApiResponse";
import { HubAPIActions } from "../../interfaces/HubAPIActions";
import StateManager from "../state/StateManager";
/**
* Service for interacting with the hub's API to manage device pairing and control.
*/
class HubAPIService implements HubAPIActions {
/**
* Creates an instance of HubAPIService.
*
* @param {StateManager} stateManager - Manages the state of devices and authentication tokens.
* @param {string} pairEndpoint - The endpoint to pair with the hub.
* @param {string} controlDeviceEndpoint - The endpoint to control devices on the hub.
*/
constructor(
private stateManager: StateManager,
private pairEndpoint: string,
private controlDeviceEndpoint: string,
) {}
/**
* Pairs with the hub using a 4-digit pairing code and stores the session token.
* The token is used for all future API requests.
*
* @param {string} pairingCode - The 4-digit code used for pairing with the hub.
* @returns {Promise<void>} Resolves when the token is successfully fetched and stored.
*/
public pair = async (pairingCode: string): Promise<void> => {
const sessionToken = await this.fetchSessionToken(pairingCode);
this.stateManager.setToken(sessionToken);
};
/**
* Sends a command to control a device on the hub.
* The device state is modified based on the provided `mutatedDevice` object.
*
* @param {HubDevice} mutatedDevice - The device with updated state properties to send to the hub.
* @returns {Promise<void>} Resolves when the command is successfully sent to the hub.
*/
public controlDevice = async (mutatedDevice: HubDevice): Promise<void> => {
const token = this.stateManager.getToken();
if (token) {
await this.sendCommand(mutatedDevice, token);
}
};
/**
* Sends a POST request to the given endpoint with the provided body and returns the response data.
*
* @template T - The expected type of the response data.
* @param {string} url - The URL to which the POST request will be sent.
* @param {object} body - The JSON payload to be sent in the request body.
* @returns {Promise<ApiResponse<T>>} A promise that resolves with the API response.
* @throws {Error} If the response is unsuccessful or lacks data.
*/
private postRequest = async <T>(
url: string,
body: object,
): Promise<ApiResponse<T>> => {
try {
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const payload: ApiResponse<T> = await res.json();
if (!res.ok) {
const errorMessage = `${payload.message}: ${payload.error}`;
throw new Error(errorMessage);
}
if (payload.data === null) {
throw new Error("No data found in the response.");
}
return payload;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
}
throw new Error(
"An unexpected error occurred. Please try again later.",
);
}
};
/**
* Retrieves the session token from the hub by sending a POST request with the provided pairing code.
*
* @param {string} pairingCode - The pairing code used to fetch the session token.
* @returns {Promise<string>} A promise that resolves with the session token.
* @throws {Error} If the session token is missing or if the request fails.
*/
private fetchSessionToken = async (
pairingCode: string,
): Promise<string> => {
const { data } = await this.postRequest<PairResponseData>(
this.pairEndpoint,
{
clientPairingCode: pairingCode,
},
);
if (!data?.clientSessionCode) {
throw new Error(
"An unexpected error occurred. Missing session token in server response.",
);
}
return data.clientSessionCode;
};
/**
* Sends a POST request to control a device, updating its properties on the hub.
*
* @param {HubDevice} mutatedDevice - The device with the desired properties to be updated.
* @param {string} token - The session token used for authentication.
* @returns {Promise<string>} A promise that resolves with the local ID of the updated device.
* @throws {Error} If the request fails or if no data is returned.
*/
private sendCommand = async (
mutatedDevice: HubDevice,
token: string,
): Promise<string> => {
const { data } = await this.postRequest<ControlDeviceResponseData>(
this.controlDeviceEndpoint,
{
connectedDeviceId: mutatedDevice.connectedDeviceId,
localId: mutatedDevice.localId,
clientSessionCode: token,
desiredProperty: (({
localId: _,
connectedDeviceId: __,
...rest
}) => rest)(mutatedDevice),
},
);
if (!data?.localId) {
throw new Error(
"An unexpected error occurred. Device state not updated correctly.",
);
}
return data.localId;
};
}
export default HubAPIService;
|