diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml new file mode 100644 index 0000000..21d7c77 --- /dev/null +++ b/.github/workflows/dockerbuild.yaml @@ -0,0 +1,63 @@ +--- +name: ci + +on: + workflow_dispatch: + pull_request: + branches: + - 'master' + push: + branches: + - master + tags: + - 'v*' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: set up qemu + uses: docker/setup-qemu-action@v2 + + - name: set up docker buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64, linux/arm64 + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile index fb015cb..495699e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,4 @@ FROM php:apache -COPY backend-php/ /var/www/html/ -COPY frontend/ /var/www/html/ -COPY docker/start.sh . RUN apt-get update && \ apt-get install -y memcached libmemcached-dev zlib1g-dev libldap2-dev && \ @@ -12,7 +9,11 @@ RUN apt-get update && \ EXPOSE 80/tcp VOLUME /etc/hauk - STOPSIGNAL SIGINT + +COPY docker/start.sh . RUN chmod +x ./start.sh +COPY backend-php/ /var/www/html/ +COPY frontend/ /var/www/html/ + CMD ["./start.sh"] diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index 82fb904..46e2182 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -39,7 +39,7 @@ - + diff --git a/android/app/build.gradle b/android/app/build.gradle index 601cc2c..0398cc8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,15 +1,17 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 29 - buildToolsVersion "29.0.2" + namespace 'info.varden.hauk' + + compileSdkVersion 33 defaultConfig { applicationId "info.varden.hauk" minSdkVersion 23 - targetSdkVersion 29 + targetSdkVersion 33 versionCode 13 - versionName "1.6.1" + versionName "1.6.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true } buildTypes { release { @@ -21,10 +23,11 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.preference:preference:1.1.1' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'androidx.appcompat:appcompat:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' + implementation 'androidx.preference:preference:1.2.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2ff1504..16fec73 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,11 +1,14 @@ + xmlns:tools="http://schemas.android.com/tools"> + + + + + android:theme="@style/HomeTheme" + android:exported="true"> @@ -45,7 +49,7 @@ @@ -53,6 +57,20 @@ + + + + + + + + + + + diff --git a/android/app/src/main/java/info/varden/hauk/Constants.java b/android/app/src/main/java/info/varden/hauk/Constants.java index a6909af..401e900 100644 --- a/android/app/src/main/java/info/varden/hauk/Constants.java +++ b/android/app/src/main/java/info/varden/hauk/Constants.java @@ -45,6 +45,7 @@ public enum Constants { public static final Preference PREF_NICKNAME = new Preference.String("nickname", ""); public static final Preference PREF_DURATION_UNIT = new Preference.Integer("durUnit", Constants.DURATION_UNIT_MINUTES); public static final Preference PREF_ALLOW_ADOPTION = new Preference.Boolean("allowAdoption", true); + public static final Preference PREF_RESTART_ON_BOOT = new Preference.Boolean("restartOnBoot", true); public static final Preference PREF_NIGHT_MODE = new Preference.Enum<>("nightMode", NightModeStyle.FOLLOW_SYSTEM); public static final Preference PREF_CONFIRM_STOP = new Preference.Boolean("confirmStop", true); public static final Preference PREF_HIDE_LOGO = new Preference.Boolean("hideLogo", false); @@ -122,6 +123,7 @@ public enum Constants { public static final String PACKET_PARAM_SPEED = "spd"; public static final String PACKET_PARAM_TIMESTAMP = "time"; public static final String PACKET_PARAM_USERNAME = "usr"; + public static final String PACKET_PARAM_ALTITUDE = "alt"; // Packet OK response header. All valid packets start with this line. public static final String PACKET_RESPONSE_OK = "OK"; diff --git a/android/app/src/main/java/info/varden/hauk/global/Receiver.java b/android/app/src/main/java/info/varden/hauk/global/ExportedReceiver.java similarity index 99% rename from android/app/src/main/java/info/varden/hauk/global/Receiver.java rename to android/app/src/main/java/info/varden/hauk/global/ExportedReceiver.java index a6d1eb4..6bb4dd6 100644 --- a/android/app/src/main/java/info/varden/hauk/global/Receiver.java +++ b/android/app/src/main/java/info/varden/hauk/global/ExportedReceiver.java @@ -51,7 +51,7 @@ import info.varden.hauk.utils.TimeUtils; * @since 1.3 * @author Marius Lindvall */ -public final class Receiver extends BroadcastReceiver { +public final class ExportedReceiver extends BroadcastReceiver { @SuppressWarnings("HardCodedStringLiteral") private static final String ACTION_START_SHARING_ALONE_WITH_MENU = "info.varden.hauk.START_ALONE_THEN_SHARE_VIA"; @SuppressWarnings("HardCodedStringLiteral") diff --git a/android/app/src/main/java/info/varden/hauk/global/RebootReceiver.java b/android/app/src/main/java/info/varden/hauk/global/RebootReceiver.java new file mode 100644 index 0000000..04a4142 --- /dev/null +++ b/android/app/src/main/java/info/varden/hauk/global/RebootReceiver.java @@ -0,0 +1,48 @@ +package info.varden.hauk.global; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.widget.Toast; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; + +import info.varden.hauk.Constants; +import info.varden.hauk.R; +import info.varden.hauk.global.ui.AuthorizationActivity; +import info.varden.hauk.global.ui.DisplayShareDialogListener; +import info.varden.hauk.global.ui.toast.GNSSStatusUpdateListenerImpl; +import info.varden.hauk.global.ui.toast.SessionInitiationResponseHandlerImpl; +import info.varden.hauk.global.ui.toast.ShareListenerImpl; +import info.varden.hauk.http.ConnectionParameters; +import info.varden.hauk.http.SessionInitiationPacket; +import info.varden.hauk.http.security.CertificateValidationPolicy; +import info.varden.hauk.manager.SessionManager; +import info.varden.hauk.struct.AdoptabilityPreference; +import info.varden.hauk.system.LocationPermissionsNotGrantedException; +import info.varden.hauk.system.LocationServicesDisabledException; +import info.varden.hauk.system.preferences.PreferenceManager; +import info.varden.hauk.utils.DeprecationMigrator; +import info.varden.hauk.utils.Log; +import info.varden.hauk.utils.TimeUtils; +// Reboot broadcast receiver for Hauk. If enabled resumes a share session if one exists on device restart +public final class RebootReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + // Subsequent calls may result in data being read from preferences. We should ensure that + // all deprecated preferences have been migrated before we continue. + new DeprecationMigrator(context).migrate(); + + PreferenceManager prefs = new PreferenceManager(context); + if(prefs.get(Constants.PREF_RESTART_ON_BOOT)) + resumeShare(context,intent); + } + private void resumeShare(Context context, Intent intent) { + Log.d("Trying to resume shares..."); + new BroadcastSessionManager(context).resumeShares(); + } +} diff --git a/android/app/src/main/java/info/varden/hauk/http/LocationUpdatePacket.java b/android/app/src/main/java/info/varden/hauk/http/LocationUpdatePacket.java index 4c571fa..21a86f7 100644 --- a/android/app/src/main/java/info/varden/hauk/http/LocationUpdatePacket.java +++ b/android/app/src/main/java/info/varden/hauk/http/LocationUpdatePacket.java @@ -5,9 +5,15 @@ import android.location.Location; import android.util.Base64; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import javax.crypto.BadPaddingException; import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import info.varden.hauk.Constants; import info.varden.hauk.R; @@ -48,36 +54,17 @@ public abstract class LocationUpdatePacket extends Packet { super(ctx, session.getServerURL(), session.getConnectionParameters(), Constants.URL_PATH_POST_LOCATION); setParameter(Constants.PACKET_PARAM_SESSION_ID, session.getID()); - if (session.getDerivableE2EKey() == null) { - // If not using end-to-end encryption, send parameters in plain text. - setParameter(Constants.PACKET_PARAM_LATITUDE, String.valueOf(location.getLatitude())); - setParameter(Constants.PACKET_PARAM_LONGITUDE, String.valueOf(location.getLongitude())); - setParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, String.valueOf(accuracy.getMode())); - setParameter(Constants.PACKET_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND)); + Cipher cipher = initCipher(session); - // Not all devices provide these parameters: - if (location.hasSpeed()) setParameter(Constants.PACKET_PARAM_SPEED, String.valueOf(location.getSpeed())); - if (location.hasAccuracy()) setParameter(Constants.PACKET_PARAM_ACCURACY, String.valueOf(location.getAccuracy())); - } else { - // We're using end-to-end encryption - generate an IV and encrypt all parameters. - try { - Cipher cipher = Cipher.getInstance(Constants.E2E_TRANSFORMATION); - cipher.init(Cipher.ENCRYPT_MODE, session.getDerivableE2EKey().deriveSpec(), new SecureRandom()); - byte[] iv = cipher.getIV(); - setParameter(Constants.PACKET_PARAM_INIT_VECTOR, Base64.encodeToString(iv, Base64.DEFAULT)); + encryptAndSetParameter(Constants.PACKET_PARAM_LATITUDE, location.getLatitude(), cipher); + encryptAndSetParameter(Constants.PACKET_PARAM_LONGITUDE, location.getLongitude(), cipher); + encryptAndSetParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, accuracy.getMode(), cipher); + encryptAndSetParameter(Constants.PACKET_PARAM_TIMESTAMP, System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND, cipher); - setParameter(Constants.PACKET_PARAM_LATITUDE, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getLatitude()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT)); - setParameter(Constants.PACKET_PARAM_LONGITUDE, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getLongitude()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT)); - setParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, Base64.encodeToString(cipher.doFinal(String.valueOf(accuracy.getMode()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT)); - setParameter(Constants.PACKET_PARAM_TIMESTAMP, Base64.encodeToString(cipher.doFinal(String.valueOf(System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT)); - - // Not all devices provide these parameters: - if (location.hasSpeed()) setParameter(Constants.PACKET_PARAM_SPEED, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getSpeed()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT)); - if (location.hasAccuracy()) setParameter(Constants.PACKET_PARAM_ACCURACY, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getAccuracy()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT)); - } catch (Exception e) { - Log.e("Error was thrown when encrypting location data", e); //NON-NLS - } - } + // Not all devices provide these parameters: + if (location.hasSpeed()) encryptAndSetParameter(Constants.PACKET_PARAM_SPEED, location.getSpeed(), cipher); + if (location.hasAccuracy()) encryptAndSetParameter(Constants.PACKET_PARAM_ACCURACY, location.getAccuracy(), cipher); + if (location.hasAltitude()) encryptAndSetParameter(Constants.PACKET_PARAM_ALTITUDE, location.getAltitude(), cipher); } @SuppressWarnings("DesignForExtension") @@ -113,4 +100,33 @@ public abstract class LocationUpdatePacket extends Packet { throw new ServerException(err.toString()); } } + + private Cipher initCipher(Session session) { + Cipher cipher = null; + if (session.getDerivableE2EKey() != null) { + try { + cipher = Cipher.getInstance(Constants.E2E_TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, session.getDerivableE2EKey().deriveSpec(), new SecureRandom()); + byte[] iv = cipher.getIV(); + setParameter(Constants.PACKET_PARAM_INIT_VECTOR, Base64.encodeToString(iv, Base64.DEFAULT)); + } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException exception) { + Log.e("Error was thrown while initializing E2E encryption", exception); //NON-NLS + } + } + return cipher; + } + + private void encryptAndSetParameter(String key, V value, Cipher cipher) { + if (cipher != null) { + // We're using end-to-end encryption - generate an IV and encrypt all parameters. + try { + setParameter(key, Base64.encodeToString(cipher.doFinal(String.valueOf(value).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT)); + } catch (BadPaddingException | IllegalBlockSizeException exception) { + Log.e("Error was thrown while encrypting location data", exception); //NON-NLS + } + } else { + // If not using end-to-end encryption, send parameters in plain text. + setParameter(key, String.valueOf(value)); + } + } } diff --git a/android/app/src/main/java/info/varden/hauk/manager/SessionManager.java b/android/app/src/main/java/info/varden/hauk/manager/SessionManager.java index 2cf388e..085907c 100644 --- a/android/app/src/main/java/info/varden/hauk/manager/SessionManager.java +++ b/android/app/src/main/java/info/varden/hauk/manager/SessionManager.java @@ -206,6 +206,15 @@ public abstract class SessionManager { } } + public final void resumeShares() { + if (pusher != null) { + Log.d("Pusher is non-null (%s), stopping and nulling it before calling service relauncher", pusher); //NON-NLS + this.ctx.stopService(pusher); + pusher = null; + } + this.resumable.tryResumeShare(new ServiceRelauncher(this, this.resumable)); + } + /** * A preparation step for initiating sessions. Checks location services status and instantiates * a response handler for the session initiation packet. @@ -448,6 +457,7 @@ public abstract class SessionManager { } } + /** * The GNSS status handler that the {@link SessionManager} itself uses to receive status updates * from the GNSS listeners. Used to propagate events further upstream. diff --git a/android/app/src/main/res/values-ca/strings.xml b/android/app/src/main/res/values-ca/strings.xml index f5e7b7e..0c51836 100644 --- a/android/app/src/main/res/values-ca/strings.xml +++ b/android/app/src/main/res/values-ca/strings.xml @@ -2,6 +2,7 @@ Nom d\'usuari <opcional> + Configuracions Password: Durada de la compartició: Interval d\'actualització (s): @@ -115,4 +116,4 @@ \n \nSi activeu aquesta opció i compartiu la vostra ubicació amb un grup d’amics i algú altre també vol compartir la seva ubicació, pot crear un grup compartit i afegir-lo al seu mapa, de manera que tots dos aparegueu al mateix mapa d’ubicació compartit. Això és útil si és conduir i no es pot utilitzar el telèfon, ja que l\'adopció de la seva participació per part d\'altres persones no requereix cap interacció per part seva. El servidor no admet comparticions de grup. Les accions del grup són compatibles amb la versió %s i superiors; actualment el servidor està executant la versió %s. La vostra participació s\'ha convertit en una participació individual. - \ No newline at end of file + diff --git a/android/app/src/main/res/values-eu/strings.xml b/android/app/src/main/res/values-eu/strings.xml index 0f281e2..43391f2 100644 --- a/android/app/src/main/res/values-eu/strings.xml +++ b/android/app/src/main/res/values-eu/strings.xml @@ -3,6 +3,7 @@ Kode irekiko kokaleku partekatzea Kokaleku partekatzea inaktibo Ados + Ezarpenak Sartu duzun zerbitzari URL-a baliogabea da. Aplikazio honek zure kokalekua atzitzeko baimena behar du funtzionatzeko, baina baimen hau ez da oraindik eman. Onartu hurrengo baimen eskaera eta gero sakatu hasi botoia berriro saiatzeko. Zerbitzariaren URLa @@ -93,4 +94,4 @@ \nIraungitzea: %s \n \nSaio hau berreskuratu nahi duzu\? - \ No newline at end of file + diff --git a/android/app/src/main/res/values-uk/strings.xml b/android/app/src/main/res/values-uk/strings.xml index 2e36751..e07486b 100644 --- a/android/app/src/main/res/values-uk/strings.xml +++ b/android/app/src/main/res/values-uk/strings.xml @@ -1,6 +1,7 @@ Нік: + налаштування Сервер не підтримує групові шари. Групові шари підтримуються в версії %s та вище; цей сервер зараз має версію %s. Ваша шара була перетворена на індивідуальну. Створити новий відстежувальний лінк Шарінг місцеположення активний! Нажміть кнопку поділитися щоб скопіювати публічно доступний URL для вашої шари. @@ -107,4 +108,4 @@ Ви хочете дозволити в майбутньому створювати сеанси з трансляцій із цією ідентифікацією\? URL серверу що ви ввели не вірний. Сервіси місцеположення вимкнуті або заборонені. Будь ласка включіть високоточні сервіси місцеположення для того щоб можно було ділитися вашим положенням. - \ No newline at end of file + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 8370c12..7fe2400 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -155,6 +155,10 @@ Disable trust anchor validation for .onion hosts (not recommended) Disable trust anchor and hostname validation for .onion hosts (not recommended) + Resume shares on device restart + Resume shares on boot + Do not resume shares on boot + Appearance Night mode diff --git a/android/app/src/main/res/xml/root_preferences.xml b/android/app/src/main/res/xml/root_preferences.xml index addb5e5..8ca2585 100644 --- a/android/app/src/main/res/xml/root_preferences.xml +++ b/android/app/src/main/res/xml/root_preferences.xml @@ -104,6 +104,11 @@ app:entries="@array/tls_validation_types" app:entryValues="@array/tls_validation_type_values" /> + diff --git a/android/build.gradle b/android/build.gradle index 95a3996..a9a4937 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:7.4.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index fff0b73..33b82e1 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/android/gradlew b/android/gradlew old mode 100755 new mode 100644 diff --git a/backend-php/api/post.php b/backend-php/api/post.php index 05048c1..562e6e9 100644 --- a/backend-php/api/post.php +++ b/backend-php/api/post.php @@ -21,45 +21,37 @@ $sid = $_POST["sid"]; $session = new Client($memcache, $sid); if (!$session->exists()) die($LANG['session_expired']."\n"); -if (!$session->isEncrypted()) { - // Perform input validation. - $lat = floatval($_POST["lat"]); - $lon = floatval($_POST["lon"]); - $time = floatval($_POST["time"]); - if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) die($LANG['location_invalid']."\n"); +$lat = $_POST["lat"]; +$lon = $_POST["lon"]; +$time = $_POST["time"]; +$speed = isset($_POST["spd"]) ? $_POST["spd"] : null; +$altitude = isset($_POST["alt"]) ? $_POST["alt"] : null; +$accuracy = isset($_POST["acc"]) ? $_POST["acc"] : null; +$provider = isset($_POST["prv"]) ? $_POST["prv"] : null; - // Not all devices report speed and accuracy, but if available, report them - // too. - $speed = isset($_POST["spd"]) ? floatval($_POST["spd"]) : null; - $accuracy = isset($_POST["acc"]) ? floatval($_POST["acc"]) : null; - $provider = isset($_POST["prv"]) && $_POST["prv"] == "1" ? 1 : 0; - - // The location data object contains the sharing interval (i), duration (d) - // and a location list (l). Each entry in the location list contains a - // latitude, longitude, timestamp, provider, accuracy and speed, in that - // order, as an array. - $session->addPoint([$lat, $lon, $time, $provider, $accuracy, $speed])->save(); - -} else { - // Input validation cannot be performed for end-to-end encrypted data. - $lat = $_POST["lat"]; - $lon = $_POST["lon"]; - $time = $_POST["time"]; - $speed = isset($_POST["spd"]) ? $_POST["spd"] : null; - $accuracy = isset($_POST["acc"]) ? $_POST["acc"] : null; - $provider = isset($_POST["prv"]) ? $_POST["prv"] : null; +// The location data object contains the sharing interval (i), duration (d) +// and a location list (l). Each entry in the location list contains a +// latitude, longitude, timestamp, provider, accuracy and speed, in that +// order, as an array. +$point = [$lat, $lon, $time, $provider, $accuracy, $speed, $altitude]; +if ($session->isEncrypted()) { // End-to-end encrypted connections also have an IV field used to decrypt // the data fields. requirePOST("iv"); $iv = $_POST["iv"]; // The IV field is prepended to the array to send to the client. - $session->addPoint([$iv, $lat, $lon, $time, $provider, $accuracy, $speed])->save(); + array_unshift($point , $iv); +} else { + // Perform input validation + if (floatval($lat) < -90 || floatval($lat) > 90 || floatval($lon) < -180 || floatval($lon) > 180) die($LANG['location_invalid']."\n"); } +$session->addPoint($point)->save(); + if ($session->hasExpired()) { echo $LANG['session_expired']."\n"; } else { echo "OK\n".getConfig("public_url")."?%s\n".implode(",", $session->getTargetIDs())."\n"; -} +} \ No newline at end of file diff --git a/backend-php/dynamic.js.php b/backend-php/dynamic.js.php index 325bead..47e2867 100644 --- a/backend-php/dynamic.js.php +++ b/backend-php/dynamic.js.php @@ -15,5 +15,8 @@ var MAX_POINTS = ; var VELOCITY_DELTA_TIME = ; var TRAIL_COLOR = ; var VELOCITY_UNIT = ; +var ALTITUDE_UNIT = ; +var SHOW_VELOCITY = ; +var SHOW_ALTITUDE_AMSL = ; var OFFLINE_TIMEOUT = ; var REQUEST_TIMEOUT = ; diff --git a/backend-php/include/config-sample.php b/backend-php/include/config-sample.php index 9b9d07d..9186369 100644 --- a/backend-php/include/config-sample.php +++ b/backend-php/include/config-sample.php @@ -274,6 +274,16 @@ // KILOMETERS_PER_HOUR, MILES_PER_HOUR, METERS_PER_SECOND "velocity_unit" => KILOMETERS_PER_HOUR, +// The unit of measurement of altitude. Valid are: +// METERS, FEET +"altitude_unit" => METERS, + +// Display velocity below marker +"show_velocity" => true, + +// Display altitude AMSL below marker +"show_altitude_amsl"=> true, + // The publicly accessible URL to reach Hauk, with trailing slash. "public_url" => 'https://example.com/' diff --git a/backend-php/include/inc.php b/backend-php/include/inc.php index 64b6d16..318a455 100644 --- a/backend-php/include/inc.php +++ b/backend-php/include/inc.php @@ -59,6 +59,16 @@ const METERS_PER_SECOND = array( "mpsMultiplier" => 1, "unit" => "m/s" ); +const METERS = array( + // Absolute distance in meters + "metersMultiplier" => 1, + "unit" => "m" +); +const FEET = array( + // Absolute distance in feet + "metersMultiplier" => 3.280839895, + "unit" => "ft" +); // Load fallback language. include(__DIR__."/lang/en/texts.php"); @@ -149,6 +159,9 @@ const DEFAULTS = array( "v_data_points" => 2, "trail_color" => '#d80037', "velocity_unit" => KILOMETERS_PER_HOUR, + "altitude_unit" => METERS, + "show_velocity" => true, + "show_altitude_amsl" => true, "public_url" => 'https://example.com/' ); diff --git a/frontend/main.js b/frontend/main.js index 14581d2..5bc0b3a 100644 --- a/frontend/main.js +++ b/frontend/main.js @@ -700,12 +700,13 @@ function processUpdate(data, init) { var lastPoint = shares[user].points.length > 0 ? shares[user].points[shares[user].points.length - 1] : null; for (var i = 0; i < users[user].length; i++) { - var lat = users[user][i][0]; - var lon = users[user][i][1]; - var time = users[user][i][2]; - var prov = users[user][i][3]; - var acc = users[user][i][4]; - var spd = users[user][i][5]; + var lat = getValueOrNull(users[user][i],0); + var lon = getValueOrNull(users[user][i],1); + var time = getValueOrNull(users[user][i],2); + var prov = getValueOrNull(users[user][i],3); + var acc = getValueOrNull(users[user][i],4); + var spd = getValueOrNull(users[user][i],5); + var alt = getValueOrNull(users[user][i],6); // Default to "Fine" provider for older clients. if (prov === null) prov = LOC_PROVIDER_FINE; @@ -726,14 +727,23 @@ function processUpdate(data, init) { '
' + '

' + '' + - '' + - '0.0 ' + - VELOCITY_UNIT.unit + - '' + + '' + + 'vel:' + + '0.0 ' + + VELOCITY_UNIT.unit + '' + + '
' + + '
' + + '' + + 'alt:' + + '0.0 ' + + ALTITUDE_UNIT.unit + '' + + '' + + '' + '' + '

' + '', - iconAnchor: [33, 18] + // FIXME: hard-coded and dependend on style.css .marker + iconAnchor: [48, 18] }); shares[user].marker = L.marker([lat, lon], {icon: shares[user].icon}).on("click", function() { follow(this.haukUser); @@ -754,7 +764,7 @@ function processUpdate(data, init) { shares[user].circle.setLatLng([lat, lon]); if (acc !== null) shares[user].circle.setRadius(acc); } - shares[user].points.push({lat: lat, lon: lon, line: line, time: time, spd: spd, acc: acc}); + shares[user].points.push({lat: lat, lon: lon, line: line, time: time, spd: spd, acc: acc, alt: alt}); lastPoint = shares[user].points[shares[user].points.length - 1]; } } @@ -797,6 +807,20 @@ function processUpdate(data, init) { vel = velocity(dist, time); eVelocity.textContent = vel.toFixed(1);; } + + // Altitude (If available) + var eAltitude = document.getElementById("altitude-" + shares[user].id); + var alt = 0; + var eAltitudeContainer = document.getElementById("altitude-container-" + shares[user].id); + if (lastPoint !== null && lastPoint.alt !== null && eAltitude !== null) { + alt = lastPoint.alt * ALTITUDE_UNIT.metersMultiplier; + eAltitude.textContent = alt.toFixed(1); + if (SHOW_ALTITUDE_AMSL) { + eAltitudeContainer.classList.remove("hidden"); + } + } else { + eAltitudeContainer.classList.add("hidden"); + } // Flag that the first location has been received, for map centering. if (lastPoint !== null && !hasReceivedFirst) { @@ -906,6 +930,14 @@ function processUpdate(data, init) { } } +function getValueOrNull(points,idx) { + var value = null; + if (idx < points.length) { + value = points[idx]; + } + return value; +} + // Calculates the distance between two points on a sphere using the Haversine // algorithm. function distance(from, to) { diff --git a/frontend/style.css b/frontend/style.css index 4967b12..4fb6db8 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -264,7 +264,7 @@ a:last-child > .store-icon { /* The outer marker div. */ .marker { - width: 66px; + width: 96px; height: 62px; } @@ -312,8 +312,8 @@ a:last-child > .store-icon { width: 100%; border-radius: 15px; text-align: center; - padding: 2px 0; - line-height: 100%; + padding: 5px 0 5px; + line-height: 125%; font-family: sans-serif; overflow: hidden; white-space: nowrap; @@ -334,7 +334,7 @@ a:last-child > .store-icon { background-color: rgba(165,0,42,0.5); } -.marker p.dead > span.velocity { +.marker p.dead > span.metric { display: none; } @@ -343,3 +343,20 @@ a:last-child > .store-icon { background: none; border: none; } + +.metric-label { + display:inline-block; + width: 30%; + text-align:right; + vertical-align: bottom; + padding-right: 0.5em; +} + +.metric-value { + display:inline-block; + width: 70%; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: bottom; +} diff --git a/hooks/build b/hooks/build old mode 100755 new mode 100644 diff --git a/hooks/post_push b/hooks/post_push old mode 100755 new mode 100644 diff --git a/hooks/pre_build b/hooks/pre_build old mode 100755 new mode 100644 diff --git a/hooks/pre_push b/hooks/pre_push old mode 100755 new mode 100644 diff --git a/install.sh b/install.sh old mode 100755 new mode 100644