Add PostOutbox Create unit tests.

Fix normalizing the recipents between the object and the activity. Also
allow the handlers to adjust what gets placed in the outbox, since the
client-to-server API allows the server to modify the object before being
delivered.
このコミットが含まれているのは:
Cory Slep 2018-04-22 14:02:45 +02:00
コミット 2adcc56581
3個のファイルの変更484行の追加53行の削除

ファイルの表示

@ -271,11 +271,11 @@ func (f *federator) PostOutbox(c context.Context, w http.ResponseWriter, r *http
if m, err = typer.Serialize(); err != nil {
return true, err
}
if err := f.addToOutbox(c, r, m); err != nil {
deliverable := false
if err = f.getPostOutboxResolver(c, &deliverable, &m).Deserialize(m); err != nil {
return true, err
}
deliverable := false
if err = f.getPostOutboxResolver(c, &deliverable).Deserialize(m); err != nil {
if err := f.addToOutbox(c, r, m); err != nil {
return true, err
}
if f.EnableServer && deliverable {
@ -334,9 +334,9 @@ func (f *federator) addToOutbox(c context.Context, r *http.Request, m map[string
return f.App.Set(c, outbox)
}
func (f *federator) getPostOutboxResolver(c context.Context, deliverable *bool) *streams.Resolver {
func (f *federator) getPostOutboxResolver(c context.Context, deliverable *bool, toAddToOutbox *map[string]interface{}) *streams.Resolver {
return &streams.Resolver{
CreateCallback: f.handleClientCreate(c, deliverable),
CreateCallback: f.handleClientCreate(c, deliverable, toAddToOutbox),
UpdateCallback: f.handleClientUpdate(c, deliverable),
DeleteCallback: f.handleClientDelete(c, deliverable),
FollowCallback: f.handleClientFollow(c, deliverable),
@ -351,7 +351,7 @@ func (f *federator) getPostOutboxResolver(c context.Context, deliverable *bool)
}
}
func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) func(s *streams.Create) error {
func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool, toAddToOutbox *map[string]interface{}) func(s *streams.Create) error {
return func(s *streams.Create) error {
*deliverable = true
if s.LenObject() == 0 {
@ -379,12 +379,11 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f
}
var obj []vocab.ObjectType
for i := 0; i < c.ObjectLen(); i++ {
if c.IsObject(i) {
obj = append(obj, c.GetObject(i))
} else if c.IsObjectIRI(i) {
if !c.IsObject(i) {
// TODO: Fetch IRIs as well
return fmt.Errorf("unsupported: Create Activity with 'object' that is only an IRI")
}
obj = append(obj, c.GetObject(i))
}
objectAttributedToIds := make([]map[string]interface{}, len(obj))
for i := range objectAttributedToIds {
@ -419,10 +418,6 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f
}
}
}
// As such, a server SHOULD copy any recipients of the Create activity to its
// object upon initial distribution, and likewise with copying recipients from
// the object to the wrapping Create activity.
// Again, presumably if it does not already exist.
for _, attributedToMap := range objectAttributedToIds {
for k, v := range attributedToMap {
if _, ok := createActorIds[k]; !ok {
@ -436,6 +431,12 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f
}
}
}
// As such, a server SHOULD copy any recipients of the Create activity to its
// object upon initial distribution, and likewise with copying recipients from
// the object to the wrapping Create activity.
if err := f.sameRecipients(c); err != nil {
return err
}
// Create requires the client application to persist the 'object' that
// was created.
for _, o := range obj {
@ -443,6 +444,13 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f
return err
}
}
// Persist the above changes in the outbox
var err error
*toAddToOutbox = make(map[string]interface{})
*toAddToOutbox, err = c.Serialize()
if err != nil {
return err
}
return f.ClientCallbacker.Create(ctx, s)
}
}

ファイルの表示

@ -34,34 +34,36 @@ const (
)
var (
iri *url.URL
noteIRI *url.URL
noteActivityIRI *url.URL
updateActivityIRI *url.URL
testNewIRI *url.URL
sallyIRI *url.URL
sallyIRIInbox *url.URL
sallyActor *vocab.Person
sallyActorJSON []byte
samIRI *url.URL
samIRIInbox *url.URL
samIRIFollowers *url.URL
samActor *vocab.Person
samActorJSON []byte
testNote *vocab.Note
testSingleOrderedCollection *vocab.OrderedCollection
testCreateNote *vocab.Create
testUpdateNote *vocab.Update
testDeleteNote *vocab.Delete
testTombstoneNote *vocab.Tombstone
testFollow *vocab.Follow
testAcceptNote *vocab.Accept
testAcceptFollow *vocab.Accept
testRejectFollow *vocab.Reject
testAddNote *vocab.Add
testRemoveNote *vocab.Remove
testLikeNote *vocab.Like
testUndoLike *vocab.Undo
iri *url.URL
noteIRI *url.URL
noteActivityIRI *url.URL
updateActivityIRI *url.URL
testNewIRI *url.URL
sallyIRI *url.URL
sallyIRIInbox *url.URL
sallyActor *vocab.Person
sallyActorJSON []byte
samIRI *url.URL
samIRIInbox *url.URL
samIRIFollowers *url.URL
samActor *vocab.Person
samActorJSON []byte
testNote *vocab.Note
testSingleOrderedCollection *vocab.OrderedCollection
testCreateNote *vocab.Create
testUpdateNote *vocab.Update
testDeleteNote *vocab.Delete
testTombstoneNote *vocab.Tombstone
testFollow *vocab.Follow
testAcceptNote *vocab.Accept
testAcceptFollow *vocab.Accept
testRejectFollow *vocab.Reject
testAddNote *vocab.Add
testRemoveNote *vocab.Remove
testLikeNote *vocab.Like
testUndoLike *vocab.Undo
testClientExpectedNote *vocab.Note
testClientExpectedCreateNote *vocab.Create
)
func init() {
@ -207,6 +209,19 @@ func init() {
testUndoLike.AddActorObject(sallyActor)
testUndoLike.AddObject(testLikeNote)
testUndoLike.AddToObject(samActor)
testClientExpectedNote = &vocab.Note{}
testClientExpectedNote.SetId(*noteIRI)
testClientExpectedNote.AddNameString(noteName)
testClientExpectedNote.AddContentString("This is a simple note")
testClientExpectedNote.AddAttributedToObject(sallyActor)
testClientExpectedNote.AddToObject(samActor)
testClientExpectedCreateNote = &vocab.Create{}
testClientExpectedCreateNote.SetId(*testNewIRI)
testClientExpectedCreateNote.AddSummaryString("Sally created a note")
testClientExpectedCreateNote.AddActorObject(sallyActor)
testClientExpectedCreateNote.AddObject(testClientExpectedNote)
testClientExpectedCreateNote.AddToObject(samActor)
}
func Must(l *time.Location, e error) *time.Location {
@ -752,9 +767,9 @@ func TestSocialPubber_PostOutbox(t *testing.T) {
app.set = func(c context.Context, o PubObject) error {
gotSet++
if gotSet == 1 {
gotSetOutbox = o
} else if gotSet == 2 {
gotSetCreateObject = o
} else if gotSet == 2 {
gotSetOutbox = o
}
return nil
}
@ -1038,9 +1053,9 @@ func TestPubber_PostOutbox(t *testing.T) {
app.set = func(c context.Context, o PubObject) error {
gotSet++
if gotSet == 1 {
gotSetOutbox = o
} else if gotSet == 2 {
gotSetCreateObject = o
} else if gotSet == 2 {
gotSetOutbox = o
}
return nil
}
@ -2892,23 +2907,130 @@ func TestPostOutbox_RequiresTarget(t *testing.T) {
}
func TestPostOutbox_Create_CopyToAttributedTo(t *testing.T) {
// TODO: Implement
}
func TestPostOutbox_Create_CopyRecipientsToObject(t *testing.T) {
// TODO: Implement
app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t)
PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p)
resp := httptest.NewRecorder()
req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote))))
var gotCallbackObject *streams.Create
socialCb.create = func(c context.Context, s *streams.Create) error {
gotCallbackObject = s
return nil
}
handled, err := p.PostOutbox(context.Background(), resp, req)
if err != nil {
t.Fatal(err)
} else if !handled {
t.Fatalf("expected handled, got !handled")
} else if e := PubObjectEquals(gotCallbackObject.Raw(), testClientExpectedCreateNote); e != nil {
t.Fatal(e)
}
}
func TestPostOutbox_Create_SetCreatedObject(t *testing.T) {
// TODO: Implement
app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t)
PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p)
resp := httptest.NewRecorder()
req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote))))
socialCb.create = func(c context.Context, s *streams.Create) error {
return nil
}
gotSet := 0
var gotSetOutbox PubObject
var gotSetCreate PubObject
app.set = func(c context.Context, o PubObject) error {
gotSet++
if gotSet == 1 {
gotSetCreate = o
} else {
gotSetOutbox = o
}
return nil
}
handled, err := p.PostOutbox(context.Background(), resp, req)
expectedOutbox := &vocab.OrderedCollection{}
expectedOutbox.AddType("OrderedCollection")
expectedOutbox.AddOrderedItemsObject(testClientExpectedCreateNote)
if err != nil {
t.Fatal(err)
} else if !handled {
t.Fatalf("expected handled, got !handled")
} else if gotSet != 2 {
t.Fatalf("expected %d, got %d", 2, gotSet)
} else if err := PubObjectEquals(gotSetCreate, testClientExpectedNote); err != nil {
t.Fatalf("unexpected callback object: %s", err)
} else if err := PubObjectEquals(gotSetOutbox, expectedOutbox); err != nil {
t.Fatalf("unexpected callback object: %s", err)
}
}
func TestPostOutbox_Create_CallsCallback(t *testing.T) {
// TODO: Implement
app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t)
PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p)
resp := httptest.NewRecorder()
req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote))))
gotCallback := 0
var gotCallbackObject *streams.Create
socialCb.create = func(c context.Context, s *streams.Create) error {
gotCallback++
gotCallbackObject = s
return nil
}
handled, err := p.PostOutbox(context.Background(), resp, req)
if err != nil {
t.Fatal(err)
} else if !handled {
t.Fatalf("expected handled, got !handled")
} else if gotCallback != 1 {
t.Fatalf("expected %d, got %d", 1, gotCallback)
} else if err := PubObjectEquals(gotCallbackObject.Raw(), testClientExpectedCreateNote); err != nil {
t.Fatalf("unexpected callback object: %s", err)
}
}
func TestPostOutbox_Create_IsDelivered(t *testing.T) {
// TODO: Implement
app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t)
PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p)
resp := httptest.NewRecorder()
req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote))))
socialCb.create = func(c context.Context, s *streams.Create) error {
return nil
}
gotHttpDo := 0
var httpDeliveryRequest *http.Request
httpClient.do = func(req *http.Request) (*http.Response, error) {
gotHttpDo++
if gotHttpDo == 1 {
actorResp := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBuffer(samActorJSON)),
}
return actorResp, nil
} else if gotHttpDo == 2 {
actorResp := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBuffer(sallyActorJSON)),
}
return actorResp, nil
} else if gotHttpDo == 3 {
httpDeliveryRequest = req
okResp := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBuffer([]byte{})),
}
return okResp, nil
}
return nil, nil
}
handled, err := p.PostOutbox(context.Background(), resp, req)
if err != nil {
t.Fatal(err)
} else if !handled {
t.Fatalf("expected handled, got !handled")
} else if httpDeliveryRequest.Method != "POST" {
t.Fatalf("expected %s, got %s", "POST", httpDeliveryRequest.Method)
} else if s := httpDeliveryRequest.URL.String(); s != samIRIInboxString {
t.Fatalf("expected %s, got %s", samIRIInboxString, s)
}
}
func TestPostOutbox_Update_OverwriteUpdatedFields(t *testing.T) {

ファイルの表示

@ -173,6 +173,307 @@ func (f *federator) wrapInCreate(o vocab.ObjectType, actor url.URL) *vocab.Creat
return c
}
// Ensures the activity and object have the same 'to', 'bto', 'cc', 'bcc', and
// 'audience' properties. Copy the Activity's recipients to objects, and the
// objects to the activity, but does NOT copy objects' recipients to each other.
//
// If there is any disagreement between the activity and an object, we default
// to a no-op.
func (f *federator) sameRecipients(a vocab.ActivityType) error {
// First, map recipients for each object and the activity.
to := make([]map[string]interface{}, a.ObjectLen())
for i := 0; i < a.ObjectLen(); i++ {
to[i] = make(map[string]interface{})
if !a.IsObject(i) {
return fmt.Errorf("sameRecipients does not support 'to' object IRIs on Activities")
}
o := a.GetObject(i)
for j := 0; j < o.ToLen(); j++ {
if o.IsToObject(j) {
id := o.GetToObject(j).GetId()
to[i][(&id).String()] = o.GetToObject(j)
} else if o.IsToLink(j) {
id := o.GetToLink(j).GetHref()
to[i][(&id).String()] = o.GetToLink(j)
} else if o.IsToIRI(j) {
id := o.GetToIRI(j)
to[i][(&id).String()] = id
}
}
}
toActivity := make(map[string]interface{})
for i := 0; i < a.ToLen(); i++ {
if a.IsToObject(i) {
id := a.GetToObject(i).GetId()
toActivity[(&id).String()] = a.GetToObject(i)
} else if a.IsToLink(i) {
id := a.GetToLink(i).GetHref()
toActivity[(&id).String()] = a.GetToLink(i)
} else if a.IsToIRI(i) {
id := a.GetToIRI(i)
toActivity[(&id).String()] = id
}
}
bto := make([]map[string]interface{}, a.ObjectLen())
for i := 0; i < a.ObjectLen(); i++ {
bto[i] = make(map[string]interface{})
if !a.IsObject(i) {
return fmt.Errorf("sameRecipients does not support 'bto' object IRIs on Activities")
}
o := a.GetObject(i)
for j := 0; j < o.BtoLen(); j++ {
if o.IsBtoObject(j) {
id := o.GetBtoObject(j).GetId()
bto[i][(&id).String()] = o.GetBtoObject(j)
} else if o.IsBtoLink(j) {
id := o.GetBtoLink(j).GetHref()
bto[i][(&id).String()] = o.GetBtoLink(j)
} else if o.IsBtoIRI(j) {
id := o.GetBtoIRI(j)
bto[i][(&id).String()] = id
}
}
}
btoActivity := make(map[string]interface{})
for i := 0; i < a.BtoLen(); i++ {
if a.IsBtoObject(i) {
id := a.GetBtoObject(i).GetId()
btoActivity[(&id).String()] = a.GetBtoObject(i)
} else if a.IsBtoLink(i) {
id := a.GetBtoLink(i).GetHref()
btoActivity[(&id).String()] = a.GetBtoLink(i)
} else if a.IsBtoIRI(i) {
id := a.GetBtoIRI(i)
btoActivity[(&id).String()] = id
}
}
cc := make([]map[string]interface{}, a.ObjectLen())
for i := 0; i < a.ObjectLen(); i++ {
cc[i] = make(map[string]interface{})
if !a.IsObject(i) {
return fmt.Errorf("sameRecipients does not support 'cc' object IRIs on Activities")
}
o := a.GetObject(i)
for j := 0; j < o.CcLen(); j++ {
if o.IsCcObject(j) {
id := o.GetCcObject(j).GetId()
cc[i][(&id).String()] = o.GetCcObject(j)
} else if o.IsCcLink(j) {
id := o.GetCcLink(j).GetHref()
cc[i][(&id).String()] = o.GetCcLink(j)
} else if o.IsCcIRI(j) {
id := o.GetCcIRI(j)
cc[i][(&id).String()] = id
}
}
}
ccActivity := make(map[string]interface{})
for i := 0; i < a.CcLen(); i++ {
if a.IsCcObject(i) {
id := a.GetCcObject(i).GetId()
ccActivity[(&id).String()] = a.GetCcObject(i)
} else if a.IsCcLink(i) {
id := a.GetCcLink(i).GetHref()
ccActivity[(&id).String()] = a.GetCcLink(i)
} else if a.IsCcIRI(i) {
id := a.GetCcIRI(i)
ccActivity[(&id).String()] = id
}
}
bcc := make([]map[string]interface{}, a.ObjectLen())
for i := 0; i < a.ObjectLen(); i++ {
bcc[i] = make(map[string]interface{})
if !a.IsObject(i) {
return fmt.Errorf("sameRecipients does not support 'bcc' object IRIs on Activities")
}
o := a.GetObject(i)
for j := 0; j < o.BccLen(); j++ {
if o.IsBccObject(j) {
id := o.GetBccObject(j).GetId()
bcc[i][(&id).String()] = o.GetBccObject(j)
} else if o.IsBccLink(j) {
id := o.GetBccLink(j).GetHref()
bcc[i][(&id).String()] = o.GetBccLink(j)
} else if o.IsBccIRI(j) {
id := o.GetBccIRI(j)
bcc[i][(&id).String()] = id
}
}
}
bccActivity := make(map[string]interface{})
for i := 0; i < a.BccLen(); i++ {
if a.IsBccObject(i) {
id := a.GetBccObject(i).GetId()
bccActivity[(&id).String()] = a.GetBccObject(i)
} else if a.IsBccLink(i) {
id := a.GetBccLink(i).GetHref()
bccActivity[(&id).String()] = a.GetBccLink(i)
} else if a.IsBccIRI(i) {
id := a.GetBccIRI(i)
bccActivity[(&id).String()] = id
}
}
audience := make([]map[string]interface{}, a.ObjectLen())
for i := 0; i < a.ObjectLen(); i++ {
audience[i] = make(map[string]interface{})
if !a.IsObject(i) {
return fmt.Errorf("sameRecipients does not support 'audience' object IRIs on Activities")
}
o := a.GetObject(i)
for j := 0; j < o.AudienceLen(); j++ {
if o.IsAudienceObject(j) {
id := o.GetAudienceObject(j).GetId()
audience[i][(&id).String()] = o.GetAudienceObject(j)
} else if o.IsAudienceLink(j) {
id := o.GetAudienceLink(j).GetHref()
audience[i][(&id).String()] = o.GetAudienceLink(j)
} else if o.IsAudienceIRI(j) {
id := o.GetAudienceIRI(j)
audience[i][(&id).String()] = id
}
}
}
audienceActivity := make(map[string]interface{})
for i := 0; i < a.AudienceLen(); i++ {
if a.IsAudienceObject(i) {
id := a.GetAudienceObject(i).GetId()
audienceActivity[(&id).String()] = a.GetAudienceObject(i)
} else if a.IsAudienceLink(i) {
id := a.GetAudienceLink(i).GetHref()
audienceActivity[(&id).String()] = a.GetAudienceLink(i)
} else if a.IsAudienceIRI(i) {
id := a.GetAudienceIRI(i)
audienceActivity[(&id).String()] = id
}
}
// Next, add activity recipients to all objects if not already present
for k, v := range toActivity {
for i := 0; i < a.ObjectLen(); i++ {
if _, ok := to[i][k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.GetObject(i).AddToObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.GetObject(i).AddToLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.GetObject(i).AddToIRI(vIRI)
}
}
}
}
for k, v := range btoActivity {
for i := 0; i < a.ObjectLen(); i++ {
if _, ok := bto[i][k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.GetObject(i).AddBtoObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.GetObject(i).AddBtoLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.GetObject(i).AddBtoIRI(vIRI)
}
}
}
}
for k, v := range ccActivity {
for i := 0; i < a.ObjectLen(); i++ {
if _, ok := cc[i][k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.GetObject(i).AddCcObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.GetObject(i).AddCcLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.GetObject(i).AddCcIRI(vIRI)
}
}
}
}
for k, v := range bccActivity {
for i := 0; i < a.ObjectLen(); i++ {
if _, ok := bcc[i][k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.GetObject(i).AddBccObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.GetObject(i).AddBccLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.GetObject(i).AddBccIRI(vIRI)
}
}
}
}
for k, v := range audienceActivity {
for i := 0; i < a.ObjectLen(); i++ {
if _, ok := audience[i][k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.GetObject(i).AddAudienceObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.GetObject(i).AddAudienceLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.GetObject(i).AddAudienceIRI(vIRI)
}
}
}
}
// Finally, add all the objects' recipients to the activity if not
// already present.
for i := 0; i < a.ObjectLen(); i++ {
for k, v := range to[i] {
if _, ok := toActivity[k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.AddToObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.AddToLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.AddToIRI(vIRI)
}
}
}
for k, v := range bto[i] {
if _, ok := btoActivity[k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.AddBtoObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.AddBtoLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.AddBtoIRI(vIRI)
}
}
}
for k, v := range cc[i] {
if _, ok := ccActivity[k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.AddCcObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.AddCcLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.AddCcIRI(vIRI)
}
}
}
for k, v := range bcc[i] {
if _, ok := bccActivity[k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.AddBccObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.AddBccLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.AddBccIRI(vIRI)
}
}
}
for k, v := range audience[i] {
if _, ok := audienceActivity[k]; !ok {
if vObj, ok := v.(vocab.ObjectType); ok {
a.AddAudienceObject(vObj)
} else if vLink, ok := v.(vocab.LinkType); ok {
a.AddAudienceLink(vLink)
} else if vIRI, ok := v.(url.URL); ok {
a.AddAudienceIRI(vIRI)
}
}
}
}
return nil
}
// TODO: (Section 7) HTTP caching mechanisms [RFC7234] SHOULD be respected when appropriate, both when receiving responses from other servers as well as sending responses to other servers.
// deliver will complete the peer-to-peer sending of a federated message to