Implement hashtag url storage for easy retry
This commit is contained in:
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user