import Router from "@koa/router" ; import coBody from "co-body" ; import createDebug from "debug" ; import createError from "http-errors" ; import { EventEmitter } from "events" ; import { AS } from "../util/consts" ; import { JsonLdService } from "../shared/jsonld" ; import { Pg , tryInsertInboxObject } from "../util/model" ; import { SigningService } from "../shared/signing" ; import { extractText } from "../util/html" ; import { originOf } from "../util/misc" ; import { Actor , Object , getActivity , getActor , getObject , getTag , } from "../util/rdfModel" ; export interface ObjectExt extends Object { actor : Actor ; contentText : string ; mentions : Set < string > ; } export interface InboxCtrl extends EventEmitter { on ( event , listener ) : this ; } const debug = createDebug ( "chess" ) ; export default async ( { jsonld , pg , router , signing , } ) : Promise < InboxCtrl > => { const inbox = new EventEmitter ( ) ; router . post ( "/inbox" , async ( ctx ) => { const { raw , parsed } = await coBody . json ( ctx . req , { returnRawBody : true } ) ; if ( ! parsed || typeof parsed . id !== "string" ) { throw createError ( 400 , "Invalid request body" ) ; } const origin = originOf ( parsed . id ) ; if ( ! origin ) { throw createError ( 400 , "Invalid activity ID, not a URL" ) ; } const store = jsonld . createStore ( ) ; try { await store . load ( parsed ) ; } catch ( err ) { throw createError ( 400 , `Activity document could not be loaded: ${ err . message }` ) ; } const activity = getActivity ( store , parsed . id ) ; if ( ! activity . type || ! activity . actor ) { throw createError ( 400 , "Incomplete activity object" ) ; } const publicKey = await signing . verify ( ctx , raw , store ) ; if ( publicKey . owner !== activity . actor ) { throw createError ( 400 , "Signature does not match actor" ) ; } if ( originOf ( activity . actor ) !== origin ) { throw createError ( 400 , "Activity and actor origins don't match" ) ; } try { await store . load ( activity . actor ) ; } catch ( err ) { throw createError ( 400 , `Actor document could not be loaded: ${ err . message }` ) ; } const actor = getActor ( store , activity . actor ) ; const now = new Date ( ) ; const { rowCount } = await tryInsertInboxObject ( pg , activity . id , now ) ; if ( rowCount === 0 ) { debug ( `Ignoring duplicate activity: ${ activity . id }` ) ; ctx . status = 202 ; ctx . body = null ; return ; } if ( activity . type === AS ( "Create" ) ) { if ( typeof parsed . object !== "object" ) { throw createError ( 400 , "Invalid object in 'Create' activity" ) ; } const object = getObject ( store , activity . object ! ) ; if ( object . type === AS ( "Note" ) ) { if ( object . attributedTo !== activity . actor ) { throw createError ( 400 , "Activity creates note not attributed to the actor" ) ; } const objectExt = { ... object , actor : actor , contentText : extractText ( object . content || "" ) , mentions : new Set ( ) , } ; for ( const tagId of object . tags ) { const tag = getTag ( store , tagId ) ; if ( tag . type === AS ( "Mention" ) ) { objectExt . mentions . add ( tag . href ) ; } } debug ( `<< ${ objectExt . actor . id } - ${ objectExt . contentText }` ) ; inbox . emit ( "noteCreated" , objectExt ) ; } } ctx . status = 202 ; ctx . body = null ; } ) ; return inbox ; } ;