fix: clear stale Bluesky polling cursor to restore interaction ingestion
This commit is contained in:
121
scripts/patch-conversations-bluesky-cursor-fix.mjs
Normal file
121
scripts/patch-conversations-bluesky-cursor-fix.mjs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* Patch: fix Bluesky notification polling cursor misuse.
|
||||||
|
*
|
||||||
|
* The Bluesky `listNotifications` API cursor is a PAGINATION cursor —
|
||||||
|
* it moves BACKWARDS in time (older pages). Using it as a "poll since
|
||||||
|
* last check" marker causes each poll to fetch progressively older
|
||||||
|
* notifications. Over time the cursor drifts far into the past, beyond
|
||||||
|
* Bluesky's notification history window, so every poll returns an empty
|
||||||
|
* list and no new interactions are ever stored.
|
||||||
|
*
|
||||||
|
* Fix: remove the cursor from the fetchBlueskyNotifications call so it
|
||||||
|
* always fetches the latest notifications (most-recent page), and rely
|
||||||
|
* on platform_id deduplication (upsertConversationItem) to avoid storing
|
||||||
|
* duplicates. Also clear any stale bluesky_cursor from the DB state so
|
||||||
|
* the dashboard no longer shows a misleading 2024 timestamp.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { access, readFile, writeFile } from "node:fs/promises";
|
||||||
|
|
||||||
|
const schedulerCandidates = [
|
||||||
|
"node_modules/@rmdes/indiekit-endpoint-conversations/lib/polling/scheduler.js",
|
||||||
|
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-conversations/lib/polling/scheduler.js",
|
||||||
|
];
|
||||||
|
|
||||||
|
const patchSpecs = [
|
||||||
|
{
|
||||||
|
name: "conversations-bluesky-cursor-fix-fetch",
|
||||||
|
candidates: schedulerCandidates,
|
||||||
|
marker: "// cursor intentionally omitted",
|
||||||
|
oldSnippet: ` const result = await fetchBlueskyNotifications({
|
||||||
|
identifier: credentials.identifier,
|
||||||
|
password: credentials.password,
|
||||||
|
cursor: state.bluesky_cursor,
|
||||||
|
});`,
|
||||||
|
newSnippet: ` const result = await fetchBlueskyNotifications({
|
||||||
|
identifier: credentials.identifier,
|
||||||
|
password: credentials.password,
|
||||||
|
// cursor intentionally omitted — listNotifications cursor paginates
|
||||||
|
// BACKWARDS (older pages), not forwards. Passing it drifts polling
|
||||||
|
// into the past until no notifications are returned. Rely on
|
||||||
|
// platform_id deduplication (upsertConversationItem) instead.
|
||||||
|
});`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "conversations-bluesky-cursor-fix-save",
|
||||||
|
candidates: schedulerCandidates,
|
||||||
|
marker: "// bluesky_cursor cleared",
|
||||||
|
oldSnippet: ` // Update cursor and status
|
||||||
|
const updateFields = {
|
||||||
|
bluesky_last_poll: new Date().toISOString(),
|
||||||
|
bluesky_last_error: null,
|
||||||
|
};
|
||||||
|
if (result.cursor) {
|
||||||
|
updateFields.bluesky_cursor = result.cursor;
|
||||||
|
}`,
|
||||||
|
newSnippet: ` // Update status — bluesky_cursor cleared (see fetch comment above)
|
||||||
|
const updateFields = {
|
||||||
|
bluesky_last_poll: new Date().toISOString(),
|
||||||
|
bluesky_last_error: null,
|
||||||
|
bluesky_cursor: null, // bluesky_cursor cleared — not used for incremental polling
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async function exists(filePath) {
|
||||||
|
try {
|
||||||
|
await access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedFiles = new Set();
|
||||||
|
const patchedFiles = new Set();
|
||||||
|
|
||||||
|
for (const spec of patchSpecs) {
|
||||||
|
let foundAnyTarget = false;
|
||||||
|
|
||||||
|
for (const filePath of spec.candidates) {
|
||||||
|
if (!(await exists(filePath))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundAnyTarget = true;
|
||||||
|
checkedFiles.add(filePath);
|
||||||
|
|
||||||
|
const source = await readFile(filePath, "utf8");
|
||||||
|
|
||||||
|
if (spec.marker && source.includes(spec.marker)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.includes(spec.oldSnippet)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = source.replace(spec.oldSnippet, spec.newSnippet);
|
||||||
|
|
||||||
|
if (updated === source) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(filePath, updated, "utf8");
|
||||||
|
patchedFiles.add(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundAnyTarget) {
|
||||||
|
console.log(`[postinstall] ${spec.name}: no target files found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkedFiles.size === 0) {
|
||||||
|
console.log("[postinstall] No conversations bluesky cursor-fix files found");
|
||||||
|
} else if (patchedFiles.size === 0) {
|
||||||
|
console.log("[postinstall] conversations bluesky cursor-fix patches already applied");
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`[postinstall] Patched conversations bluesky cursor-fix in ${patchedFiles.size}/${checkedFiles.size} file(s)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user