function createUpdateFollowUp({ storage = null, getLogin = () => '' } = {}) { const sourceKey = 'stackchain.update-follow-up-source.v1'; const clean = (value, limit) => String(value || '').replace(/\s+/g, ' ').trim().slice(0, limit); const safeRepository = value => { const candidate = String(value || ''); return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(candidate) && candidate.split('/').every(part => part !== '.' && part !== '..') ? candidate : ''; }; const safeUrl = value => { try { const parsed = new URL(String(value || '')); return parsed.protocol === 'https:' || parsed.protocol === 'http:' ? parsed.href : ''; } catch (_error) { return ''; } }; function draft(detail = {}) { const source = safeUrl(detail.url || detail.latest_comment?.url); const context = clean(detail.latest_comment?.body || detail.subject_body, 9000); const title = clean(detail.title, 228); const sections = []; if (source) sections.push('Source: ' + source); if (context) sections.push('Latest context:\n> ' + context.replace(/\n/g, '\n> ')); return { repository: safeRepository(detail.repository), title: ('Follow up: ' + (title || 'Unread update')).slice(0, 240), body: sections.join('\n\n').slice(0, 9500), }; } function readSource() { const login = String(getLogin() || '').trim(); if (!login) return null; try { const record = JSON.parse(storage?.getItem(sourceKey) || 'null'); return record?.version === 1 && record.ownerLogin === login && Number.isInteger(record.source?.notification_id) && record.source.notification_id > 0 ? record : null; } catch (_error) { return null; } } function writeSource(record) { storage?.setItem(sourceKey, JSON.stringify(record)); } function stageSource(item = {}) { const ownerLogin = String(getLogin() || '').trim(); const notificationId = Number(item.notification_id); if (!ownerLogin) throw new Error('Confirm your Gitea account before continuing this follow-up.'); if (!Number.isInteger(notificationId) || notificationId <= 0) throw new Error('Choose a valid source update.'); const source = { notification_id: notificationId, repository: safeRepository(item.repository), number: Number.isInteger(item.number) && item.number > 0 ? item.number : null, title: clean(item.title, 240), kind: 'update', has_update: true, }; writeSource({ version:1, ownerLogin, source, admission:null }); return { ...source }; } function source() { const record = readSource(); return record ? { ...record.source } : null; } function discardSource() { if (!readSource()) return false; storage?.removeItem(sourceKey); return true; } async function complete({ admit, queueRead, advance }) { const record = readSource(); if (!record) throw new Error('Reopen the source update before continuing.'); let admission = record.admission; if (!admission) { admission = await admit(); writeSource({ ...record, admission }); } await queueRead(record.source.notification_id); const advanced = await advance({ ...record.source }); storage?.removeItem(sourceKey); return { admission, advanced:Boolean(advanced) }; } return { draft, stageSource, source, discardSource, complete }; } if (typeof module !== 'undefined' && module.exports) module.exports = createUpdateFollowUp;