mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 00:59:29 -07:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
407da90f8a | ||
|
|
3a0e6aa345 | ||
|
|
650dda7b61 | ||
|
|
8f1e8ffb74 | ||
|
|
93af84cbd8 | ||
|
|
117f66e9d0 | ||
|
|
bca9376edc | ||
|
|
8b076db25d | ||
|
|
807932fe3c | ||
|
|
7bb024eff5 | ||
|
|
f4a1a3a1d8 | ||
|
|
9a5efe9d48 | ||
|
|
14b1891efd |
@@ -10,8 +10,10 @@ server {
|
|||||||
|
|
||||||
add_header X-Weatherstar true always;
|
add_header X-Weatherstar true always;
|
||||||
|
|
||||||
|
include /etc/nginx/includes/wsqs_redirect.conf;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
index redirect.html index.html index.htm;
|
index index.html index.htm;
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1328
package-lock.json
generated
1328
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.2",
|
"version": "6.2.5",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -106,17 +106,34 @@ const init = async () => {
|
|||||||
|
|
||||||
// attempt to parse the url parameters
|
// attempt to parse the url parameters
|
||||||
const parsedParameters = parseQueryString();
|
const parsedParameters = parseQueryString();
|
||||||
const loadFromParsed = parsedParameters.latLonQuery && parsedParameters.latLon;
|
const loadFromParsed = !!parsedParameters.latLon;
|
||||||
|
|
||||||
// Auto load the parsed parameters and fall back to the previous query
|
// Auto load the parsed parameters and fall back to the previous query
|
||||||
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
|
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
|
||||||
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
|
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
|
||||||
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
|
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
|
||||||
|
|
||||||
if (query && latLon && !fromGPS) {
|
if (parsedParameters.latLonQuery && !parsedParameters.latLon) {
|
||||||
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
||||||
txtAddress.value = query;
|
txtAddress.value = parsedParameters.latLonQuery;
|
||||||
loadData(JSON.parse(latLon));
|
const geometry = await geocodeLatLonQuery(parsedParameters.latLonQuery);
|
||||||
|
if (geometry) {
|
||||||
|
doRedirectToGeometry(geometry);
|
||||||
|
}
|
||||||
|
} else if (latLon && !fromGPS) {
|
||||||
|
// update in-page search box if using cached data, or parsed parameter
|
||||||
|
if ((query && !loadFromParsed) || (parsedParameters.latLonQuery && loadFromParsed)) {
|
||||||
|
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
||||||
|
txtAddress.value = query;
|
||||||
|
}
|
||||||
|
// use lat-long lookup if that's all that was provided in the query string
|
||||||
|
if (loadFromParsed && parsedParameters.latLon && !parsedParameters.latLonQuery) {
|
||||||
|
const { lat, lon } = JSON.parse(latLon);
|
||||||
|
getForecastFromLatLon(lat, lon, true);
|
||||||
|
} else {
|
||||||
|
// otherwise use pre-stored data
|
||||||
|
loadData(JSON.parse(latLon));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (fromGPS) {
|
if (fromGPS) {
|
||||||
btnGetGpsClick();
|
btnGetGpsClick();
|
||||||
@@ -162,6 +179,26 @@ const init = async () => {
|
|||||||
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const geocodeLatLonQuery = async (query) => {
|
||||||
|
try {
|
||||||
|
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
||||||
|
data: {
|
||||||
|
text: query,
|
||||||
|
f: 'json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const loc = data.locations?.[0];
|
||||||
|
if (loc) {
|
||||||
|
return loc.feature.geometry;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Geocoding failed:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const autocompleteOnSelect = async (suggestion) => {
|
const autocompleteOnSelect = async (suggestion) => {
|
||||||
// Note: it's fine that this uses json instead of safeJson since it's infrequent and user-initiated
|
// Note: it's fine that this uses json instead of safeJson since it's infrequent and user-initiated
|
||||||
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
||||||
|
|||||||
@@ -298,4 +298,5 @@ export {
|
|||||||
hide,
|
hide,
|
||||||
screenCount,
|
screenCount,
|
||||||
atDefault,
|
atDefault,
|
||||||
|
hazards,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Setting from './utils/setting.mjs';
|
import Setting from './utils/setting.mjs';
|
||||||
import { reset as resetScroll, addScreen as addScroll } from './currentweatherscroll.mjs';
|
import { reset as resetScroll, addScreen as addScroll, hazards } from './currentweatherscroll.mjs';
|
||||||
import { json } from './utils/fetch.mjs';
|
import { json } from './utils/fetch.mjs';
|
||||||
|
|
||||||
let firstRun = true;
|
let firstRun = true;
|
||||||
@@ -42,8 +42,9 @@ const parseFeed = (textInput) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add single text scroll
|
// add single text scroll after hazards if present
|
||||||
resetScroll();
|
resetScroll();
|
||||||
|
addScroll(hazards);
|
||||||
addScroll(
|
addScroll(
|
||||||
() => (
|
() => (
|
||||||
{
|
{
|
||||||
@@ -81,6 +82,8 @@ const getFeed = async (url) => {
|
|||||||
|
|
||||||
// reset the scroll, then add the screens
|
// reset the scroll, then add the screens
|
||||||
resetScroll();
|
resetScroll();
|
||||||
|
// add the hazards scroll first
|
||||||
|
addScroll(hazards);
|
||||||
titles.forEach((title) => {
|
titles.forEach((title) => {
|
||||||
// data is provided to the screen handler, so we return a function
|
// data is provided to the screen handler, so we return a function
|
||||||
addScroll(
|
addScroll(
|
||||||
|
|||||||
@@ -5,21 +5,22 @@ import en from '../../vendor/auto/locale/en.js';
|
|||||||
|
|
||||||
// metar-taf-parser requires regex lookbehind
|
// metar-taf-parser requires regex lookbehind
|
||||||
// this does not work in iOS < 16.4
|
// this does not work in iOS < 16.4
|
||||||
// this is a detection algorithm for iOS versions
|
// this is a detection algorithm for missing lookbehind support
|
||||||
const isIos = /iP(ad|od|hone)/i.test(window.navigator.userAgent);
|
const supportsRegexLookAheadLookBehindCheck = () => {
|
||||||
let iosVersionOk = false;
|
try {
|
||||||
if (isIos) {
|
return (
|
||||||
// regex match the version string
|
// deliberately using RegExp for broader browser support during check
|
||||||
const iosVersionRaw = /OS (\d+)_(\d+)/.exec(window.navigator.userAgent);
|
/* eslint-disable prefer-regex-literals */
|
||||||
// check for match
|
'hibyehihi'
|
||||||
if (iosVersionRaw) {
|
.replace(new RegExp('(?<=hi)hi', 'g'), 'hello')
|
||||||
// break into parts
|
.replace(new RegExp('hi(?!bye)', 'g'), 'hey') === 'hibyeheyhello'
|
||||||
const iosVersionMajor = parseInt(iosVersionRaw[1], 10);
|
/* eslint-enable prefer-regex-literals */
|
||||||
const iosVersionMinor = parseInt(iosVersionRaw[2], 10);
|
);
|
||||||
if (iosVersionMajor > 16) iosVersionOk = true;
|
} catch {
|
||||||
if (iosVersionMajor === 16 && iosVersionMinor >= 4) iosVersionOk = true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
const supportsRegexLookAheadLookBehind = supportsRegexLookAheadLookBehindCheck();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Augment observation data by parsing METAR when API fields are missing
|
* Augment observation data by parsing METAR when API fields are missing
|
||||||
@@ -27,8 +28,8 @@ if (isIos) {
|
|||||||
* @returns {Object} - Augmented observation with parsed METAR data filled in
|
* @returns {Object} - Augmented observation with parsed METAR data filled in
|
||||||
*/
|
*/
|
||||||
const augmentObservationWithMetar = (observation) => {
|
const augmentObservationWithMetar = (observation) => {
|
||||||
// check for a metar message and for unusable ios versions
|
// check for a metar message and for regex lookbehind support
|
||||||
if (!observation?.rawMessage || (isIos && !iosVersionOk)) {
|
if (!observation?.rawMessage || (!supportsRegexLookAheadLookBehind)) {
|
||||||
return observation;
|
return observation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ url_encode() {
|
|||||||
|
|
||||||
# build query string from WSQS_ env vars
|
# build query string from WSQS_ env vars
|
||||||
while IFS='=' read -r key val; do
|
while IFS='=' read -r key val; do
|
||||||
|
# Skip empty lines
|
||||||
|
[ -z "$key" ] && continue
|
||||||
|
|
||||||
# Remove WSQS_ prefix and convert underscores to hyphens
|
# Remove WSQS_ prefix and convert underscores to hyphens
|
||||||
key="${key#WSQS_}"
|
key="${key#WSQS_}"
|
||||||
key="${key//_/-}"
|
key="${key//_/-}"
|
||||||
@@ -23,11 +26,16 @@ while IFS='=' read -r key val; do
|
|||||||
QS="${key}=${encoded_val}"
|
QS="${key}=${encoded_val}"
|
||||||
fi
|
fi
|
||||||
done << EOF
|
done << EOF
|
||||||
$(env | grep '^WSQS_')
|
$(env | grep '^WSQS_' || true)
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
mkdir -p /etc/nginx/includes
|
||||||
|
|
||||||
if [ -n "$QS" ]; then
|
if [ -n "$QS" ]; then
|
||||||
|
# Escape the query string for use in JavaScript (escape backslashes and single quotes)
|
||||||
|
QS_ESCAPED=$(printf '%s' "$QS" | sed "s/\\\\/\\\\\\\\/g; s/'/\\\'/g")
|
||||||
|
|
||||||
|
# Generate redirect.html with JavaScript logic
|
||||||
cat > "$ROOT/redirect.html" <<EOF
|
cat > "$ROOT/redirect.html" <<EOF
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -35,10 +43,36 @@ if [ -n "$QS" ]; then
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Redirecting</title>
|
<title>Redirecting</title>
|
||||||
<meta http-equiv="refresh" content="0;url=/index.html?$QS" />
|
<meta http-equiv="refresh" content="0;url=/index.html?$QS" />
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var wsqsParams = '$QS_ESCAPED';
|
||||||
|
var currentParams = window.location.search.substring(1);
|
||||||
|
var targetParams = currentParams || wsqsParams;
|
||||||
|
window.location.replace('/index.html?' + targetParams);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Generate nginx config for conditional redirects
|
||||||
|
cat > /etc/nginx/includes/wsqs_redirect.conf <<'EOF'
|
||||||
|
location = / {
|
||||||
|
if ($args = '') {
|
||||||
|
rewrite ^ /redirect.html last;
|
||||||
|
}
|
||||||
|
rewrite ^/$ /index.html?$args? redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /index.html {
|
||||||
|
if ($args = '') {
|
||||||
|
rewrite ^ /redirect.html last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
touch /etc/nginx/includes/wsqs_redirect.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|||||||
@@ -73,7 +73,10 @@
|
|||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
},
|
||||||
|
"cSpell.words": [
|
||||||
|
"hibyehihi"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
|
|||||||
Reference in New Issue
Block a user