Implement hashtag url storage for easy retry

This commit is contained in:
2026-08-03 23:03:18 +02:00
parent 0d0da28660
commit 59b5f2dde6
2 changed files with 45 additions and 0 deletions
+5
View File
@@ -28,6 +28,11 @@ PORT=8090 ./build/immich-sync
Open `http://localhost:8090`.
After both share URLs are valid, starting a job updates the browser fragment in the form
`#left=<encoded-share-url>&right=<encoded-share-url>`. Reloading or bookmarking that URL restores both fields and
automatically starts a new inspection. Passwords are intentionally excluded from the fragment and must be entered
again for protected shares.
## WebSocket protocol
All messages are JSON. Each WebSocket accepts exactly one complete job request and closes after its terminal response. A client that does not send a request within 10 seconds is disconnected. Passwords are accepted only in client messages and are never placed in URLs or echoed in status responses.
+40
View File
@@ -21,6 +21,43 @@ let jobCompleted = false;
let abortRequested = false;
let syncRunning = false;
function normalizedShareUrl(value) {
try {
const url = new URL(value.trim());
if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password) return undefined;
const marker = url.pathname.lastIndexOf('/share/');
if (marker < 0) return undefined;
const credential = url.pathname.slice(marker + 7).replace(/\/+$/, '');
if (!credential || credential.includes('/')) return undefined;
url.search = '';
url.hash = '';
return url.toString();
} catch {
return undefined;
}
}
function updateShareFragment() {
const left = normalizedShareUrl($('#leftUrl').value);
const right = normalizedShareUrl($('#rightUrl').value);
if (!left || !right) {
if (location.hash) history.replaceState(null, '', `${location.pathname}${location.search}`);
return;
}
const fragment = new URLSearchParams({ left, right });
history.replaceState(null, '', `${location.pathname}${location.search}#${fragment}`);
}
function restoreShareFragment() {
const fragment = new URLSearchParams(location.hash.slice(1));
const left = normalizedShareUrl(fragment.get('left') || '');
const right = normalizedShareUrl(fragment.get('right') || '');
if (!left || !right) return false;
$('#leftUrl').value = left;
$('#rightUrl').value = right;
return true;
}
function openJob(request) {
if (socket && socket.readyState !== WebSocket.CLOSED) {
ui.message.textContent = 'Please wait for the current job to finish.';
@@ -34,6 +71,7 @@ function openJob(request) {
jobCompleted = false;
abortRequested = false;
ui.inspect.disabled = true;
updateShareFragment();
setConnection('connecting', 'Connecting');
jobSocket.addEventListener('open', () => {
@@ -242,6 +280,7 @@ function handle(message) {
}
ui.inspect.addEventListener('click', inspect);
for (const input of [$('#leftUrl'), $('#rightUrl')]) input.addEventListener('input', updateShareFragment);
for (const input of $$('input')) input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') inspect();
});
@@ -254,3 +293,4 @@ ui.abort.addEventListener('click', () => {
});
setConnection('idle', 'Idle');
if (restoreShareFragment()) inspect();