@atproto/sync
Version:
atproto sync library
331 lines • 11.6 kB
JavaScript
"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