This commit is contained in:
TheCataliasTNT2k 2023-04-04 23:42:36 +02:00 committed by GitHub
commit bcba1a3f6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 337 additions and 97 deletions

63
.github/workflows/dockerbuild.yaml vendored Normal file
View file

@ -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 }}

View file

@ -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"]

View file

@ -39,7 +39,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -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'
}

View file

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="info.varden.hauk">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
@ -31,7 +34,8 @@
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden"
android:theme="@style/HomeTheme">
android:theme="@style/HomeTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -45,7 +49,7 @@
</activity>
<receiver
android:name=".global.Receiver"
android:name=".global.ExportedReceiver"
android:exported="true"
tools:ignore="ExportedReceiver">
<intent-filter>
@ -53,6 +57,20 @@
<action android:name="info.varden.hauk.START_ALONE_THEN_MAKE_TOAST" />
</intent-filter>
</receiver>
<receiver
android:name=".global.RebootReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name=".notify.CopyLinkReceiver"
android:exported="false">

View file

@ -45,6 +45,7 @@ public enum Constants {
public static final Preference<String> PREF_NICKNAME = new Preference.String("nickname", "");
public static final Preference<Integer> PREF_DURATION_UNIT = new Preference.Integer("durUnit", Constants.DURATION_UNIT_MINUTES);
public static final Preference<Boolean> PREF_ALLOW_ADOPTION = new Preference.Boolean("allowAdoption", true);
public static final Preference<Boolean> PREF_RESTART_ON_BOOT = new Preference.Boolean("restartOnBoot", true);
public static final Preference<NightModeStyle> PREF_NIGHT_MODE = new Preference.Enum<>("nightMode", NightModeStyle.FOLLOW_SYSTEM);
public static final Preference<Boolean> PREF_CONFIRM_STOP = new Preference.Boolean("confirmStop", true);
public static final Preference<Boolean> 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";

View file

@ -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")

View file

@ -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();
}
}

View file

@ -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 <V> 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));
}
}
}

View file

@ -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.

View file

@ -2,6 +2,7 @@
<resources>
<string name="pref_cryptUsername_title">Nom d\'usuari</string>
<string name="pref_cryptUsername_hint">&lt;opcional&gt;</string>
<string name="action_settings">Configuracions</string>
<string name="pref_cryptPassword_title">Password:</string>
<string name="label_duration">Durada de la compartició:</string>
<string name="pref_interval_title">Interval d\'actualització (s):</string>
@ -115,4 +116,4 @@
\n
\nSi activeu aquesta opció i compartiu la vostra ubicació amb un grup damics 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 dubicació 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.</string>
<string name="err_ver_group">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.</string>
</resources>
</resources>

View file

@ -3,6 +3,7 @@
<string name="label_heading">Kode irekiko kokaleku partekatzea</string>
<string name="label_status_none">Kokaleku partekatzea inaktibo</string>
<string name="btn_ok">Ados</string>
<string name="action_settings">Ezarpenak</string>
<string name="err_malformed_url">Sartu duzun zerbitzari URL-a baliogabea da.</string>
<string name="req_perms_message">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.</string>
<string name="pref_cryptServer_title">Zerbitzariaren URLa</string>
@ -93,4 +94,4 @@
\nIraungitzea: %s
\n
\nSaio hau berreskuratu nahi duzu\?</string>
</resources>
</resources>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="label_nickname">Нік:</string>
<string name="action_settings">налаштування</string>
<string name="err_ver_group">Сервер не підтримує групові шари. Групові шари підтримуються в версії %s та вище; цей сервер зараз має версію %s. Ваша шара була перетворена на індивідуальну.</string>
<string name="btn_link">Створити новий відстежувальний лінк</string>
<string name="ok_message">Шарінг місцеположення активний! Нажміть кнопку поділитися щоб скопіювати публічно доступний URL для вашої шари.</string>
@ -107,4 +108,4 @@
<string name="authorize_question">Ви хочете дозволити в майбутньому створювати сеанси з трансляцій із цією ідентифікацією\?</string>
<string name="err_malformed_url">URL серверу що ви ввели не вірний.</string>
<string name="err_location_disabled">Сервіси місцеположення вимкнуті або заборонені. Будь ласка включіть високоточні сервіси місцеположення для того щоб можно було ділитися вашим положенням.</string>
</resources>
</resources>

View file

@ -155,6 +155,10 @@
<string name="tls_validation_no_anchor_onion">Disable trust anchor validation for .onion hosts (not recommended)</string>
<string name="tls_validation_none_for_onion">Disable trust anchor and hostname validation for .onion hosts (not recommended)</string>
<string name="pref_restartOnBoot_title">Resume shares on device restart</string>
<string name="pref_restartOnBoot_summaryOn">Resume shares on boot</string>
<string name="pref_restartOnBoot_summaryOff">Do not resume shares on boot</string>
<!-- Appearance settings -->
<string name="prefs_header_appearance">Appearance</string>
<string name="prefs_nightMode_title">Night mode</string>

View file

@ -104,6 +104,11 @@
app:entries="@array/tls_validation_types"
app:entryValues="@array/tls_validation_type_values" />
<SwitchPreference
app:key="restartOnBoot"
app:title="@string/pref_restartOnBoot_title"
app:summaryOff="@string/pref_restartOnBoot_summaryOff"
app:summaryOn="@string/pref_restartOnBoot_summaryOn" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/prefs_header_appearance">

View file

@ -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

View file

@ -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

0
android/gradlew vendored Executable file → Normal file
View file

View file

@ -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";
}
}

View file

@ -15,5 +15,8 @@ var MAX_POINTS = <?php echo json_encode(getConfig("max_shown_pts")); ?>;
var VELOCITY_DELTA_TIME = <?php echo json_encode(getConfig("v_data_points")); ?>;
var TRAIL_COLOR = <?php echo json_encode(getConfig("trail_color")); ?>;
var VELOCITY_UNIT = <?php echo json_encode(getConfig("velocity_unit")); ?>;
var ALTITUDE_UNIT = <?php echo json_encode(getConfig("altitude_unit")); ?>;
var SHOW_VELOCITY = <?php echo json_encode(getConfig("show_velocity")); ?>;
var SHOW_ALTITUDE_AMSL = <?php echo json_encode(getConfig("show_altitude_amsl")); ?>;
var OFFLINE_TIMEOUT = <?php echo json_encode(getConfig("offline_timeout")); ?>;
var REQUEST_TIMEOUT = <?php echo json_encode(getConfig("request_timeout")); ?>;

View file

@ -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/'

View file

@ -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/'
);

View file

@ -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) {
'<div class="arrow still-' + shares[user].state + '" id="arrow-' + shares[user].id + '"></div>' +
'<p class="' + shares[user].state + '" id="label-' + shares[user].id + '">' +
'<span id="nickname-' + shares[user].id + '"></span>' +
'<span class="velocity">' +
'<span id="velocity-' + shares[user].id + '">0.0</span> ' +
VELOCITY_UNIT.unit +
'</span><span class="offline" id="last-seen-' + shares[user].id + '">' +
'<span class="metric' + (SHOW_VELOCITY ? "" : " hidden") + '">' +
'<span class="metric-label">vel:</span>' +
'<span class="metric-value"><span id="velocity-' + shares[user].id + '">0.0</span> ' +
VELOCITY_UNIT.unit + '</span>' +
'<br>' +
'</span>' +
'<span id="altitude-container-' + shares[user].id + '" class="metric'+ (SHOW_ALTITUDE_AMSL ? "" : " hidden") + '">' +
'<span class="metric-label">alt:</span>' +
'<span class="metric-value"><span id="altitude-' + shares[user].id + '">0.0</span> ' +
ALTITUDE_UNIT.unit + '</span>' +
'</span>' +
'<span class="offline" id="last-seen-' + shares[user].id + '">' +
'</span>' +
'</p>' +
'</div>',
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) {

View file

@ -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;
}

0
hooks/build Executable file → Normal file
View file

0
hooks/post_push Executable file → Normal file
View file

0
hooks/pre_build Executable file → Normal file
View file

0
hooks/pre_push Executable file → Normal file
View file

0
install.sh Executable file → Normal file
View file