UNPKG

@atproto/sync

Version:
331 lines 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FirehoseHandlerError = exports.FirehoseSubscriptionError = exports.FirehoseParseError = exports.FirehoseValidationError = exports.parseAccount = exports.parseIdentity = exports.parseSync = exports.parseCommitUnauthenticated = exports.parseCommitAuthenticated = exports.Firehose = void 0; const common_1 = require("@atproto/common"); const identity_1 = require("@atproto/identity"); const repo_1 = require("@atproto/repo"); const syntax_1 = require("@atproto/syntax"); const xrpc_server_1 = require("@atproto/xrpc-server"); const util_1 = require("../util"); const lexicons_1 = require("./lexicons"); class Firehose { constructor(opts) { Object.defineProperty(this, "opts", { enumerable: true, configurable: true, writable: true, value: opts }); Object.defineProperty(this, "sub", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "abortController", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "destoryDefer", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.destoryDefer = (0, common_1.createDeferrable)(); this.abortController = new AbortController(); if (this.opts.getCursor && this.opts.runner) { throw new Error('Must set only `getCursor` or `runner`'); } this.sub = new xrpc_server_1.Subscription({ ...opts, service: opts.service ?? 'wss://bsky.network', method: 'com.atproto.sync.subscribeRepos', signal: this.abortController.signal, getParams: async () => { const getCursorFn = () => this.opts.runner?.getCursor() ?? this.opts.getCursor; if (!getCursorFn) { return undefined; } const cursor = await getCursorFn(); return { cursor }; }, validate: (value) => { try { return (0, lexicons_1.isValidRepoEvent)(value); } catch (err) { this.opts.onError(new FirehoseValidationError(err, value)); } }, }); } async start() { try { for await (const evt of this.sub) { if (this.opts.runner) { const parsed = (0, util_1.didAndSeqForEvt)(evt); if (!parsed) { continue; } this.opts.runner.trackEvent(parsed.did, parsed.seq, async () => { const parsed = await this.parseEvt(evt); for (const write of parsed) { try { await this.opts.handleEvent(write); } catch (err) { this.opts.onError(new FirehoseHandlerError(err, write)); } } }); } else { await this.processEvt(evt); } } } catch (err) { if (err && err['name'] === 'AbortError') { this.destoryDefer.resolve(); return; } this.opts.onError(new FirehoseSubscriptionError(err)); await (0, common_1.wait)(this.opts.subscriptionReconnectDelay ?? 3000); return this.start(); } } async parseEvt(evt) { try { if ((0, lexicons_1.isCommit)(evt) && !this.opts.excludeCommit) { return this.opts.unauthenticatedCommits ? await (0, exports.parseCommitUnauthenticated)(evt, this.opts.filterCollections) : await (0, exports.parseCommitAuthenticated)(this.opts.idResolver, evt, this.opts.filterCollections); } else if ((0, lexicons_1.isAccount)(evt) && !this.opts.excludeAccount) { const parsed = (0, exports.parseAccount)(evt); return parsed ? [parsed] : []; } else if ((0, lexicons_1.isIdentity)(evt) && !this.opts.excludeIdentity) { const parsed = await (0, exports.parseIdentity)(this.opts.idResolver, evt, this.opts.unauthenticatedHandles); return parsed ? [parsed] : []; } else if ((0, lexicons_1.isSync)(evt) && !this.opts.excludeSync) { const parsed = await (0, exports.parseSync)(evt); return parsed ? [parsed] : []; } else { return []; } } catch (err) { this.opts.onError(new FirehoseParseError(err, evt)); return []; } } async processEvt(evt) { const parsed = await this.parseEvt(evt); for (const write of parsed) { try { await this.opts.handleEvent(write); } catch (err) { this.opts.onError(new FirehoseHandlerError(err, write)); } } } async destroy() { this.abortController.abort(); await this.destoryDefer.complete; } } exports.Firehose = Firehose; const parseCommitAuthenticated = async (idResolver, evt, filterCollections, forceKeyRefresh = false) => { const did = evt.repo; const ops = maybeFilterOps(evt.ops, filterCollections); if (ops.length === 0) { return []; } const claims = ops.map((op) => { const { collection, rkey } = (0, repo_1.parseDataKey)(op.path); return { collection, rkey, cid: op.action === 'delete' ? null : op.cid, }; }); const key = await idResolver.did.resolveAtprotoKey(did, forceKeyRefresh); const verifiedCids = {}; try { const results = await (0, repo_1.verifyProofs)(evt.blocks, claims, did, key); results.verified.forEach((op) => { const path = (0, repo_1.formatDataKey)(op.collection, op.rkey); verifiedCids[path] = op.cid; }); } catch (err) { if (err instanceof repo_1.RepoVerificationError && !forceKeyRefresh) { return (0, exports.parseCommitAuthenticated)(idResolver, evt, filterCollections, true); } throw err; } const verifiedOps = ops.filter((op) => { if (op.action === 'delete') { return verifiedCids[op.path] === null; } else { return op.cid !== null && op.cid.equals(verifiedCids[op.path]); } }); return formatCommitOps(evt, verifiedOps); }; exports.parseCommitAuthenticated = parseCommitAuthenticated; const parseCommitUnauthenticated = async (evt, filterCollections) => { const ops = maybeFilterOps(evt.ops, filterCollections); return formatCommitOps(evt, ops); }; exports.parseCommitUnauthenticated = parseCommitUnauthenticated; const maybeFilterOps = (ops, filterCollections) => { if (!filterCollections) return ops; return ops.filter((op) => { const { collection } = (0, repo_1.parseDataKey)(op.path); return filterCollections.includes(collection); }); }; const formatCommitOps = async (evt, ops) => { const car = await (0, repo_1.readCar)(evt.blocks); const evts = []; for (const op of ops) { const uri = syntax_1.AtUri.make(evt.repo, op.path); const meta = { seq: evt.seq, time: evt.time, commit: evt.commit, blocks: car.blocks, rev: evt.rev, uri, did: uri.host, collection: uri.collection, rkey: uri.rkey, }; if (op.action === 'create' || op.action === 'update') { if (!op.cid) continue; const recordBytes = car.blocks.get(op.cid); if (!recordBytes) continue; const record = (0, repo_1.cborToLexRecord)(recordBytes); evts.push({ ...meta, event: op.action, cid: op.cid, record, }); } if (op.action === 'delete') { evts.push({ ...meta, event: 'delete', }); } } return evts; }; const parseSync = async (evt) => { const car = await (0, repo_1.readCarWithRoot)(evt.blocks); return { event: 'sync', seq: evt.seq, time: evt.time, did: evt.did, cid: car.root, rev: evt.rev, blocks: car.blocks, }; }; exports.parseSync = parseSync; const parseIdentity = async (idResolver, evt, unauthenticated = false) => { const res = await idResolver.did.resolve(evt.did); const handle = res && !unauthenticated ? await verifyHandle(idResolver, evt.did, res) : undefined; return { event: 'identity', seq: evt.seq, time: evt.time, did: evt.did, handle, didDocument: res ?? undefined, }; }; exports.parseIdentity = parseIdentity; const verifyHandle = async (idResolver, did, didDoc) => { const { handle } = (0, identity_1.parseToAtprotoDocument)(didDoc); if (!handle) { return undefined; } const res = await idResolver.handle.resolve(handle); return res === did ? handle : undefined; }; const parseAccount = (evt) => { if (evt.status && !isValidStatus(evt.status)) return; return { event: 'account', seq: evt.seq, time: evt.time, did: evt.did, active: evt.active, status: evt.status, }; }; exports.parseAccount = parseAccount; const isValidStatus = (str) => { return ['takendown', 'suspended', 'deleted', 'deactivated'].includes(str); }; class FirehoseValidationError extends Error { constructor(err, value) { super('error in firehose event lexicon validation', { cause: err }); Object.defineProperty(this, "value", { enumerable: true, configurable: true, writable: true, value: value }); } } exports.FirehoseValidationError = FirehoseValidationError; class FirehoseParseError extends Error { constructor(err, event) { super('error in parsing and authenticating firehose event', { cause: err }); Object.defineProperty(this, "event", { enumerable: true, configurable: true, writable: true, value: event }); } } exports.FirehoseParseError = FirehoseParseError; class FirehoseSubscriptionError extends Error { constructor(err) { super('error on firehose subscription', { cause: err }); } } exports.FirehoseSubscriptionError = FirehoseSubscriptionError; class FirehoseHandlerError extends Error { constructor(err, event) { super('error in firehose event handler', { cause: err }); Object.defineProperty(this, "event", { enumerable: true, configurable: true, writable: true, value: event }); } } exports.FirehoseHandlerError = FirehoseHandlerError; //# sourceMappingURL=index.js.map