Changeset b79466e in Klonkt
- Timestamp:
- 06/26/2026 10:04:14 AM (2 months ago)
- Branches:
- main
- Children:
- a404018
- Parents:
- 14e2ce1
- Location:
- src
- Files:
-
- 2 edited
-
server.js (modified) (2 diffs)
-
services/ActivityPubService.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/server.js
r14e2ce1 rb79466e 58 58 import ogRoutes from './routes/og.js'; 59 59 import apRoutes from './routes/activitypub.js'; 60 import { apWants, startDeliveryWorker, autoMigrateCircles } from './services/ActivityPubService.js';60 import { apWants, startDeliveryWorker, autoMigrateCircles, selfHealTimeline } from './services/ActivityPubService.js'; 61 61 62 62 // SESSION_SECRET: use the env var if set. Otherwise auto-generate a strong one … … 162 162 startDeliveryWorker(); // retry failed fediverse deliveries with backoff 163 163 autoMigrateCircles(); // one-time: convert legacy circle_links -> AP auto-boost follows 164 selfHealTimeline(); // once per SELFHEAL_VERSION bump: re-sync the fediverse cache (covers/edits) after a drastic update 164 165 165 166 // Safety net: guarantee that there is always a primary site (solo/hub/circle). -
src/services/ActivityPubService.js
r14e2ce1 rb79466e 1105 1105 } 1106 1106 1107 // ── Self-heal: re-sync the fediverse cache (ap_timeline) after a DRASTIC update ── 1108 // Runs ONCE per SELFHEAL_VERSION bump — NOT on every boot. Re-fetches each cached 1109 // note and refreshes content + media (recovers covers/edits that were delivered 1110 // during a flux window, e.g. a fleet-wide update), and drops notes that are gone 1111 // (404/410). Bump SELFHEAL_VERSION only on a release that warrants a re-sync. 1112 const SELFHEAL_VERSION = 1; 1113 async function fetchNoteAP(url) { 1114 try { 1115 const r = await fetch(url, { headers: { Accept: 'application/activity+json' } }); 1116 if (r.status === 404 || r.status === 410) return 404; 1117 if (r.ok) return await r.json(); 1118 } catch { /* unreachable */ } 1119 return null; 1120 } 1121 function mediaFromNote(note) { 1122 const atts = (Array.isArray(note.attachment) ? note.attachment : []).map((a) => ({ url: safeUrl(a && a.url), type: (a && a.mediaType) || '' })).filter((m) => m.url); 1123 if (!atts.some((m) => !m.type || /image/i.test(m.type)) && note.image) { 1124 const im = Array.isArray(note.image) ? note.image[0] : note.image; 1125 const iu = safeUrl(typeof im === 'string' ? im : (im && im.url)); 1126 if (iu) atts.push({ url: iu, type: (im && im.mediaType) || 'image/jpeg' }); 1127 } 1128 return JSON.stringify(atts); 1129 } 1130 let _selfHealing = false; 1131 export async function selfHealTimeline() { 1132 if (_selfHealing) return; _selfHealing = true; 1133 try { 1134 let cur = 0; 1135 try { const r = db.prepare('SELECT value FROM app_settings WHERE key = ?').get('selfheal_version'); cur = r ? (parseInt(r.value, 10) || 0) : 0; } catch { return; } 1136 if (cur >= SELFHEAL_VERSION) return; // already healed for this version — skip on normal boots 1137 let rows = []; 1138 try { rows = db.prepare('SELECT id, content, media_json FROM ap_timeline ORDER BY rowid DESC LIMIT 200').all(); } catch { /* no table */ } 1139 let healed = 0; 1140 for (const r of rows) { 1141 try { 1142 const note = await fetchNoteAP(r.id); 1143 if (note === 404) { db.prepare('DELETE FROM ap_timeline WHERE id = ?').run(r.id); healed++; continue; } 1144 if (!note || typeof note !== 'object') continue; 1145 const html = HtmlSanitizerService.sanitize(note.content || ''); 1146 const media = mediaFromNote(note); 1147 if ((html && html !== r.content) || media !== (r.media_json || '[]')) { 1148 db.prepare('UPDATE ap_timeline SET content = ?, media_json = ? WHERE id = ?').run(html || r.content, media, r.id); 1149 healed++; 1150 } 1151 } catch { /* per-note best-effort */ } 1152 } 1153 try { db.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)').run('selfheal_version', String(SELFHEAL_VERSION)); } catch { /* ignore */ } 1154 if (rows.length) console.log(`[AP] self-heal v${SELFHEAL_VERSION}: ${healed}/${rows.length} timeline notes`); 1155 } catch { /* never block boot */ } finally { _selfHealing = false; } 1156 } 1157 1107 1158 // Follow a fediverse account by @handle (WebFinger → actor → signed Follow). 1108 1159 export async function followActor(site, handle, autoBoost = false) { … … 1263 1314 listOutbox, deliverOutboxDelete, 1264 1315 webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, getTimeline, sendInteraction, 1265 autoBoostCount, getCirkelPosts, getCirkelMembers, autoMigrateCircles, 1316 autoBoostCount, getCirkelPosts, getCirkelMembers, autoMigrateCircles, selfHealTimeline, 1266 1317 getNotifications, listBlocks, isBlockedAny, blockTarget, unblock, 1267 1318 deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)