const pull = require('../server/node_modules/pull-stream'); module.exports = ({ cooler }) => { let ssb; const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; }; const getPixelByCoordinate = async (coordinateKey) => { const ssbClient = await openSsb(); const messages = await new Promise((res, rej) => { pull( ssbClient.createLogStream(), pull.collect((err, msgs) => err ? rej(err) : res(msgs)) ); }); const tombstoned = new Set( messages .filter(m => m.value?.content?.type === 'tombstone' && m.value?.content?.target) .map(m => m.value.content.target) ); const replaces = new Map(); const byId = new Map(); for (const m of messages) { const c = m.value?.content; const k = m.key; if (!c || c.type !== 'pixelia' || c.coordinateKey !== coordinateKey) continue; if (tombstoned.has(k)) continue; if (c.replaces) replaces.set(c.replaces, k); byId.set(k, m); } for (const r of replaces.keys()) { byId.delete(r); } return [...byId.values()][0] || null; }; const paintPixel = async (x, y, color) => { if (x < 1 || x > 50 || y < 1 || y > 200) { throw new Error('Coordinates out of bounds. Please use x (1-50) and y (1-200)'); } const ssbClient = await openSsb(); const userId = ssbClient.id; const coordinateKey = `${x}:${y}`; const existingPixel = await getPixelByCoordinate(coordinateKey); if (existingPixel) { const tombstone = { type: 'tombstone', target: existingPixel.key, deletedAt: new Date().toISOString() }; await new Promise((resolve, reject) => ssbClient.publish(tombstone, err => err ? reject(err) : resolve()) ); } const contributors = existingPixel?.value?.content?.contributors_inhabitants || []; const contributors_inhabitants = contributors.includes(userId) ? contributors : [...contributors, userId]; const content = { type: 'pixelia', x: x - 1, y: y - 1, color, author: userId, contributors_inhabitants, timestamp: Date.now(), coordinateKey, replaces: existingPixel?.key || null }; await new Promise((resolve, reject) => { ssbClient.publish(content, (err) => err ? reject(err) : resolve()); }); }; const listPixels = async () => { const ssbClient = await openSsb(); const messages = await new Promise((res, rej) => { pull( ssbClient.createLogStream(), pull.collect((err, msgs) => err ? rej(err) : res(msgs)) ); }); const tombstoned = new Set(); const replaces = new Map(); const byKey = new Map(); for (const m of messages) { const c = m.value?.content; const k = m.key; if (!c) continue; if (c.type === 'tombstone' && c.target) { tombstoned.add(c.target); continue; } if (c.type === 'pixelia') { if (tombstoned.has(k)) continue; if (c.replaces) replaces.set(c.replaces, k); byKey.set(k, m); } } for (const replaced of replaces.keys()) { byKey.delete(replaced); } return Array.from(byKey.values()).map(m => ({ x: m.value.content.x + 1, y: m.value.content.y + 1, color: m.value.content.color, author: m.value.content.author, contributors_inhabitants: m.value.content.contributors_inhabitants || [], timestamp: m.value.timestamp })); }; return { paintPixel, listPixels }; };