Ocelot-Social/src/activitypub/ActivityPub.js

159 lines
5.2 KiB
JavaScript

import {
sendAcceptActivity,
sendRejectActivity,
extractNameFromId,
extractDomainFromUrl,
signAndSend
} from './utils'
import request from 'request'
import as from 'activitystrea.ms'
import NitroDatasource from './NitroDatasource'
import router from './routes'
import dotenv from 'dotenv'
import { resolve } from 'path'
const debug = require('debug')('ea')
let activityPub = null
export { activityPub }
export default class ActivityPub {
constructor (domain, port) {
if (domain === 'localhost') { this.domain = `${domain}:${port}` } else { this.domain = domain }
this.port = port
this.dataSource = new NitroDatasource(this.domain)
}
static init (server) {
if (!activityPub) {
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
// const app = express()
activityPub = new ActivityPub(process.env.ACTIVITYPUB_DOMAIN || 'localhost', process.env.ACTIVITYPUB_PORT || 4100)
server.express.set('ap', activityPub)
server.express.use(router)
debug('ActivityPub service added to graphql endpoint')
} else {
debug('ActivityPub service already added to graphql endpoint')
}
}
getFollowersCollection (actorId) {
return this.dataSource.getFollowersCollection(actorId)
}
getFollowersCollectionPage (actorId) {
return this.dataSource.getFollowersCollectionPage(actorId)
}
getFollowingCollection (actorId) {
return this.dataSource.getFollowingCollection(actorId)
}
getFollowingCollectionPage (actorId) {
return this.dataSource.getFollowingCollectionPage(actorId)
}
getOutboxCollection (actorId) {
return this.dataSource.getOutboxCollection(actorId)
}
getOutboxCollectionPage (actorId) {
return this.dataSource.getOutboxCollectionPage(actorId)
}
handleFollowActivity (activity) {
debug(`inside FOLLOW ${activity.actor}`)
let toActorName = extractNameFromId(activity.object)
let fromDomain = extractDomainFromUrl(activity.actor)
const dataSource = this.dataSource
return new Promise((resolve, reject) => {
request({
url: activity.actor,
headers: {
'Accept': 'application/activity+json'
}
}, async (err, response, toActorObject) => {
if (err) return reject(err)
debug(`name = ${toActorName}@${this.domain}`)
// save shared inbox
await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox)
let followersCollectionPage = await this.dataSource.getFollowersCollectionPage(activity.object)
const followActivity = as.follow()
.id(activity.id)
.actor(activity.actor)
.object(activity.object)
// add follower if not already in collection
if (followersCollectionPage.orderedItems.includes(activity.actor)) {
debug('follower already in collection!')
debug(`inbox = ${toActorObject.inbox}`)
resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
} else {
followersCollectionPage.orderedItems.push(activity.actor)
}
debug(`toActorObject = ${toActorObject}`)
toActorObject = typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject
debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`)
debug(`inbox = ${toActorObject.inbox}`)
debug(`outbox = ${toActorObject.outbox}`)
debug(`followers = ${toActorObject.followers}`)
debug(`following = ${toActorObject.following}`)
// TODO save after accept activity for the corresponding follow is received
try {
await dataSource.saveFollowersCollectionPage(followersCollectionPage)
debug('follow activity saved')
resolve(sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
} catch (e) {
debug('followers update error!', e)
resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
}
})
})
}
handleUndoActivity (activity) {
debug('inside UNDO')
switch (activity.object.type) {
case 'Follow':
const followActivity = activity.object
return this.dataSource.undoFollowActivity(followActivity.actor, followActivity.object)
default:
}
}
handleCreateActivity (activity) {
debug('inside create')
switch (activity.object.type) {
case 'Article':
case 'Note':
const articleObject = activity.object
if (articleObject.inReplyTo) {
return this.dataSource.createComment(activity)
} else {
return this.dataSource.createPost(activity)
}
default:
}
}
handleDeleteActivity (activity) {
debug('inside delete')
}
async sendActivity (activity) {
if (Array.isArray(activity.to) && activity.to.includes('https://www.w3.org/ns/activitystreams#Public')) {
delete activity.send
const fromName = extractNameFromId(activity.actor)
const sharedInboxEndpoints = await this.dataSource.getSharedInboxEndpoints()
await Promise.all(
sharedInboxEndpoints.map((el) => {
return signAndSend(activity, fromName, new URL(el).host, el)
})
)
}
}
}