123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- const pull = require('../server/node_modules/pull-stream');
- const { config } = require('../server/SSB_server.js');
- const { getConfig } = require('../configs/config-manager.js');
- const logLimit = getConfig().ssbLogStream?.limit || 1000;
- module.exports = ({ cooler }) => {
- let ssb;
- const openSsb = async () => {
- if (!ssb) ssb = await cooler.open();
- return ssb;
- };
- const hasBlob = async (ssbClient, url) =>
- new Promise(resolve => ssbClient.blobs.has(url, (err, has) => resolve(!err && has)));
- return {
- async listBlockchain(filter = 'all') {
- const ssbClient = await openSsb();
- const results = await new Promise((resolve, reject) =>
- pull(
- ssbClient.createLogStream({ reverse: true, limit: logLimit }),
- pull.collect((err, msgs) => err ? reject(err) : resolve(msgs))
- )
- );
- const tombstoned = new Set();
- const idToBlock = new Map();
- const referencedAsReplaces = new Set();
- for (const msg of results) {
- const k = msg.key;
- const c = msg.value?.content;
- const author = msg.value?.author;
- if (!c?.type) continue;
- if (c.type === 'tombstone' && c.target) {
- tombstoned.add(c.target);
- idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c });
- continue;
- }
- if (c.replaces) referencedAsReplaces.add(c.replaces);
- idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c });
- }
- const tipBlocks = [];
- for (const [id, block] of idToBlock.entries()) {
- if (!referencedAsReplaces.has(id) && block.content.replaces) tipBlocks.push(block);
- }
- for (const [id, block] of idToBlock.entries()) {
- if (!block.content.replaces && !referencedAsReplaces.has(id)) tipBlocks.push(block);
- }
- const groups = {};
- for (const block of tipBlocks) {
- const ancestor = block.content.replaces || block.id;
- if (!groups[ancestor]) groups[ancestor] = [];
- groups[ancestor].push(block);
- }
- const liveTipIds = new Set();
- for (const groupBlocks of Object.values(groups)) {
- let best = groupBlocks[0];
- for (const block of groupBlocks) {
- if (block.type === 'market') {
- const isClosedSold = s => s === 'SOLD' || s === 'CLOSED';
- if (isClosedSold(block.content.status) && !isClosedSold(best.content.status)) {
- best = block;
- } else if ((block.content.status === best.content.status) && block.ts > best.ts) {
- best = block;
- }
- } else if (block.type === 'job' || block.type === 'forum') {
- if (block.ts > best.ts) best = block;
- } else {
- if (block.ts > best.ts) best = block;
- }
- }
- liveTipIds.add(best.id);
- }
- const blockData = Array.from(idToBlock.values()).map(block => {
- const c = block.content;
- const rootDeleted = c?.type === 'forum' && c.root && tombstoned.has(c.root);
- return {
- ...block,
- isTombstoned: tombstoned.has(block.id),
- isReplaced: c.replaces
- ? (!liveTipIds.has(block.id) || tombstoned.has(block.id))
- : referencedAsReplaces.has(block.id) || tombstoned.has(block.id) || rootDeleted
- };
- });
- let filtered = blockData;
- if (filter === 'RECENT') {
- const now = Date.now();
- filtered = blockData.filter(b => b && now - b.ts <= 24 * 60 * 60 * 1000);
- }
- if (filter === 'MINE') {
- filtered = blockData.filter(b => b && b.author === config.keys.id);
- }
- return filtered.filter(Boolean);
- },
- async getBlockById(id) {
- const ssbClient = await openSsb();
- const results = await new Promise((resolve, reject) =>
- pull(
- ssbClient.createLogStream({ reverse: true, limit: logLimit }),
- pull.collect((err, msgs) => err ? reject(err) : resolve(msgs))
- )
- );
- const tombstoned = new Set();
- const idToBlock = new Map();
- const referencedAsReplaces = new Set();
- for (const msg of results) {
- const k = msg.key;
- const c = msg.value?.content;
- const author = msg.value?.author;
- if (!c?.type) continue;
- if (c.type === 'tombstone' && c.target) {
- tombstoned.add(c.target);
- idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c });
- continue;
- }
- if (c.replaces) referencedAsReplaces.add(c.replaces);
- idToBlock.set(k, { id: k, author, ts: msg.value.timestamp, type: c.type, content: c });
- }
- const tipBlocks = [];
- for (const [bid, block] of idToBlock.entries()) {
- if (!referencedAsReplaces.has(bid) && block.content.replaces) tipBlocks.push(block);
- }
- for (const [bid, block] of idToBlock.entries()) {
- if (!block.content.replaces && !referencedAsReplaces.has(bid)) tipBlocks.push(block);
- }
- const groups = {};
- for (const block of tipBlocks) {
- const ancestor = block.content.replaces || block.id;
- if (!groups[ancestor]) groups[ancestor] = [];
- groups[ancestor].push(block);
- }
- const liveTipIds = new Set();
- for (const groupBlocks of Object.values(groups)) {
- let best = groupBlocks[0];
- for (const block of groupBlocks) {
- if (block.type === 'market') {
- const isClosedSold = s => s === 'SOLD' || s === 'CLOSED';
- if (isClosedSold(block.content.status) && !isClosedSold(best.content.status)) {
- best = block;
- } else if ((block.content.status === best.content.status) && block.ts > best.ts) {
- best = block;
- }
- } else if (block.type === 'job' || block.type === 'forum') {
- if (block.ts > best.ts) best = block;
- } else {
- if (block.ts > best.ts) best = block;
- }
- }
- liveTipIds.add(best.id);
- }
- const block = idToBlock.get(id);
- if (!block) return null;
- if (block.type === 'document') {
- const valid = await hasBlob(ssbClient, block.content.url);
- if (!valid) return null;
- }
- const c = block.content;
- const rootDeleted = c?.type === 'forum' && c.root && tombstoned.has(c.root);
- const isTombstoned = tombstoned.has(block.id);
- const isReplaced = c.replaces
- ? (!liveTipIds.has(block.id) || tombstoned.has(block.id))
- : referencedAsReplaces.has(block.id) || tombstoned.has(block.id) || rootDeleted;
- return {
- ...block,
- isTombstoned,
- isReplaced
- };
- }
- };
- };
|