@atproto/ozone
Version:
Backend service for moderating the Bluesky network.
171 lines • 6.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerificationListener = void 0;
const api_1 = require("@atproto/api");
const background_1 = require("../background");
const service_1 = require("../jetstream/service");
const logger_1 = require("../logger");
const service_2 = require("../verification/service");
class VerificationListener {
constructor(db, jetstreamUrl, verifierIssuersToIndex) {
Object.defineProperty(this, "db", {
enumerable: true,
configurable: true,
writable: true,
value: db
});
Object.defineProperty(this, "jetstreamUrl", {
enumerable: true,
configurable: true,
writable: true,
value: jetstreamUrl
});
Object.defineProperty(this, "verifierIssuersToIndex", {
enumerable: true,
configurable: true,
writable: true,
value: verifierIssuersToIndex
});
Object.defineProperty(this, "destroyed", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "cursor", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "jetstream", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "collection", {
enumerable: true,
configurable: true,
writable: true,
value: 'app.bsky.graph.verification'
});
Object.defineProperty(this, "backgroundQueue", {
enumerable: true,
configurable: true,
writable: true,
value: new background_1.BackgroundQueue(this.db, { concurrency: 1 })
});
Object.defineProperty(this, "verificationService", {
enumerable: true,
configurable: true,
writable: true,
value: service_2.VerificationService.creator()(this.db)
});
}
// When the queue has capacity, this method returns true which means we can continue to handle events
// otherwise, it will close jetstream connection and wait for all previously queued events to be processed first
// and then start jetstream listener again before returning false. At that point, the previous listeners should
// have updates the cursor in db to the last processed event and the new listener will start from that cursor
async ensureCoolDown() {
const { waitingCount, runningCount } = this.backgroundQueue.getStats();
if (waitingCount > 50 || runningCount > 50) {
logger_1.verificationLogger.warn(`Background queue is full, pausing listener`);
this.jetstream?.close();
await this.backgroundQueue.processAll();
await this.start();
return false;
}
return true;
}
handleNewVerification(issuer, uri, cid, record, cursor) {
this.backgroundQueue.add(async () => {
try {
const { subject, handle, displayName, createdAt } = record;
await this.verificationService.create([
{ uri, cid, issuer, subject, handle, displayName, createdAt },
]);
await this.updateCursor(cursor);
}
catch (err) {
logger_1.verificationLogger.error(err, 'Error handling verification create event');
}
});
}
handleDeletedVerification(uri, cursor) {
this.backgroundQueue.add(async () => {
try {
await this.verificationService.markRevoked({
uris: [uri],
});
await this.updateCursor(cursor);
}
catch (err) {
logger_1.verificationLogger.error(err, 'Error handling verification delete event');
}
});
}
async getCursor() {
await this.verificationService.createFirehoseCursor();
const cursor = await this.verificationService.getFirehoseCursor();
if (cursor) {
this.cursor = cursor;
}
return this.cursor;
}
async updateCursor(cursor) {
// Assuming cursors are always incremental, if we have processed an event with higher value cursor, let's not update to a lower value
if (this.cursor && this.cursor >= cursor) {
return;
}
// This will only update if the cursor is higher than the current one in db
const updatedCursor = await this.verificationService.updateFirehoseCursor(cursor);
if (updatedCursor) {
this.cursor = updatedCursor;
}
}
async start() {
await this.getCursor();
this.jetstream = new service_1.Jetstream({
endpoint: this.jetstreamUrl,
cursor: this.cursor || undefined,
wantedCollections: [this.collection],
wantedDids: this.verifierIssuersToIndex?.length
? this.verifierIssuersToIndex
: undefined,
});
await this.jetstream.start({
onCreate: {
[this.collection]: async (e) => {
const recordValidity = api_1.lexicons.validate(this.collection, e.commit.record);
if (!recordValidity.success) {
logger_1.verificationLogger.error(recordValidity.error, 'Invalid verification record in the firehose');
return;
}
const hasCapacity = await this.ensureCoolDown();
if (hasCapacity) {
const issuer = e.did;
const { record, rkey, collection, cid } = e.commit;
const uri = `at://${issuer}/${collection}/${rkey}`;
this.handleNewVerification(issuer, uri, cid, record, e.time_us);
}
},
},
onDelete: {
[this.collection]: async (e) => {
const hasCapacity = await this.ensureCoolDown();
if (hasCapacity) {
this.handleDeletedVerification(`at://${e.did}/${e.commit.collection}/${e.commit.rkey}`, e.time_us);
}
},
},
});
}
stop() {
this.jetstream?.close();
this.backgroundQueue.destroy();
this.destroyed = true;
}
}
exports.VerificationListener = VerificationListener;
//# sourceMappingURL=verification-listener.js.map