105 lines
4.3 KiB
JavaScript
105 lines
4.3 KiB
JavaScript
function createProgressiveHumanGates(options = {}) {
|
|
const document = options.document || globalThis.document;
|
|
const location = options.location || globalThis.location;
|
|
const history = options.history || globalThis.history;
|
|
const storage = options.storage || globalThis.localStorage;
|
|
const isOnline = options.isOnline || (() => globalThis.navigator?.onLine !== false);
|
|
const fetchJson = options.fetchJson || (async (path, requestOptions = {}) => {
|
|
const response = await fetch(path, requestOptions);
|
|
const payload = await response.json().catch(() => ({}));
|
|
if (!response.ok) throw new Error(payload.error || payload.detail?.message || payload.detail || 'Review request failed.');
|
|
return payload;
|
|
});
|
|
const liveSnapshot = options.liveSnapshot || null;
|
|
const getIdentity = options.getIdentity || (async () => {
|
|
const response = liveSnapshot ? await liveSnapshot.acquire({requireIdentity:true}) :
|
|
await fetchJson('api/v1/live', {headers:{Accept:'application/json'}});
|
|
const user = response?.context?.user || {};
|
|
const login = String(user.login || '').trim();
|
|
return {login, accountKey:login && user.id ? String(user.id) + ':' + login : login};
|
|
});
|
|
const query = selector => document.querySelector(selector);
|
|
const nodes = {
|
|
count:query('#human-gates-count'), list:query('#human-gates-list'),
|
|
status:query('#human-gates-status'), panel:query('#human-gates'),
|
|
detail:query('#human-gate-detail'),
|
|
};
|
|
let login = '';
|
|
let accountKey = '';
|
|
let started = false;
|
|
let startFlight = null;
|
|
|
|
const controller = createHumanGates({
|
|
storage, isOnline, location, fetchJson,
|
|
getLogin:() => login, getAccountKey:() => accountKey, nodes,
|
|
});
|
|
|
|
const showError = error => {
|
|
if (nodes.status) nodes.status.textContent = error?.message || 'Human Gates are unavailable.';
|
|
};
|
|
const open = () => start(true).catch(showError);
|
|
query('#open-human-gates')?.addEventListener?.('click', open);
|
|
query('#close-human-gates')?.addEventListener?.('click', () => {
|
|
if (nodes.panel) nodes.panel.hidden = true;
|
|
if (location.hash === '#/my-work/human-gates') history.replaceState({}, '', '#/my-work');
|
|
});
|
|
nodes.list?.addEventListener?.('click', event => {
|
|
const card = event.target?.closest?.('[data-human-gate-id]');
|
|
if (card) controller.select(card.dataset.humanGateId);
|
|
});
|
|
nodes.detail?.addEventListener?.('click', event => {
|
|
const decision = event.target?.closest?.('[data-gate-decision]')?.dataset.gateDecision;
|
|
if (!decision) return;
|
|
const checklist = Object.fromEntries(Array.from(nodes.detail.querySelectorAll('[data-gate-checklist]')).map(input => [input.dataset.gateChecklist, input.checked]));
|
|
controller.decideAndNext(decision, {
|
|
checklist,
|
|
reason:nodes.detail.querySelector?.('[data-gate-reason]')?.value || '',
|
|
override_reason:nodes.detail.querySelector?.('[data-gate-override]')?.value || '',
|
|
}).catch(showError);
|
|
});
|
|
|
|
async function start(force = false) {
|
|
if (!force && location.hash !== '#/my-work/human-gates') return false;
|
|
if (started) {
|
|
if (nodes.panel) nodes.panel.hidden = false;
|
|
return true;
|
|
}
|
|
if (startFlight) return startFlight;
|
|
startFlight = (async () => {
|
|
const identity = await getIdentity();
|
|
login = String(identity?.login || '').trim();
|
|
accountKey = String(identity?.accountKey || login).trim();
|
|
if (!login) throw new Error('Authenticated account identity is required.');
|
|
await controller.open();
|
|
started = true;
|
|
return true;
|
|
})();
|
|
try { return await startFlight; }
|
|
finally { startFlight = null; }
|
|
}
|
|
|
|
return {
|
|
start,
|
|
handoff() {
|
|
return {
|
|
controller, started,
|
|
adoptIdentity(nextLogin, nextAccountKey) {
|
|
login = String(nextLogin || '').trim();
|
|
accountKey = String(nextAccountKey || login).trim();
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
window.stackchainProgressiveHumanGates = createProgressiveHumanGates({
|
|
document, liveSnapshot:window.stackchainProgressiveLiveSnapshot,
|
|
});
|
|
void window.stackchainProgressiveHumanGates.start().catch(() => {});
|
|
}
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
globalThis.createHumanGates = globalThis.createHumanGates || require('./human-gates.js');
|
|
module.exports = createProgressiveHumanGates;
|
|
}
|