ferment
Version:
Peer-to-peer audio publishing and streaming application. Like SoundCloud but decentralized. A mashup of ssb, webtorrent and electron.
243 lines (208 loc) • 5.82 kB
JavaScript
var MutantMap = require('@mmckegg/mutant/map')
var electron = require('electron')
var Profiles = require('./profiles')
var schemas = require('ssb-msg-schemas')
var Proxy = require('@mmckegg/mutant/proxy')
var concat = require('@mmckegg/mutant/concat')
var computed = require('@mmckegg/mutant/computed')
var mlib = require('ssb-msgs')
var onceTrue = require('../lib/once-true')
var ObservLocal = require('../lib/obs-local')
var callbacks = {}
electron.ipcRenderer.on('response', (ev, id, ...args) => {
var cb = callbacks[id]
if (cb) {
delete callbacks[id]
cb(...args)
}
})
module.exports = function (ssbClient, config) {
var windowId = Date.now()
var seq = 0
var profiles = null
var profilesLoaded = Proxy()
var scope = (config.friends || {}).scope
var localPeerIds = ObservLocal(ssbClient, config)
return {
id: ssbClient.id,
getDiscoveryFeed (cb) {
checkProfilesLoaded()
return lookupItems(sortedPostIds(profiles.pubFriendPostIds, {maxSameProfile: 2}))
},
getFollowingFeed (cb) {
checkProfilesLoaded()
var profile = profiles.get(ssbClient.id)
var postIds = computed([profile.following, profiles.lookup], (following, profiles) => {
var result = []
following.forEach((id) => {
var otherProfile = profiles[id]
if (otherProfile) {
otherProfile.posts.forEach(x => result.push(x))
}
})
return result
}, { nextTick: true })
return lookupItems(sortedPostIds(postIds))
},
getProfileFeed (id, cb) {
checkProfilesLoaded()
return lookupItems(reverse(profiles.get(id).posts))
},
setOwnDisplayName (name, cb) {
publish({
type: 'about',
about: ssbClient.id,
name: name
}, cb)
},
getLikedFeedFor (id) {
checkProfilesLoaded()
var likes = profiles.get(id).likes
return lookupItems(likes)
},
profilesLoaded,
localPeerIds,
getProfile (id) {
checkProfilesLoaded()
return profiles.get(id)
},
rankProfileIds (ids, max) {
checkProfilesLoaded()
return profiles.rankProfileIds(ids, max)
},
getOwnProfile () {
checkProfilesLoaded()
return profiles.get(ssbClient.id)
},
getSuggestedProfiles (max) {
checkProfilesLoaded()
return profiles.getSuggested(max)
},
getLocalProfiles (max) {
checkProfilesLoaded()
return MutantMap(localPeerIds, profiles.get)
},
publish,
follow (id, cb) {
checkProfilesLoaded(() => {
var profile = profiles.get(id)
var msg = schemas.follow(id)
msg.scope = scope
if (profile.isPub()) {
msg.pub = true
}
publish(msg, cb)
})
},
unfollow (id, cb) {
publish(schemas.unfollow(id), cb)
},
like (id, cb) {
var likeLink = mlib.link(id)
likeLink.value = true
publish({
type: 'ferment/like',
like: likeLink
}, cb)
ssbClient.gossip.peers(function (err, data) {
console.log(err, data)
})
},
unlike (id, cb) {
var unlikeLink = mlib.link(id)
unlikeLink.value = false
publish({
type: 'ferment/like',
like: unlikeLink
}, cb)
},
repost (id, cb) {
var repostLink = mlib.link(id)
repostLink.value = true
publish({
type: 'ferment/repost',
repost: repostLink
}, cb)
},
unrepost (id, cb) {
var unrepostLink = mlib.link(id)
unrepostLink.value = false
publish({
type: 'ferment/repost',
repost: unrepostLink
}, cb)
},
getPost (id) {
checkProfilesLoaded()
return profiles.getPost(id)
},
addBlob (dataOrPath, cb) {
var id = `${windowId}-${seq++}`
callbacks[id] = cb
electron.ipcRenderer.send('add-blob', id, dataOrPath)
},
getBlobUrl (id) {
var prefix = config.blobsPrefix != null ? config.blobsPrefix : `http://localhost:${config.blobsPort}`
var link = mlib.link(id, 'blob')
if (link) {
return `${prefix}/${encodeURIComponent(link.link)}`
} else if (typeof id === 'string' && id.startsWith('blobstore:')) {
// legacy ferment artwork blobs
return `${prefix}/${encodeURIComponent(id.slice(10))}`
}
}
}
// scoped
function sortedPostIds (ids, opts) {
return computed([ids], function (ids) {
var sorted = ids.map(id => profiles.getPost(id)).sort((a, b) => b.timestamp() - a.timestamp())
var sameCount = 0
var lastAuthorId = null
var filtered = opts && opts.maxSameProfile ? sorted.filter((post) => {
if (post.author.id === lastAuthorId) {
sameCount += 1
} else {
sameCount = 0
lastAuthorId = post.author.id
}
if (sameCount < opts.maxSameProfile) {
return true
}
}) : sorted
return filtered.map(x => x.id)
}, { nextTick: true })
}
function reverse (ids) {
return computed([ids], function (ids) {
var result = []
ids.forEach((id, i) => {
result[ids.length - 1 - i] = id
})
return result
})
}
function checkProfilesLoaded (cb) {
if (!profiles) {
profiles = Profiles(ssbClient, config)
profilesLoaded.set(profiles.sync)
if (cb) {
onceTrue(profiles.sync, cb)
}
} else if (cb) {
cb()
}
}
function publish (message, cb) {
ssbClient.publish(message, function (err, msg) {
if (!cb && err) throw err
cb && cb(err, msg)
})
}
function lookupItems (ids) {
var result = MutantMap(ids, (id) => {
return profiles.postLookup.get(id)
})
result.sync = profiles.sync
return result
}
}