refactor sonarqube findings
This commit is contained in:
+49
-33
@@ -914,34 +914,33 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
return sendForm(path, method, data);
|
return sendForm(path, method, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createServerRouteFromLocal(routePackage) {
|
function checkpointLocalUpload(routePackage) {
|
||||||
const checkpoint = () => {
|
|
||||||
const latest = ns.LocalRoutes.get(routePackage.id);
|
const latest = ns.LocalRoutes.get(routePackage.id);
|
||||||
// GPS samples may arrive while media requests are pending.
|
// GPS samples may arrive while media requests are pending.
|
||||||
if (latest) routePackage.gpx = latest.gpx;
|
if (latest) routePackage.gpx = latest.gpx;
|
||||||
return ns.LocalRoutes.save(routePackage);
|
return ns.LocalRoutes.save(routePackage);
|
||||||
};
|
}
|
||||||
let id = routePackage.route.serverRouteId;
|
|
||||||
let previousPois = [];
|
async function prepareServerRoute(routePackage) {
|
||||||
|
const id = routePackage.route.serverRouteId;
|
||||||
if (id) {
|
if (id) {
|
||||||
const payload = await readJson(`/routes/${encodeURIComponent(id)}`);
|
const payload = await readJson(`/routes/${encodeURIComponent(id)}`);
|
||||||
const existing = payload.route || payload;
|
const existing = payload.route || payload;
|
||||||
if (existing.activatedAt || existing.activated_at) {
|
if (existing.activatedAt || existing.activated_at) {
|
||||||
throw new Error(ns.I18n.t("editor.read-only-hint"));
|
throw new Error(ns.I18n.t("editor.read-only-hint"));
|
||||||
}
|
}
|
||||||
previousPois = existing.pois || [];
|
|
||||||
await sendForm(`/routes/${encodeURIComponent(id)}`, 'PUT', localRouteUploadForm(routePackage));
|
await sendForm(`/routes/${encodeURIComponent(id)}`, 'PUT', localRouteUploadForm(routePackage));
|
||||||
} else {
|
return { id, previousPois: existing.pois || [] };
|
||||||
const payload = await sendForm('/routes', 'POST', localRouteUploadForm(routePackage));
|
|
||||||
id = routeId(payload.route || payload);
|
|
||||||
if (!id) throw new Error(ns.I18n.t("editor.error.send-route"));
|
|
||||||
routePackage.route.serverRouteId = id;
|
|
||||||
checkpoint();
|
|
||||||
}
|
}
|
||||||
const orderedPois = [...(routePackage.pois || [])]
|
const payload = await sendForm('/routes', 'POST', localRouteUploadForm(routePackage));
|
||||||
.sort((left, right) => Number(left.sequence) - Number(right.sequence));
|
const createdId = routeId(payload.route || payload);
|
||||||
for (const [index, poi] of orderedPois.entries()) {
|
if (!createdId) throw new Error(ns.I18n.t("editor.error.send-route"));
|
||||||
const base = `/routes/${encodeURIComponent(id)}/pois`;
|
routePackage.route.serverRouteId = createdId;
|
||||||
|
checkpointLocalUpload(routePackage);
|
||||||
|
return { id: createdId, previousPois: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncLocalPoi(base, poi, index, routePackage) {
|
||||||
const payload = await sendJson(poi.serverPoiId ? `${base}/${poi.serverPoiId}` : base,
|
const payload = await sendJson(poi.serverPoiId ? `${base}/${poi.serverPoiId}` : base,
|
||||||
poi.serverPoiId ? 'PUT' : 'POST', {
|
poi.serverPoiId ? 'PUT' : 'POST', {
|
||||||
description: poi.description || '', lat: poi.lat, lon: poi.lon, sequence: index,
|
description: poi.description || '', lat: poi.lat, lon: poi.lon, sequence: index,
|
||||||
@@ -950,38 +949,55 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
});
|
});
|
||||||
poi.serverPoiId = poiId(payload.poi || payload);
|
poi.serverPoiId = poiId(payload.poi || payload);
|
||||||
if (!poi.serverPoiId) throw new Error(ns.I18n.t("editor.error.send-station", { value1: poi.title || '' }));
|
if (!poi.serverPoiId) throw new Error(ns.I18n.t("editor.error.send-station", { value1: poi.title || '' }));
|
||||||
checkpoint();
|
checkpointLocalUpload(routePackage);
|
||||||
const mediaBase = `${base}/${poi.serverPoiId}`;
|
}
|
||||||
const oldPoi = previousPois.find(old => String(old.id) === String(poi.serverPoiId));
|
|
||||||
|
async function syncLocalAudio(mediaBase, poi, oldPoi, routePackage) {
|
||||||
if (poi.audio) {
|
if (poi.audio) {
|
||||||
await uploadStoredMedia(`${mediaBase}/audio`, 'audio', poi.audio, 'audio.bin',
|
await uploadStoredMedia(`${mediaBase}/audio`, 'audio', poi.audio, 'audio.bin',
|
||||||
oldPoi?.audioUrl || poi.serverAudio ? 'PUT' : 'POST');
|
oldPoi?.audioUrl || poi.serverAudio ? 'PUT' : 'POST');
|
||||||
poi.serverAudio = true;
|
poi.serverAudio = true;
|
||||||
checkpoint();
|
checkpointLocalUpload(routePackage);
|
||||||
} else if (oldPoi?.audioUrl) {
|
} else if (oldPoi?.audioUrl) {
|
||||||
await sendDelete(`${mediaBase}/audio`);
|
await sendDelete(`${mediaBase}/audio`);
|
||||||
poi.serverAudio = false;
|
poi.serverAudio = false;
|
||||||
checkpoint();
|
checkpointLocalUpload(routePackage);
|
||||||
}
|
}
|
||||||
for (const [imageIndex, image] of (poi.images || []).entries()) {
|
}
|
||||||
|
|
||||||
|
async function removeMissingServerItems(previous, current, serverIdKey, base) {
|
||||||
|
const retained = new Set(current.map(item => String(item[serverIdKey])));
|
||||||
|
for (const item of previous) {
|
||||||
|
if (!retained.has(String(item.id))) await sendDelete(`${base}/${item.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncLocalPictures(mediaBase, poi, oldPoi, routePackage) {
|
||||||
|
const images = poi.images || [];
|
||||||
|
for (const [index, image] of images.entries()) {
|
||||||
const path = image.serverPictureId ? `${mediaBase}/pictures/${image.serverPictureId}` : `${mediaBase}/pictures`;
|
const path = image.serverPictureId ? `${mediaBase}/pictures/${image.serverPictureId}` : `${mediaBase}/pictures`;
|
||||||
const picture = await uploadStoredMedia(path, 'picture', image, `bild-${imageIndex + 1}.bin`,
|
const picture = await uploadStoredMedia(path, 'picture', image, `bild-${index + 1}.bin`,
|
||||||
image.serverPictureId ? 'PUT' : 'POST');
|
image.serverPictureId ? 'PUT' : 'POST');
|
||||||
image.serverPictureId = (picture.picture || picture).id;
|
image.serverPictureId = (picture.picture || picture).id;
|
||||||
if (!image.serverPictureId) throw new Error(ns.I18n.t("editor.error.send-file", { value1: image.name || '' }));
|
if (!image.serverPictureId) throw new Error(ns.I18n.t("editor.error.send-file", { value1: image.name || '' }));
|
||||||
checkpoint();
|
checkpointLocalUpload(routePackage);
|
||||||
}
|
}
|
||||||
for (const oldImage of oldPoi?.images || []) {
|
await removeMissingServerItems(oldPoi?.images || [], images, 'serverPictureId', `${mediaBase}/pictures`);
|
||||||
if (!(poi.images || []).some(image => String(image.serverPictureId) === String(oldImage.id))) {
|
|
||||||
await sendDelete(`${mediaBase}/pictures/${oldImage.id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const oldPoi of previousPois) {
|
|
||||||
if (!orderedPois.some(poi => String(poi.serverPoiId) === String(oldPoi.id))) {
|
|
||||||
await sendDelete(`/routes/${encodeURIComponent(id)}/pois/${oldPoi.id}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createServerRouteFromLocal(routePackage) {
|
||||||
|
const { id, previousPois } = await prepareServerRoute(routePackage);
|
||||||
|
const base = `/routes/${encodeURIComponent(id)}/pois`;
|
||||||
|
const orderedPois = [...(routePackage.pois || [])]
|
||||||
|
.sort((left, right) => Number(left.sequence) - Number(right.sequence));
|
||||||
|
for (const [index, poi] of orderedPois.entries()) {
|
||||||
|
await syncLocalPoi(base, poi, index, routePackage);
|
||||||
|
const mediaBase = `${base}/${poi.serverPoiId}`;
|
||||||
|
const oldPoi = previousPois.find(old => String(old.id) === String(poi.serverPoiId));
|
||||||
|
await syncLocalAudio(mediaBase, poi, oldPoi, routePackage);
|
||||||
|
await syncLocalPictures(mediaBase, poi, oldPoi, routePackage);
|
||||||
}
|
}
|
||||||
|
await removeMissingServerItems(previousPois, orderedPois, 'serverPoiId', base);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
if (typeof globalThis.crypto?.getRandomValues !== 'function') {
|
if (typeof globalThis.crypto?.getRandomValues !== 'function') {
|
||||||
throw new Error(ns.I18n.t("local.error.identity"));
|
throw new TypeError(ns.I18n.t("local.error.identity"));
|
||||||
}
|
}
|
||||||
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(32));
|
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(32));
|
||||||
const value = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
const value = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||||
|
|||||||
@@ -725,7 +725,7 @@ test('256-bit creator identity survives reload, route deletion and requires expl
|
|||||||
|
|
||||||
test('creator identity never falls back to insecure randomness or replaces corrupt stored identity', async () => {
|
test('creator identity never falls back to insecure randomness or replaces corrupt stored identity', async () => {
|
||||||
const { api, localStorage } = await loadLocalRoutes({ crypto: {} });
|
const { api, localStorage } = await loadLocalRoutes({ crypto: {} });
|
||||||
assert.throws(() => api.creatorId(), /Gerätekennung/);
|
assert.throws(() => api.creatorId(), error => error.name === 'TypeError' && /Gerätekennung/.test(error.message));
|
||||||
localStorage.setItem('wegwichtel.creator-id.v1', 'broken');
|
localStorage.setItem('wegwichtel.creator-id.v1', 'broken');
|
||||||
assert.throws(() => api.creatorId(), /Gerätekennung/);
|
assert.throws(() => api.creatorId(), /Gerätekennung/);
|
||||||
assert.equal(localStorage.getItem('wegwichtel.creator-id.v1'), 'broken');
|
assert.equal(localStorage.getItem('wegwichtel.creator-id.v1'), 'broken');
|
||||||
|
|||||||
Reference in New Issue
Block a user