diff --git a/.env_example b/.env_example
index 319d1c26..311fe592 100644
--- a/.env_example
+++ b/.env_example
@@ -3,5 +3,4 @@ ROOT=https://your-produciton-server.com
KEY=
USER_JOHNDOE=
USER_TEST=
-LOCALHOST=127.0.0.1
-LOCALHOSTv6=::1
+MAPBOX=
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 269894e8..fea0e1dd 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -14,6 +14,7 @@ jobs:
NODE_ENV: ${{ vars.NODE_ENV }}
KEY: ${{ secrets.KEY }}
USER_TEST: ${{ secrets.USER_TEST }}
+ MAPBOX: ${{ secrets.MAPBOX }}
steps:
- uses: actions/checkout@v3
@@ -27,7 +28,7 @@ jobs:
- run: npm run build --if-present
- name: Start server
run: |
- sudo NODE_ENV=$NODE_ENV KEY=$KEY USER_TEST=$USER_TEST npm start &
+ sudo NODE_ENV=$NODE_ENV KEY=$KEY USER_TEST=$USER_TEST MAPBOX=$MAPBOX npm start &
sleep 16 # Give server some time to start
- name: Check if server is running
run: |
diff --git a/README.md b/README.md
index b33c9377..a3b7040e 100644
--- a/README.md
+++ b/README.md
@@ -100,6 +100,11 @@ This application is designed to be used with the [OSMAND+ mobile app]([url](http
Due to a plugin called [Triprecording]([url](https://osmand.net/docs/user/plugins/trip-recording/))
Using the above link or by [clicking here](https://osmand.net/docs/user/plugins/trip-recording#recording-settings) more information can be found to setup webtracking or "online tracking"
+## Mapbox
+Mapbox requires an access token.
+These can be created for free [register required]([url](https://tiles.mapbox.com/signup))
+Once available, add it to the envrionment variables.
+
## DEMO
At this point, there is no demo, but here is a screenshot:

diff --git a/demo.png b/demo.png
index f012a3f9..8cfb494d 100644
Binary files a/demo.png and b/demo.png differ
diff --git a/httpdocs/color-table.svg b/httpdocs/color-table.svg
index d2999418..4e051225 100644
--- a/httpdocs/color-table.svg
+++ b/httpdocs/color-table.svg
@@ -1,159 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/httpdocs/icon.png b/httpdocs/icon.png
index 827ec59b..8cfbcb6f 100644
Binary files a/httpdocs/icon.png and b/httpdocs/icon.png differ
diff --git a/httpdocs/images/marker.svg b/httpdocs/images/marker.svg
index da4c914c..469c94a9 100644
--- a/httpdocs/images/marker.svg
+++ b/httpdocs/images/marker.svg
@@ -1,4 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/init/generatePassword.js b/init/generatePassword.js
index c3b26b0f..40961337 100644
--- a/init/generatePassword.js
+++ b/init/generatePassword.js
@@ -21,6 +21,13 @@ const rl = readline.createInterface({
output: process.stdout
});
+
+if (!process.env.KEY) {
+ console.error("KEY is missing! Please provide Environment Variable KEY. \nExample: KEY=your-key node ./init/generatePassword.js");
+ return;
+}
+
+
// Prompt user for input
rl.question('Enter Password to be generated: ', async (input) => {
const cryptedPassword = await crypt(input);
diff --git a/package-lock.json b/package-lock.json
index c241ae7e..da70d12a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,7 +35,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-leaflet": "^4.2.1",
- "react-router-dom": "^6.26.0",
+ "react-router-dom": "^6.26.1",
"toobusy-js": "^0.5.1"
},
"devDependencies": {
@@ -1967,9 +1967,9 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz",
- "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==",
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
+ "integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -11564,12 +11564,12 @@
}
},
"node_modules/react-router": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz",
- "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==",
+ "version": "6.26.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz",
+ "integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.19.0"
+ "@remix-run/router": "1.19.1"
},
"engines": {
"node": ">=14.0.0"
@@ -11579,13 +11579,13 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz",
- "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==",
+ "version": "6.26.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz",
+ "integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.19.0",
- "react-router": "6.26.0"
+ "@remix-run/router": "1.19.1",
+ "react-router": "6.26.1"
},
"engines": {
"node": ">=14.0.0"
diff --git a/package.json b/package.json
index aec3d180..43059118 100644
--- a/package.json
+++ b/package.json
@@ -92,7 +92,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-leaflet": "^4.2.1",
- "react-router-dom": "^6.26.0",
+ "react-router-dom": "^6.26.1",
"toobusy-js": "^0.5.1"
},
"_moduleAliases": {
diff --git a/src/app.ts b/src/app.ts
index a06be028..a37f7311 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -40,7 +40,7 @@ app.use((req, res, next) => { // clean up IPv6 Addresses
})
if (process.env.NODE_ENV != "development") {
- app.use(helmet({ contentSecurityPolicy: { directives: { "default-src": "'self'", "img-src": "*" } } }));
+ app.use(helmet({ contentSecurityPolicy: { directives: { "default-src": "'self'", "img-src": "*" } }, referrerPolicy: { policy: "strict-origin-when-cross-origin" } }));
}
app.use(cache);
app.use(compression())
@@ -58,7 +58,7 @@ app.set('trust proxy',true);
// routes
app.get(['/', '/login'], (req, res) => {
logger.log(req.ip + " - " + res.locals.ip, true);
- res.render("index");
+ res.render("index", {"mapbox": process.env.MAPBOX, "root": process.env.ROOT});
});
app.use('/write', writeRouter);
diff --git a/src/client/components/App.tsx b/src/client/components/App.tsx
index 62290271..19e7916e 100644
--- a/src/client/components/App.tsx
+++ b/src/client/components/App.tsx
@@ -4,6 +4,7 @@ import { useColorScheme } from '@mui/material/styles';
import { useMediaQuery } from '@mui/material';
import Start from '../pages/Start';
import Login from '../pages/Login';
+import axios from "axios";
export const Context = createContext([]);
@@ -38,18 +39,41 @@ const router = createBrowserRouter([
}
]);
+
const App = () => {
const [userInfo, setUserInfo] = useState(convertJwt());
const [isLoggedIn, setLogin] = useState(loginDefault(userInfo));
const { mode, setMode } = useColorScheme();
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
+ const [mapToken, setMapToken] = useState(null);
useEffect(() => {
setMode(prefersDarkMode ? "dark" : "light");
}, [prefersDarkMode, setMode]);
+
+ useEffect(() => {
+ if (!isLoggedIn) return;
+ const fetchToken = async () => {
+ try {
+ const token = localStorage.getItem("jwt");
+ const response = await axios.get("/read/maptoken", {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ setMapToken(response.data.mapbox);
+ } catch (error) {
+ console.error("Error fetching map token:", error);
+ }
+ };
+
+ fetchToken();
+ }, [isLoggedIn]);
+
return (
-
+
);
diff --git a/src/client/components/Map.tsx b/src/client/components/Map.tsx
index 6d40d427..e6b8eb41 100644
--- a/src/client/components/Map.tsx
+++ b/src/client/components/Map.tsx
@@ -54,18 +54,20 @@ const MultiColorPolyline = ({ cleanEntries }: { cleanEntries: Models.IEntry[] })
});
}
+
function Map({ entries }: { entries: Models.IEntry[] }) {
if (!entries?.length) {
return No Data to be displayed
}
- const [, , , , mode] = useContext(Context);
+
+ const [, , , , mode, , , mapToken] = useContext(Context);
const [mapStyle, setMapStyle] = useState(mode);
const lastEntry = entries.at(-1);
const cleanEntries = entries.filter((entry) => !entry.ignore);
const cleanEntriesWithoutLast = cleanEntries.slice(0, -1);
-
+ const replaceKeyword = "XXXREPLACEXXX";
// Function to create custom icon with dynamic className
function createCustomIcon(entry: Models.IEntry) {
@@ -128,7 +130,7 @@ function Map({ entries }: { entries: Models.IEntry[] }) {
>
diff --git a/src/client/components/Status.tsx b/src/client/components/Status.tsx
index 6dd5c886..ed2690ee 100644
--- a/src/client/components/Status.tsx
+++ b/src/client/components/Status.tsx
@@ -30,7 +30,6 @@ function getStatusData(entries) {
}, 0) / divider; // now that all values have been added together, devide by amount of them
- //console.log(prop + ": " + value + " divider: " + divider);
return value;
}
@@ -56,7 +55,6 @@ function getStatusData(entries) {
function getDistance() {
return cleanEntries.reduce((accumulatorValue: number, entry) => {
- console.log(accumulatorValue);
if (!entry.distance) { return accumulatorValue }
return accumulatorValue + parseFloat(entry.distance.horizontal);
}, 0) / 1000;
diff --git a/src/client/scripts/layers.ts b/src/client/scripts/layers.ts
index 0ede698c..68d19e50 100644
--- a/src/client/scripts/layers.ts
+++ b/src/client/scripts/layers.ts
@@ -40,7 +40,7 @@ export const layers:client.Layer[] = [
{
name: "Mapbox Satelite Streets",
attribution: '© Mapbox',
- url: 'https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoidHlwZS1zdHlsZSIsImEiOiJjbGJ4aG14enEwZ2toM3BvNW5uanhuOGRvIn0.7TUEM9vA-EYSt3WW_bcsAA',
+ url: `https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/{z}/{x}/{y}?access_token=XXXREPLACEXXX`,
markerStyle: "dark",
size: 512,
zoomOffset: -1
diff --git a/src/controller/read.ts b/src/controller/read.ts
index 4106251f..accf7220 100644
--- a/src/controller/read.ts
+++ b/src/controller/read.ts
@@ -36,5 +36,15 @@ router.get('/',
});
+router.get('/maptoken',
+ isLoggedIn,
+ async function mapToken(req: Request, res: Response, next: NextFunction) {
+ const mapbox = process.env.MAPBOX;
+ if (!mapbox) { return createError(res, undefined, `Missing configuration, environment variable not defined`, next); }
+
+ res.json({ mapbox });
+ }
+);
+
export default router;
diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts
index 9cad2b5b..4bea5c2b 100644
--- a/src/tests/integration.test.ts
+++ b/src/tests/integration.test.ts
@@ -335,4 +335,27 @@ describe('read and login', () => {
expect(response.status).toBe(200);
expect(response.data.entries.length).toBe(1);
});
+
+
+
+ test(`unable to get maptoken without logged in`, async () => {
+ try {
+ await axios.get("http://localhost:80/read/maptoken");
+ } catch (error) {
+ const axiosError = error as AxiosError;
+ if (axiosError.response) {
+ expect(axiosError.response.status).toBe(401);
+ } else {
+ console.error(axiosError);
+ }
+ }
+ });
+
+ test(`get maptoken with login`, async () => {
+ const response = await verifiedRequest("http://localhost:80/read/maptoken", token);
+ expect(response.status).toBe(200);
+ expect(response.data).toBeTruthy();
+ expect(response.data.mapbox).toBeTruthy();
+ expect(typeof response.data.mapbox).toBe('string');
+ });
});
\ No newline at end of file