105 lines
3.6 KiB
JavaScript
105 lines
3.6 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) module.exports = factory;
|
|
else root.createLoginController = factory;
|
|
}(typeof self !== 'undefined' ? self : this, function createLoginController(options) {
|
|
function validShareContinuation(value) {
|
|
if (typeof value !== 'string' || !value.startsWith('./?') || value.includes('#')) return './';
|
|
const limits = { title: 200, text: 8000, url: 2048 };
|
|
const params = new URLSearchParams(value.slice(3));
|
|
const entries = Array.from(params.entries());
|
|
if (!entries.length) return './';
|
|
const invalid = entries.some(([name, content]) => (
|
|
!Object.prototype.hasOwnProperty.call(limits, name)
|
|
|| !content
|
|
|| content.length > limits[name]
|
|
));
|
|
if (invalid) return './';
|
|
return value;
|
|
}
|
|
|
|
const form = options.form;
|
|
const status = options.status;
|
|
const button = options.button;
|
|
const fetchImpl = options.fetchImpl;
|
|
const location = options.location;
|
|
const continuation = validShareContinuation(options.continuation);
|
|
const setIntervalImpl = options.setIntervalImpl || setInterval;
|
|
const clearIntervalImpl = options.clearIntervalImpl || clearInterval;
|
|
let timer = null;
|
|
|
|
if (continuation !== './') status.textContent = 'Sign in to continue your shared capture.';
|
|
|
|
function showReason(reason) {
|
|
if (reason !== 'session-expired') return;
|
|
status.textContent = 'Your session expired. Private drafts remain on this device. Sign in to continue.';
|
|
}
|
|
|
|
function showRetryCountdown(seconds) {
|
|
let remaining = Math.max(1, Number.parseInt(seconds, 10) || 1);
|
|
button.disabled = true;
|
|
status.textContent = `Too many attempts. Try again in ${remaining} seconds.`;
|
|
timer = setIntervalImpl(() => {
|
|
remaining -= 1;
|
|
if (remaining <= 0) {
|
|
clearIntervalImpl(timer);
|
|
timer = null;
|
|
button.disabled = false;
|
|
status.textContent = 'You can try signing in again.';
|
|
return;
|
|
}
|
|
status.textContent = `Too many attempts. Try again in ${remaining} seconds.`;
|
|
}, 1000);
|
|
}
|
|
|
|
async function submit(accessToken, deviceLabel = 'This device') {
|
|
status.textContent = 'Signing in…';
|
|
let response;
|
|
try {
|
|
response = await fetchImpl('api/v1/session', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ access_token: accessToken, device_label: deviceLabel }),
|
|
});
|
|
} catch (_error) {
|
|
form.reset();
|
|
status.textContent = 'Sign-in failed. Check your connection and try again.';
|
|
return;
|
|
}
|
|
form.reset();
|
|
if (response.ok) {
|
|
location.replace(continuation);
|
|
return;
|
|
}
|
|
if (response.status === 429) {
|
|
showRetryCountdown(response.headers.get('Retry-After'));
|
|
return;
|
|
}
|
|
status.textContent = 'Sign-in failed. Check the token and try again.';
|
|
}
|
|
|
|
return { submit, showReason };
|
|
}));
|
|
|
|
if (typeof document !== 'undefined') {
|
|
const form = document.getElementById('sign-in');
|
|
const status = document.getElementById('status');
|
|
const button = document.getElementById('submit-sign-in');
|
|
const loginParams = new URLSearchParams(window.location.search);
|
|
const controller = createLoginController({
|
|
form,
|
|
status,
|
|
button,
|
|
fetchImpl: fetch.bind(window),
|
|
location: window.location,
|
|
continuation: loginParams.get('continue'),
|
|
});
|
|
controller.showReason(loginParams.get('reason'));
|
|
form.addEventListener('submit', event => {
|
|
event.preventDefault();
|
|
const data = new FormData(form);
|
|
const accessToken = data.get('access_token');
|
|
const deviceLabel = data.get('device_label');
|
|
controller.submit(accessToken, deviceLabel);
|
|
});
|
|
}
|