From af03bd37004a04643d29560c07f29bcbfb94a50e Mon Sep 17 00:00:00 2001 From: Sheherezade <102605523+Sheherezadhe@users.noreply.github.com> Date: Fri, 1 Apr 2022 21:14:45 +0200 Subject: [PATCH] first release --- README.md | 26 +++++++++++++++++--- package.json | 12 ++++++++-- src/components/AvailableSensors.tsx | 20 ++++++++-------- src/components/Log.tsx | 2 +- src/components/ManageSensors.css | 3 ++- src/components/ManageSensors.tsx | 19 +++++++++------ src/components/Status.tsx | 14 ++++++----- src/constants.ts | 12 ++++------ src/index.ts | 18 ++++++++++++++ src/mappers/AwairToPlanetWatchMapper.ts | 5 ++-- src/scheduler.ts | 32 ++++++++++++------------- src/services/planetWatchService.ts | 4 ++-- src/types/awair/latestData.ts | 22 ++++++++++------- src/types/planetWatch/dataPacket.ts | 6 +++-- src/types/planetWatch/sensors.ts | 11 +++++---- src/utils/authorization.tsx | 19 ++++++--------- 16 files changed, 138 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index b59c4dd..c2e06f6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,28 @@ This is a community based opensource tool that is capable of fetching data of Awair Element devices from Awair servers and send them to a custom endpoint. The idea behind this project is to send these data to PlanetWatch servers in order to be able to be still elibile for rewards. +## First Release +I've finally finished a first, basic version of this tool. I wanted to very hurry up cause i know the community needs this kind of functionality. +I'm goint to upload two builds of this tool at the first very moment: +- one for windows (x64) +- one for MacOs (M1) (I've tested on M1 but i'm not sure it will work only on M1, but also on Intel, just drop a comment if you can't run this on Intel) + +## DISCLAIMER AND WARNINGS +This tool was built in very few days so it can be full of bugs. For the time being i've found one main issue while using this tool. Since the authentication is a tricky process, once you launch the application you have to force the reload when it got stucked on "Loading..." page using the "View" -> "Force Reload" + +## How to use this tool +1. Open the tool +2. Log in with the PLANETWATCH CREDENTIALS (Read the disclaimer, if you got stucked, pls refresh with Force Reload (even several times)) +3. Open your Awair Home Application +4. Click one of your sensors and press Awair+, Awair APIs Beta, Cloud API, Get API Token +5. Once you have clicked the Get API Token you will be able to insert the AWAIR HOME Credentials. Then you will be able to grab the token (ex: eyJ0......) +5. Once you got it insert it into the "Add sensor" section (into the search bar). +6. Search +7. Press "Add your token" +8. Repeat from step 3 for EACH awair account you own +9. Finish! + + ## Installation Since the project is not yet approved by PlanetWatch Company, the software is basically only fetching data from Awair's servers. For the time being we discourage any usage of this project until PlanetWatch approves and shares the endpoints where to write our data. @@ -20,6 +42,4 @@ npm run make The project was developed on MacOS and the building section is only for MacOS. Feel free to contribute to the project by adding your distro building section (Windows/Linux) with a Pull Request. -We will appreciate any help to improve of the code. - -Stay tuned for next updates coming in the next days! \ No newline at end of file +We will appreciate any help to improve of the code. \ No newline at end of file diff --git a/package.json b/package.json index 803dc1e..8ff45dc 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "version": "0.0.1", "description": "An Awair Element data uploader", "main": ".webpack/main", + "author": { + "name": "Sheherezadhe and the lovely PW Comunity" + }, "scripts": { "start": "electron-forge start", "package": "electron-forge package", @@ -26,7 +29,9 @@ { "name": "@electron-forge/maker-zip", "platforms": [ - "darwin" + "darwin", + "linux", + "windows" ] }, { @@ -46,7 +51,7 @@ "@electron-forge/plugin-webpack", { "mainConfig": "./webpack.main.config.js", - "devContentSecurityPolicy": "connect-src 'self' https://developer-apis.awair.is 'unsafe-eval'", + "devContentSecurityPolicy": "connect-src 'self' http://wearableapi.test.planetwatch.io http://login.planetwatch.io https://developer-apis.awair.is 'unsafe-eval'", "renderer": { "config": "./webpack.renderer.config.js", "entryPoints": [ @@ -70,6 +75,7 @@ "@electron-forge/maker-squirrel": "^6.0.0-beta.63", "@electron-forge/maker-zip": "^6.0.0-beta.63", "@electron-forge/plugin-webpack": "^6.0.0-beta.63", + "@types/http-server": "^0.12.1", "@types/javascript-time-ago": "^2.0.3", "@types/luxon": "^2.3.1", "@types/react": "^17.0.38", @@ -96,6 +102,8 @@ "antd": "^4.19.3", "axios": "^0.26.1", "electron-squirrel-startup": "^1.0.0", + "express": "^4.17.3", + "http-server": "^14.1.0", "javascript-time-ago": "^2.3.13", "jwt-decode": "^3.1.2", "keycloak-js": "^17.0.0", diff --git a/src/components/AvailableSensors.tsx b/src/components/AvailableSensors.tsx index 8b15688..3b6be4c 100644 --- a/src/components/AvailableSensors.tsx +++ b/src/components/AvailableSensors.tsx @@ -4,11 +4,10 @@ import { Sensors } from '../types/planetWatch/sensors'; import './AvailableSensors.css'; const AvailableSensors = () => { - const [sensors, setSensors] = useState([]); + const [sensors, setSensors] = useState(undefined); useEffect(() => { Scheduler.getInstance().loadPWSensorsList(() => { }).then((res) => { - console.log(res); setSensors(res); }); }, []); @@ -19,18 +18,19 @@ const AvailableSensors = () => {

Available PW Sensors

-
- { - sensors && sensors.map((sensor, index) => ( -
- Sensor name +
+ { + sensors && sensors.data + .filter((sensor) => sensor.sensorId.includes('awair-element')) + .map((sensor, index) => ( +
{ - // sensor.id??? Waiting for PW Specifications + sensor.sensorId }
)) - } -
+ } +
); }; diff --git a/src/components/Log.tsx b/src/components/Log.tsx index 5a6ba1a..6eef1a8 100644 --- a/src/components/Log.tsx +++ b/src/components/Log.tsx @@ -25,7 +25,7 @@ const Log = () => {
{ data.map((log, index) => ( -
+
{log}
)) diff --git a/src/components/ManageSensors.css b/src/components/ManageSensors.css index 901fdc4..0a533f0 100644 --- a/src/components/ManageSensors.css +++ b/src/components/ManageSensors.css @@ -25,8 +25,9 @@ .sensorList { margin-top: 10px; + margin-bottom: 10px; background-color: rgba(211, 211, 211, 0.571); - height: 268px; + height: 225px; overflow-y: scroll; scrollbar-width: thin; overflow: overlay; diff --git a/src/components/ManageSensors.tsx b/src/components/ManageSensors.tsx index 49b60d1..756c1fb 100644 --- a/src/components/ManageSensors.tsx +++ b/src/components/ManageSensors.tsx @@ -20,7 +20,7 @@ const ManageSensors = () => { setRegisteredAccounts(Scheduler.getInstance().getAccounts()); }, []); - const addAccount = async () => { + const onAddAccount = async () => { setRegisteredAccounts(await Scheduler.getInstance().addSubscription({ jwt: tempJwt, devices })); }; @@ -48,6 +48,10 @@ const ManageSensors = () => { setRegisteredAccounts(Scheduler.getInstance().removeSubscription(device)); }; + const onRemoveAllDevice = () => { + setRegisteredAccounts(Scheduler.getInstance().removeAllSubscriptions()); + }; + return ( <>

Manage sensors

@@ -71,15 +75,15 @@ const ManageSensors = () => { )) }
- +
List of added sensors:
{ - registeredAccounts.map((account) => ( - <> + registeredAccounts.map((account, index) => ( +
{ - account.devices.map((device, index) => ( -
+ account.devices.map((device, index2) => ( +
onRemoveDevice(device)}>x
{ device.deviceUUID @@ -87,10 +91,11 @@ const ManageSensors = () => {
)) } - +
)) }
+ { error &&
Error while getting the devices of this jwt: {error}
diff --git a/src/components/Status.tsx b/src/components/Status.tsx index 653f0fd..46bc1a9 100644 --- a/src/components/Status.tsx +++ b/src/components/Status.tsx @@ -22,12 +22,14 @@ const Status = () => { return (
{ - result.map((element) => ( - <> - {element.calledAt !== '' && element.receivedAt !== '' &&
- {element.name}: {timeAgo.format(new Date(element.calledAt))} - {timeAgo.format(new Date(element.receivedAt))} -
} - + result.map((element, index) => ( +
+ {element.calledAt !== '' && element.receivedAt !== '' && +
+ {element.name}: {timeAgo.format(new Date(element.calledAt))} - {timeAgo.format(new Date(element.receivedAt))} +
+ } +
)) }
diff --git a/src/constants.ts b/src/constants.ts index 0c6a490..c93bc9d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,16 +3,14 @@ export const constants = { getLatestData: 'https://developer-apis.awair.is/v1/users/self/devices/{device_type}/{device_id}/air-data/latest', getDevices: 'https://developer-apis.awair.is/v1/users/self/devices' }, - // Waiting for PW Specifications planetWatch: { - sensors: '', - sendData: '', + sensors: 'https://wearableapi.planetwatch.io/api/sensors', + sendData: 'https://wearableapi.planetwatch.io/api/data/devicedata', }, - // Waiting for PW Specifications identityProvider: { - url: '', - realm: '', - clientId: '', + url: 'https://login.planetwatch.io/auth', + realm: 'Planetwatch', + clientId: 'external-login', role: '' }, routes: { diff --git a/src/index.ts b/src/index.ts index 54a3047..d623949 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { app, BrowserWindow } from 'electron'; + // This allows TypeScript to pick up the magic constant that's auto-generated by Forge's Webpack // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on // whether you're running in development or production). @@ -15,11 +16,28 @@ const createWindow = (): void => { const mainWindow = new BrowserWindow({ height: 600 * 1.2, width: 800 * 1.2, + webPreferences: { + devTools: false + } }); // and load the index.html of the app. mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); + const { session: { webRequest } } = mainWindow.webContents; + const filter = { + urls: [ + 'http://localhost:33333/keycloak-redirect*' + ] + }; + webRequest.onBeforeRequest(filter, async ({ url }) => { + console.log(url); + const params = url.slice(url.indexOf('#')); + console.log(params); + console.log(MAIN_WINDOW_WEBPACK_ENTRY + params); + mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY + params); + }); + // Open the DevTools. // mainWindow.webContents.openDevTools(); }; diff --git a/src/mappers/AwairToPlanetWatchMapper.ts b/src/mappers/AwairToPlanetWatchMapper.ts index da0f332..bd1428c 100644 --- a/src/mappers/AwairToPlanetWatchMapper.ts +++ b/src/mappers/AwairToPlanetWatchMapper.ts @@ -2,11 +2,10 @@ import { AwairLatestData } from '../types/awair/latestData'; import { PlanetWatchDataPacket } from '../types/planetWatch/dataPacket'; const map = (sensorId: string, data: AwairLatestData): PlanetWatchDataPacket => { - // Waiting for PW Specifications const response: PlanetWatchDataPacket = { - + deviceId: sensorId, + ...(data.data[0]) }; - return response; }; diff --git a/src/scheduler.ts b/src/scheduler.ts index 647d0e1..8e210ae 100644 --- a/src/scheduler.ts +++ b/src/scheduler.ts @@ -1,6 +1,5 @@ import AwairController from './services/awairService'; import PlanetWatchService from './services/planetWatchService'; -import { AwairLatestData } from './types/awair/latestData'; import { AwairDevice } from './types/awair/devices'; import { Sensors } from './types/planetWatch/sensors'; import { DateTime } from 'luxon'; @@ -20,11 +19,6 @@ export interface Statuses { } } -interface SensorData { - sensorId: string; - data: AwairLatestData; -} - export class Scheduler { private static instance: Scheduler; @@ -109,6 +103,7 @@ export class Scheduler { public removeAllSubscriptions() { this.accounts = []; this.saveAwairAccountsToLocalStorage(); + return [...this.accounts]; } public async addSubscription(account: AwairAccount) { @@ -118,18 +113,19 @@ export class Scheduler { const deviceRegistered: AwairDevice[] = []; account.devices.forEach((device) => { - // Check if the sensor is registered on PW systems and then push - // TODO - deviceRegistered.push(device); + // Check if the sensor is registered on PW systems and then push + if (this.planetWatchRegisteredSensors.data.findIndex((localDevice) => localDevice.sensorId === device.deviceUUID) !== -1) { + deviceRegistered.push(device); + } }); if (deviceRegistered.length > 0) { this.accounts.push({ jwt: account.jwt, devices: deviceRegistered }); this.saveAwairAccountsToLocalStorage(); } - console.log(this.accounts); } } + this.reset(); return [...this.accounts]; } @@ -162,7 +158,8 @@ export class Scheduler { this.planetWatchRegisteredSensorsPromise.then((res) => { this.planetWatchRegisteredSensors = res; callback(res); - }); + }) + .catch((err) => console.log(err)); } const ready = await this.isReady(); if (ready) { @@ -170,9 +167,9 @@ export class Scheduler { } } - private async fetchAndSendData() { + private async fetchAndSendData(accounts: AwairAccounts) { // Grab all the data from awair and save them in a structure - this.accounts.forEach((account) => { + accounts .forEach((account) => { const jwt = account.jwt; account.devices.forEach((device) => { @@ -185,10 +182,11 @@ export class Scheduler { AwairController.getLatestData(jwt, deviceType, deviceId) .then((res) => { - PlanetWatchService.sendData(AwairToPlanetWatchMapper.map(device.deviceUUID, res)); + PlanetWatchService.sendData(AwairToPlanetWatchMapper.map(device.deviceUUID, res)) + .catch((err) => console.log(err)); this.statuses[device.deviceUUID].receivedAt = DateTime.now().toISO(); this.statusSubscribers.forEach((callback) => callback({ ...this.statuses })); - this.logs.push('Retrieving data from ' + deviceId + '\n'); + this.logs.push(DateTime.now().toFormat('[HH:mm:ss]') + ' - Retrieving data from ' + deviceId + '\n'); this.logSubscribers.forEach((callback) => callback([...this.logs])); }) .catch((error) => { @@ -199,8 +197,8 @@ export class Scheduler { } public start() { - this.fetchAndSendData(); - this.scheduler = setInterval(this.fetchAndSendData, constants.interval); + this.fetchAndSendData(this.accounts); + this.scheduler = setInterval(() => this.fetchAndSendData(this.accounts), constants.interval); } public stop() { diff --git a/src/services/planetWatchService.ts b/src/services/planetWatchService.ts index 9140776..8e78080 100644 --- a/src/services/planetWatchService.ts +++ b/src/services/planetWatchService.ts @@ -7,12 +7,12 @@ const getSensors = async () => { const main = constants.planetWatch.sensors; const response = await axios.get(main); - + return response.data; }; const sendData = async (data: PlanetWatchDataPacket) => { - const main = constants.planetWatch.sensors; + const main = constants.planetWatch.sendData; const response = await axios.post(main, data); diff --git a/src/types/awair/latestData.ts b/src/types/awair/latestData.ts index c5b3055..672d5d6 100644 --- a/src/types/awair/latestData.ts +++ b/src/types/awair/latestData.ts @@ -1,12 +1,16 @@ -export interface AwairLatestData { - timestamp: string, - score: number, +export interface AwairDataPacket { + timestamp: string; + score: number; sensors: { - camp: string, - value: number - }[], + comp: string; + value: number; + }[]; indices: { - camp: string, - value: number - }[] + comp: string; + value: number; + }[]; +} + +export interface AwairLatestData { + data: AwairDataPacket[]; } \ No newline at end of file diff --git a/src/types/planetWatch/dataPacket.ts b/src/types/planetWatch/dataPacket.ts index d29a212..bad580f 100644 --- a/src/types/planetWatch/dataPacket.ts +++ b/src/types/planetWatch/dataPacket.ts @@ -1,3 +1,5 @@ -export interface PlanetWatchDataPacket { - // Waiting for PW Specifications +import { AwairDataPacket, AwairLatestData } from '../awair/latestData'; + +export interface PlanetWatchDataPacket extends AwairDataPacket { + deviceId: string; } \ No newline at end of file diff --git a/src/types/planetWatch/sensors.ts b/src/types/planetWatch/sensors.ts index 8834866..fc24be4 100644 --- a/src/types/planetWatch/sensors.ts +++ b/src/types/planetWatch/sensors.ts @@ -1,5 +1,6 @@ -export interface Sensor { - // Waiting for PW Specifications -} - -export type Sensors = Sensor[]; \ No newline at end of file +export interface Sensors { + success: boolean; + data: { + sensorId: string; + }[]; +} \ No newline at end of file diff --git a/src/utils/authorization.tsx b/src/utils/authorization.tsx index ed54297..63c2ff0 100644 --- a/src/utils/authorization.tsx +++ b/src/utils/authorization.tsx @@ -4,8 +4,6 @@ import Keycloak, { KeycloakInstance } from 'keycloak-js'; import { ReactKeycloakProvider } from '@react-keycloak/web'; import { setReady, setAuthenticated } from '../features/auth/AuthSlice'; import { connect } from 'react-redux'; -import constants from '../constants'; - export interface AuthorizationProps { url: string, @@ -42,18 +40,11 @@ class Authorization { isAuthenticated = () => this.keycloak?.authenticated === true; login = () => { - const url = new URL(window.location.href); - const uri = `${url.protocol}//${url.hostname}` + (url.port ? `:${url.port}` : '') + constants.routes.home; - return this.keycloak!.login({ redirectUri: uri }); + return this.keycloak!.login({ redirectUri: 'http://localhost:33333/keycloak-redirect' }); }; - logout = (redirectUri?: string) => { - let uri = redirectUri; - if (uri === undefined) { - const url = new URL(window.location.href); - uri = `${url.protocol}//${url.hostname}` + (url.port ? `:${url.port}` : '') + constants.routes.home; - } - this.keycloak?.logout({ redirectUri: uri }); + logout = () => { + this.keycloak?.logout({ redirectUri: 'http://localhost:33333/keycloak-redirect' }); }; } @@ -72,6 +63,10 @@ const AuthProvider = (props: AuthorizationProviderProps) => { return ( { if (event === 'onAuthSuccess') { props.setAuthenticated(true);