coldsky
Version:
Library and the app for BlueSky
125 lines (102 loc) • 3.36 kB
JavaScript
// @ts-check
import { firehose } from './firehose';
import { breakFeedURIPostOnly, shortenDID } from './shorten';
/**
* @typedef {{
* [shortDID: string]: number | undefined, error?: undefined, errorCount?: undefined, waitUntil?: undefined } | {
* error: Error,
* errorCount: number,
* waitUntil: number }} FirehoseShortDIDs
*/
/**
* @param {(shortDID: string) => number} [filterShortDIDs]
* @returns {AsyncGenerator<FirehoseShortDIDs>}
*/
export async function* firehoseShortDIDs(filterShortDIDs) {
/** @type {FirehoseShortDIDs} */
let shortDIDs = {};
let addedAny = false;
let lastHealth = Date.now();
let errorCount = 0;
while (true) {
try {
for await (const block of firehose()) {
lastHealth = Date.now();
if (!block?.length) continue;
for (const entry of block) {
if (!entry.messages?.length) continue;
for (const msg of entry.messages) {
collectShortDIDs(msg);
}
}
if (addedAny) {
/** @type {(typeof shortDIDs) & { error?: undefined, errorCount?: number, waitUntil?: undefined }} */
const report = shortDIDs;
shortDIDs = {};
addedAny = false;
yield report;
}
}
} catch (error) {
errorCount++;
const now = Date.now();
let waitFor = Math.min(
30000,
Math.max(300, (now - lastHealth) / 3)
) * (0.7 + Math.random() * 0.6);
console.error('firehose error ' + errorCount + ', retry in ' + waitFor + 'ms ', error);
yield { error: /** @type {Error} */(error), errorCount, waitUntil: now + waitFor };
return new Promise(resolve => setTimeout(resolve, waitFor));
}
}
/**
* @param {import('./firehose').FirehoseRecord} msg
*/
function collectShortDIDs(msg) {
addShortDID(msg.repo);
switch (msg.$type) {
case 'app.bsky.feed.like':
addShortDID(breakFeedURIPostOnly(msg.subject?.uri)?.shortDID);
return;
case 'app.bsky.feed.post':
addShortDID(breakFeedURIPostOnly(msg.reply?.parent?.uri)?.shortDID);
addShortDID(breakFeedURIPostOnly(msg.reply?.root?.uri)?.shortDID);
if (msg.embed?.$type === 'app.bsky.embed.record')
addShortDID(breakFeedURIPostOnly(/** @type {*} */(msg.embed?.record)?.uri)?.shortDID);
return;
case 'app.bsky.feed.repost':
addShortDID(breakFeedURIPostOnly(msg.subject?.uri)?.shortDID);
return;
case 'app.bsky.feed.threadgate':
// TODO extend
return;
case 'app.bsky.graph.follow':
addShortDID(msg.subject);
return;
case 'app.bsky.graph.block':
addShortDID(msg.subject);
return;
case 'app.bsky.graph.list':
// TODO extend
return;
case 'app.bsky.graph.listitem':
addShortDID(msg.subject);
return;
case 'app.bsky.actor.profile':
return;
}
}
/**
* @param {string | undefined} did
* @param {number} [ratio]
*/
function addShortDID(did, ratio) {
if (!did) return;
const shortDID = shortenDID(did);
let increment =
(typeof filterShortDIDs === 'function' ? filterShortDIDs(did) : 1) * (ratio || 1);
if (!increment) return;
shortDIDs[shortDID] = (shortDIDs[shortDID] || 0) + increment;
addedAny = true;
}
}