Add classes for activity vocabulary. Migrate actor to use them.
This commit is contained in:
parent
0c42dbb2f8
commit
6c1ac941eb
51
render.js
51
render.js
|
@ -60,16 +60,15 @@ const Render = {
|
|||
audienceActor: function(actor) {
|
||||
var display = '<section style="display:inline-block;">'
|
||||
if (actor.valid) {
|
||||
const icon = actor.info.icon ? actor.info.icon : Icons.fallback['user']
|
||||
display = display + '<img src="' + icon + '" width="32" height="32" /> '
|
||||
display = display + '<img src="' + actor.iconUrl(Icons.fallback['user']) + '" width="32" height="32" /> '
|
||||
+ '<p style="display:inline-block;"><strong>' + actor.displayName() + '</strong> <br/>'
|
||||
+ '<a href="' + actor.urls.profile + '">'
|
||||
+ '<a href="' + actor.data.id + '">'
|
||||
+ actor.address()
|
||||
+ '</a></p>'
|
||||
} else {
|
||||
display = display + '<img src="' + Icons.fallback['user'] + '" width="32" height="32" /> '
|
||||
+ '<p style="display:inline-block;">'
|
||||
+ '<a href="' + actor.urls.profile + '">'
|
||||
+ '<a href="' + actor.data.id + '">'
|
||||
+ 'Other actor'
|
||||
+ '</a></p>'
|
||||
}
|
||||
|
@ -138,7 +137,7 @@ const UI = {
|
|||
'my-inbox': function() {
|
||||
UI.updateNav('inbox-selector')
|
||||
if (UI.is_connected) {
|
||||
UI.showTimeline(ConnectedUser.actor.urls.inbox, ConnectedUser.tokens.user.access_token)
|
||||
UI.showTimeline(ConnectedUser.actor.data.inbox, ConnectedUser.tokens.user.access_token)
|
||||
UI.showPage('show-profile', ConnectedUser.actor)
|
||||
} else {
|
||||
UI.showTimeline(undefined, undefined)
|
||||
|
@ -148,7 +147,7 @@ const UI = {
|
|||
'my-outbox': function() {
|
||||
UI.updateNav('outbox-selector')
|
||||
if (UI.is_connected) {
|
||||
UI.showTimeline(ConnectedUser.actor.urls.outbox, ConnectedUser.tokens.user.access_token)
|
||||
UI.showTimeline(ConnectedUser.actor.data.outbox, ConnectedUser.tokens.user.access_token)
|
||||
UI.showPage('show-profile', ConnectedUser.actor)
|
||||
} else {
|
||||
UI.showTimeline(undefined, undefined)
|
||||
|
@ -167,8 +166,8 @@ const UI = {
|
|||
'other-profile': function() {
|
||||
UI.updateNav(undefined)
|
||||
UI.showPage('show-profile', UI.other_actor)
|
||||
if (UI.other_actor.urls.outbox) {
|
||||
UI.showTimeline(UI.other_actor.urls.outbox, undefined)
|
||||
if (UI.other_actor.data.outbox) {
|
||||
UI.showTimeline(UI.other_actor.data.outbox, undefined)
|
||||
} else {
|
||||
UI.displayError('Actor does not have a public outbox.')
|
||||
UI.showTimeline(undefined, undefined)
|
||||
|
@ -182,24 +181,22 @@ const UI = {
|
|||
},
|
||||
'ask-password': function(_) {
|
||||
Elem('connect-password').value = ''
|
||||
const icon = ConnectedUser.actor.info.icon ? ConnectedUser.actor.info.icon : Icons.fallback['user']
|
||||
Elem('ask-password-user-icon').innerHTML = '<img src="' + icon + '" width="32" height="32" />'
|
||||
Elem('ask-password-user-icon').innerHTML = '<img src="' + ConnectedUser.actor.iconUrl(Icons.fallback['user']) + '" width="32" height="32" />'
|
||||
Elem('ask-password-user-display-name').innerText = ConnectedUser.actor.displayName()
|
||||
Elem('ask-password-user-address').href = ConnectedUser.actor.urls.profile
|
||||
Elem('ask-password-user-address').href = ConnectedUser.actor.data.id
|
||||
Elem('ask-password-user-address').innerText = ConnectedUser.actor.address()
|
||||
},
|
||||
'show-profile': function(actor) {
|
||||
// data contains the actor to display
|
||||
const icon = actor.info.icon ? actor.info.icon : Icons.fallback['user']
|
||||
Elem('profile-icon').innerHTML = '<img src="' + icon + '" width="96" height="96" />'
|
||||
Elem('profile-icon').innerHTML = '<img src="' + actor.iconUrl(Icons.fallback['user']) + '" width="96" height="96" />'
|
||||
Elem('profile-display-name').innerText = actor.displayName()
|
||||
Elem('profile-address').href = actor.urls.profile
|
||||
Elem('profile-address').href = actor.data.id
|
||||
Elem('profile-address').innerText = actor.address()
|
||||
Elem('profile-type').innerText = actor.info.type
|
||||
Elem('profile-summary').innerHTML = actor.info.summary
|
||||
Elem('profile-code-source').innerText = JSON.stringify(actor.raw, null, 1)
|
||||
Elem('profile-type').innerText = actor.data.type
|
||||
Elem('profile-summary').innerHTML = actor.data.summary
|
||||
Elem('profile-code-source').innerText = JSON.stringify(actor.data._raw, null, 1)
|
||||
// Controls only shown if the actor is the connected user
|
||||
if (actor.urls.profile === ConnectedUser.actor.urls.profile) {
|
||||
if (actor.data.id === ConnectedUser.actor.data.id) {
|
||||
Elem('profile-controls-connected').style.display = 'block';
|
||||
} else {
|
||||
Elem('profile-controls-connected').style.display = 'none';
|
||||
|
@ -209,11 +206,10 @@ const UI = {
|
|||
// data contains the activity to display
|
||||
Elem('activity-type').innerText = activity.type
|
||||
Elem('activity-published').innerText = activity.published ? activity.published.toLocaleString() : ''
|
||||
const icon = activity.actor.info.icon ? activity.actor.info.icon : Icons.fallback['user']
|
||||
Elem('activity-actor-icon').innerHTML = '<img src="' + icon + '" width="48" height="48" />'
|
||||
Elem('activity-actor-icon').innerHTML = '<img src="' + activity.actor.iconUrl(Icons.fallback['user']) + '" width="48" height="48" />'
|
||||
Elem('activity-actor-display-name').innerText = activity.actor.displayName()
|
||||
Elem('activity-actor-address').innerText = activity.actor.address()
|
||||
Elem('activity-actor-address').href = activity.actor.urls.profile
|
||||
Elem('activity-actor-address').href = activity.actor.data.id
|
||||
Elem('activity-to').innerHTML = activity.to.map(
|
||||
function(element) {
|
||||
return '<li class="actor-display">' + Render.audienceActor(element) + '</li>'
|
||||
|
@ -228,11 +224,10 @@ const UI = {
|
|||
Elem('activity-object').style.display = 'block'
|
||||
Elem('activity-object-type').innerText = activity.object.type
|
||||
Elem('activity-object-published').innerText = activity.object.published ? activity.object.published.toLocaleString() : ''
|
||||
const icon = activity.object.actor.info.icon ? activity.object.actor.info.icon : Icons.fallback['user']
|
||||
Elem('activity-object-actor-icon').innerHTML = '<img src="' + icon + '" width="48" height="48" />'
|
||||
Elem('activity-object-actor-icon').innerHTML = '<img src="' + activity.object.actor.iconUrl(Icons.fallback['user']) + '" width="48" height="48" />'
|
||||
Elem('activity-object-actor-display-name').innerText = activity.object.actor.displayName()
|
||||
Elem('activity-object-actor-address').innerText = activity.object.actor.address()
|
||||
Elem('activity-object-actor-address').href = activity.object.actor.urls.profile
|
||||
Elem('activity-object-actor-address').href = activity.object.actor.data.id
|
||||
Elem('activity-object-to').innerHTML = activity.object.to.map(
|
||||
function(element) {
|
||||
return '<li class="actor-display">' + Render.audienceActor(element) + '</li>'
|
||||
|
@ -268,14 +263,14 @@ const UI = {
|
|||
function(element) {
|
||||
return '<li class="actor-display">'
|
||||
+ Render.audienceActor(element)
|
||||
+ ' <button style="vertical-align:top;" onclick="UI.removeToRecipient(\'' + element.urls.profile + '\')">×</button>'
|
||||
+ ' <button style="vertical-align:top;" onclick="UI.removeToRecipient(\'' + element.data.id + '\')">×</button>'
|
||||
+ '</li>'
|
||||
}).join('')
|
||||
Elem('send-message-cc').innerHTML = UI.composed_message.cc.map(
|
||||
function(element) {
|
||||
return '<li class="actor-display">'
|
||||
+ Render.audienceActor(element)
|
||||
+ ' <button style="vertical-align:top;" onclick="UI.removeCcRecipient(\'' + element.urls.profile + '\')">×</button>'
|
||||
+ ' <button style="vertical-align:top;" onclick="UI.removeCcRecipient(\'' + element.data.id + '\')">×</button>'
|
||||
+ '</li>'
|
||||
}).join('')
|
||||
}
|
||||
|
@ -452,7 +447,7 @@ const UI = {
|
|||
removeToRecipient: function(url_profile) {
|
||||
// Don't fetch the actor, only set the profile url used in removal
|
||||
const actor = new Actor()
|
||||
actor.urls.profile = url_profile
|
||||
actor.data.id = url_profile
|
||||
UI.composed_message.removeToRecipient(actor)
|
||||
UI.showPage('send-message', undefined)
|
||||
},
|
||||
|
@ -460,7 +455,7 @@ const UI = {
|
|||
removeCcRecipient: function(url_profile) {
|
||||
// Don't fetch the actor, only set the profile url used in removal
|
||||
const actor = new Actor()
|
||||
actor.urls.profile = url_profile
|
||||
actor.data.id = url_profile
|
||||
UI.composed_message.removeCcRecipient(actor)
|
||||
UI.showPage('send-message', undefined)
|
||||
},
|
||||
|
|
|
@ -0,0 +1,887 @@
|
|||
// Implementation of Activity Vocabulary, with extension for ActivityPub
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary
|
||||
// https://www.w3.org/TR/activitypub
|
||||
|
||||
// Note: their prototypes are defined after to allow the definition of ObjectFetcher
|
||||
// Base for other elements - to put things in common
|
||||
const ASBase = function() {}
|
||||
// Core types
|
||||
const ASObject = function() {}
|
||||
const ASLink = function() {}
|
||||
const ASActivity = function() {}
|
||||
const ASIntransitiveActivity = function() {}
|
||||
const ASCollection = function() {}
|
||||
const ASOrderedCollection = function() {}
|
||||
const ASCollectionPage = function() {}
|
||||
const ASOrderedCollectionPage = function() {}
|
||||
// Extension for modelisation of ActivityPub actors
|
||||
const ASActor = function() {}
|
||||
|
||||
// Extended Types
|
||||
// Activity Types
|
||||
const ASAccept = function() {}
|
||||
const ASAdd = function() {}
|
||||
const ASAnnounce = function() {}
|
||||
const ASArrive = function() {}
|
||||
const ASBlock = function() {}
|
||||
const ASCreate = function() {}
|
||||
const ASDelete = function() {}
|
||||
const ASDislike = function() {}
|
||||
const ASFlag = function() {}
|
||||
const ASFollow = function() {}
|
||||
const ASIgnore = function() {}
|
||||
const ASInvite = function() {}
|
||||
const ASJoin = function() {}
|
||||
const ASLeave = function() {}
|
||||
const ASLike = function() {}
|
||||
const ASListen = function() {}
|
||||
const ASMove = function() {}
|
||||
const ASOffer = function() {}
|
||||
const ASQuestion = function() {}
|
||||
const ASReject = function() {}
|
||||
const ASRead = function() {}
|
||||
const ASRemove = function() {}
|
||||
const ASTentativeReject = function() {}
|
||||
const ASTentativeAccept = function() {}
|
||||
const ASTravel = function() {}
|
||||
const ASUndo = function() {}
|
||||
const ASUpdate = function() {}
|
||||
const ASView = function() {}
|
||||
|
||||
// Actor Types
|
||||
const ASApplication = function() {}
|
||||
const ASGroup = function() {}
|
||||
const ASOrganization = function() {}
|
||||
const ASPerson = function() {}
|
||||
const ASService = function() {}
|
||||
|
||||
// Object Types
|
||||
const ASArticle = function() {}
|
||||
const ASAudio = function() {}
|
||||
const ASDocument = function() {}
|
||||
const ASEvent = function() {}
|
||||
const ASImage = function() {}
|
||||
const ASNote = function() {}
|
||||
const ASPage = function() {}
|
||||
const ASPlace = function() {}
|
||||
const ASProfile = function() {}
|
||||
const ASRelationship = function() {}
|
||||
const ASTombstone = function() {}
|
||||
const ASVideo = function() {}
|
||||
|
||||
// Link Types
|
||||
const ASMention = function() {}
|
||||
|
||||
// Object fetcher
|
||||
// Convert resources from the resource fetcher into Activity objects
|
||||
Fetcher = {
|
||||
// Cache of already accessed objects
|
||||
knownObjects: {},
|
||||
// Get an object
|
||||
// Callback signature is function(load_ok, fetched_object, failure_message)
|
||||
get: function(id, token, callback) {
|
||||
if (Fetcher.knownObjects[id]) {
|
||||
callback(true, Fetcher.knownObjects[id], '')
|
||||
} else {
|
||||
Fetcher.refresh(id, token, callback)
|
||||
}
|
||||
},
|
||||
// Refresh a resource
|
||||
// Callback signature is function(load_ok, fetched_object, failure_message)
|
||||
refresh: function(id, token, callback) {
|
||||
const request = new XMLHttpRequest()
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState == 4 && request.status == 200) {
|
||||
const answer = JSON.parse(request.responseText)
|
||||
if (answer) {
|
||||
// Build the object
|
||||
const obj = Fetcher.fromJson(answer)
|
||||
Fetcher.knownObjects[id] = obj
|
||||
callback(true, obj, undefined)
|
||||
} else {
|
||||
callback(false, 'Unexpected answer from server when fetching object.')
|
||||
console.log(answer)
|
||||
}
|
||||
} else if (request.readyState == 4) {
|
||||
callback(false, 'Server error when fetching object (' + request.status + ').')
|
||||
}
|
||||
}
|
||||
request.open('GET', id, true)
|
||||
if (token) {
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + token)
|
||||
}
|
||||
request.setRequestHeader('Content-Type', 'application/activity+json')
|
||||
request.setRequestHeader('Accept', 'application/activity+json')
|
||||
request.send()
|
||||
},
|
||||
// Build from type name
|
||||
_type_from_name: {
|
||||
// Core types
|
||||
'Object': ASObject,
|
||||
'Link': ASLink,
|
||||
'Activity': ASActivity,
|
||||
'IntransitiveActivity': ASIntransitiveActivity,
|
||||
'Collection': ASCollection,
|
||||
'OrderedCollection': ASOrderedCollection,
|
||||
'CollectionPage': ASCollectionPage,
|
||||
'OrderedCollectionPage': ASOrderedCollectionPage,
|
||||
'Actor': ASActor,
|
||||
// Activity Types
|
||||
'Accept': ASAccept,
|
||||
'Add': ASAdd,
|
||||
'Announce': ASAnnounce,
|
||||
'Arrive': ASArrive,
|
||||
'Block': ASBlock,
|
||||
'Create': ASCreate,
|
||||
'Delete': ASDelete,
|
||||
'Dislike': ASDislike,
|
||||
'Flag': ASFlag,
|
||||
'Follow': ASFollow,
|
||||
'Ignore': ASIgnore,
|
||||
'Invite': ASInvite,
|
||||
'Join': ASJoin,
|
||||
'Leave': ASLeave,
|
||||
'Like': ASLike,
|
||||
'Listen': ASListen,
|
||||
'Move': ASMove,
|
||||
'Offer': ASOffer,
|
||||
'Question': ASQuestion,
|
||||
'Reject': ASReject,
|
||||
'Read': ASRead,
|
||||
'Remove': ASRemove,
|
||||
'TentativeReject': ASTentativeReject,
|
||||
'TentativeAccept': ASTentativeAccept,
|
||||
'Travel': ASTravel,
|
||||
'Undo': ASUndo,
|
||||
'Update': ASUpdate,
|
||||
'View': ASView,
|
||||
// Actor Types
|
||||
'Application': ASApplication,
|
||||
'Group': ASGroup,
|
||||
'Organization': ASOrganization,
|
||||
'Person': ASPerson,
|
||||
'Service': ASService,
|
||||
// Object Types
|
||||
'Article': ASArticle,
|
||||
'Audio': ASAudio,
|
||||
'Document': ASDocument,
|
||||
'Event': ASEvent,
|
||||
'Image': ASImage,
|
||||
'Note': ASNote,
|
||||
'Page': ASPage,
|
||||
'Place': ASPlace,
|
||||
'Profile': ASProfile,
|
||||
'Relationship': ASRelationship,
|
||||
'Tombstone': ASTombstone,
|
||||
'Video': ASVideo,
|
||||
// Link Types
|
||||
'Mention': ASMention,
|
||||
},
|
||||
// Add additionnal types
|
||||
addType: function(name, type) {
|
||||
Fetcher._type_from_name[name] = type
|
||||
},
|
||||
// Build an object of the right type from its json representation
|
||||
fromJson: function(raw) {
|
||||
const result = Fetcher._type_from_name[raw.type] ? new (Fetcher._type_from_name[raw.type])() : new ASBase()
|
||||
result.fromJson(raw)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// ASBase prototype
|
||||
ASBase.prototype = {
|
||||
// Raw representation of the element
|
||||
_raw: undefined,
|
||||
// Description of the properties
|
||||
// Array of properties that do not need to be fetched
|
||||
// Those are directly copied from/to raw into the object
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ['id', 'type'],
|
||||
// Array of properties that may need to be fetched
|
||||
// Those need an additional fetch from raw, and special handling when converting back to raw
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: [ /*empty*/ ],
|
||||
// Properties
|
||||
id: undefined, // Id of element
|
||||
type: undefined, // Type of element
|
||||
// Methods
|
||||
// Load from a Json representation
|
||||
fromJson: function(raw) {
|
||||
this._raw = raw
|
||||
// copy everything expected from raw
|
||||
this._alwaysAvailable.map(function(elem) {
|
||||
this[elem] = raw[elem]
|
||||
}.bind(this))
|
||||
// Copy everything expected from raw, fetchAttribute should be used to retrieve the values when needed
|
||||
this._mayNeedFetch.map(function(elem) {
|
||||
this[elem] = raw[elem]
|
||||
}.bind(this))
|
||||
},
|
||||
// Convert to Json representation
|
||||
updateRaw: function() {
|
||||
this._raw = {}
|
||||
// Copy everything of _alwaysAvailable into raw
|
||||
this._alwaysAvailable.map(function(elem) {
|
||||
this._raw[elem] = this[elem]
|
||||
}.bind(this))
|
||||
// Use getAttributeRawValue for everything of _mayNeedFetch
|
||||
this._mayNeedFetch.map(function(elem) {
|
||||
this._raw[elem] = this.getAttributeRawValue(this[elem])
|
||||
}.bind(this))
|
||||
},
|
||||
// Utilitary function to use for updating raw data
|
||||
// Returns either:
|
||||
// - val.id if it exists
|
||||
// - val._raw if val.updateRaw exists, after a call to val.updateRaw()
|
||||
// - val in other cases
|
||||
getAttributeRawValue: function(val) {
|
||||
if (val && val.id) {
|
||||
return val.id
|
||||
} else if (val && val.updateRaw) {
|
||||
val.updateRaw()
|
||||
return val._raw
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
},
|
||||
// For fetching properties
|
||||
// Fetch a property
|
||||
// After fromJson has been called, the attributes may not be yet in a usable format, notably those which can be objects
|
||||
// This method fetch and convert an attribute in a usable format
|
||||
// Callback signature is function(load_ok, failure_message)
|
||||
// Note: done this way to avoid fetching all resources in fromJson, but only when they are needed
|
||||
fetchAttribute: function(attribute_name, token, callback) {
|
||||
const attribute_value = this[attribute_name]
|
||||
if (this._alwaysAvailable.includes(attribute_name)) {
|
||||
// Those attributes are always available in a usable format, no need to fetch
|
||||
callback(true, undefined)
|
||||
} else if (!this._mayNeedFetch.includes(attribute_name)) {
|
||||
// Invalid call: the asked attribute cannot be fetched
|
||||
callback(false, 'Invalid argument to fetchAttribute: ' + attribute_name + ' cannot be fetched.')
|
||||
} else if ((typeof attribute_value === undefined) || (typeof attribute_value === null)) {
|
||||
// Nothing to do
|
||||
callback(true, undefined)
|
||||
} else if (Array.isArray(attribute_value)) {
|
||||
// Attribute is an array of elements. Each element should be converted.
|
||||
this._fetchAndConvertAllAttributeValue(attribute_value.values(), token, function(load_ok, fetched_value, failure_message) {
|
||||
// Update value of attribute
|
||||
this[attribute_name] = fetched_value
|
||||
callback(load_ok, failure_message)
|
||||
}.bind(this))
|
||||
} else if ((typeof attribute_value === 'object') && (attribute_value._raw !== undefined)) {
|
||||
// Attribute has already been fetched
|
||||
callback(true, undefined)
|
||||
} else {
|
||||
// Convertion needed
|
||||
this._fetchAndConvertAttributeValue(attribute_value, token, function(load_ok, fetched_value, failure_message) {
|
||||
// Update value of attribute
|
||||
this[attribute_name] = fetched_value
|
||||
callback(load_ok, failure_message)
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
// Fetch all elements of an array iterator to populate a result array
|
||||
// Callback signature is function(load_ok, fetched_value, failure_message)
|
||||
_fetchAndConvertAllAttributeValue: function(iter, token, callback, ret_value, previous_errors) {
|
||||
const next = iter.next()
|
||||
if (next.done) {
|
||||
callback(true, ret_value, (previous_errors === '') ? undefined : previous_errors)
|
||||
} else {
|
||||
this._fetchAndConvertAttributeValue(next.value, token, function(load_ok, fetched_value, failure_message) {
|
||||
var values = ret_value ? ret_value : []
|
||||
const msg_errors = (previous_errors ? previous_errors : '') + (load_ok ? '' : '<br/>' + failure_message)
|
||||
if (load_ok) {
|
||||
values.push(fetched_value)
|
||||
} else {
|
||||
// Don't stop at first error, but cumulate error messages
|
||||
console.log(failure_message)
|
||||
}
|
||||
// Next
|
||||
this._fetchAndConvertAllAttributeValue(iter, token, callback, values, msg_errors)
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
// Fetch an attribute value
|
||||
// Callback signature is function(load_ok, fetched_value, failure_message)
|
||||
_fetchAndConvertAttributeValue: function(attribute_value, token, callback) {
|
||||
if ((attribute_value === undefined) || (attribute_value === null)) {
|
||||
// attribute is not present, return it as is
|
||||
callback(true, attribute_value, undefined)
|
||||
} else if (typeof attribute_value === 'object') {
|
||||
// In object format already, as a Json value,
|
||||
// use Fetcher.fromJson
|
||||
callback(true, Fetcher.fromJson(attribute_value), undefined)
|
||||
} else if (typeof attribute_value === 'string') {
|
||||
// Link => fetch value
|
||||
Fetcher.get(attribute_value, token, function(load_ok, obj, failure_message) {
|
||||
if (load_ok) {
|
||||
callback(true, obj, undefined)
|
||||
} else {
|
||||
callback(false, undefined, failure_message)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Should not happen
|
||||
console.log(attribute_value)
|
||||
callback(false, undefined, 'Unexpected type of attribute.')
|
||||
}
|
||||
},
|
||||
// Fetch several attributes at the same time
|
||||
// Callback signature is function(load_ok, failure_message)
|
||||
fetchAttributeList: function (attribute_lst, token, callback) {
|
||||
this._fetchAttributeListIter(attribute_lst.values(), token, callback, undefined)
|
||||
},
|
||||
//
|
||||
_fetchAttributeListIter: function (attribute_lst, token, callback, previous_errors) {
|
||||
const next = attribute_lst.next()
|
||||
if (next.done) {
|
||||
callback(true, (previous_errors === '') ? undefined : previous_errors)
|
||||
} else {
|
||||
this.fetchAttribute(next.value, token, function(load_ok, failure_message) {
|
||||
const msg_errors = (previous_errors ? previous_errors : '') + (load_ok ? '' : '<br/>' + failure_message)
|
||||
if (!load_ok) {
|
||||
// Don't stop at first error, but cumulate error messages
|
||||
console.log(failure_message)
|
||||
}
|
||||
// Next
|
||||
this._fetchAttributeListIter(attribute_lst, token, callback, msg_errors)
|
||||
}.bind(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ASObject prototype
|
||||
ASObject.prototype = Object.create(ASBase.prototype)
|
||||
Object.assign(ASObject.prototype,
|
||||
{
|
||||
// Attributes
|
||||
attachment: undefined,
|
||||
attributedTo: undefined,
|
||||
audience: undefined,
|
||||
content: undefined,
|
||||
context: undefined,
|
||||
name: undefined,
|
||||
endTime: undefined,
|
||||
generator: undefined,
|
||||
icon: undefined,
|
||||
image: undefined,
|
||||
inReplyTo: undefined,
|
||||
location: undefined,
|
||||
preview: undefined,
|
||||
published: undefined,
|
||||
replies: undefined,
|
||||
startTime: undefined,
|
||||
summary: undefined,
|
||||
tag: undefined,
|
||||
updated: undefined,
|
||||
url: undefined,
|
||||
to: undefined,
|
||||
bto: undefined,
|
||||
cc: undefined,
|
||||
bcc: undefined,
|
||||
mediaType: undefined,
|
||||
duration: undefined,
|
||||
// defined in ActivityPub
|
||||
source: undefined,
|
||||
likes: undefined,
|
||||
shares: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASBase.prototype._alwaysAvailable.concat([
|
||||
'content', 'name', 'endTime', 'published', 'startTime', 'summary', 'updated', 'url',
|
||||
'mediaType', 'duration',
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASBase.prototype._mayNeedFetch.concat([
|
||||
'attachment', 'attributedTo', 'audience', 'context', 'generator', 'icon', 'image', 'inReplyTo',
|
||||
'location', 'preview', 'replies', 'tag', 'to', 'bto', 'cc', 'bcc', 'source', 'likes', 'shares'
|
||||
]),
|
||||
})
|
||||
ASObject.prototype.constructor = ASObject
|
||||
|
||||
// ASLink prototype
|
||||
ASLink.prototype = Object.create(ASBase.prototype)
|
||||
Object.assign(ASLink.prototype,
|
||||
{
|
||||
// Attributes
|
||||
href: undefined,
|
||||
rel: undefined,
|
||||
mediaType: undefined,
|
||||
name: undefined,
|
||||
hreflang: undefined,
|
||||
height: undefined,
|
||||
width: undefined,
|
||||
preview: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASBase.prototype._alwaysAvailable.concat([
|
||||
'href', 'rel', 'mediaType', 'name', 'hreflang', 'height', 'width',
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASBase.prototype._mayNeedFetch.concat([
|
||||
'preview',
|
||||
]),
|
||||
})
|
||||
ASLink.prototype.constructor = ASLink
|
||||
|
||||
// ASActivity prototype
|
||||
ASActivity.prototype = Object.create(ASObject.prototype)
|
||||
Object.assign(ASActivity.prototype,
|
||||
{
|
||||
// Attributes
|
||||
actor: undefined,
|
||||
object: undefined,
|
||||
target: undefined,
|
||||
result: undefined,
|
||||
origin: undefined,
|
||||
instrument: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASObject.prototype._alwaysAvailable.concat([
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASObject.prototype._mayNeedFetch.concat([
|
||||
'actor', 'object', 'target', 'result', 'origin', 'instrument',
|
||||
]),
|
||||
})
|
||||
ASActivity.prototype.constructor = ASActivity
|
||||
|
||||
// ASIntransitiveActivity prototype
|
||||
// Note: the object field is not removed from ASActivity
|
||||
ASIntransitiveActivity.prototype = Object.create(ASActivity.prototype)
|
||||
ASIntransitiveActivity.prototype.constructor = ASIntransitiveActivity
|
||||
|
||||
// ASCollection prototype
|
||||
ASCollection.prototype = Object.create(ASObject.prototype)
|
||||
Object.assign(ASCollection.prototype,
|
||||
{
|
||||
// Attributes
|
||||
totalItems: undefined,
|
||||
current: undefined,
|
||||
first: undefined,
|
||||
last: undefined,
|
||||
items: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASObject.prototype._alwaysAvailable.concat([
|
||||
'totalItems',
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASObject.prototype._mayNeedFetch.concat([
|
||||
'current', 'first', 'last', 'items',
|
||||
]),
|
||||
})
|
||||
ASCollection.prototype.constructor = ASCollection
|
||||
|
||||
// ASOrderedCollection prototype
|
||||
ASOrderedCollection.prototype = Object.create(ASCollection.prototype)
|
||||
ASOrderedCollection.prototype.constructor = ASOrderedCollection
|
||||
|
||||
// ASCollectionPage prototype
|
||||
ASCollectionPage.prototype = Object.create(ASCollection.prototype)
|
||||
Object.assign(ASCollectionPage.prototype,
|
||||
{
|
||||
// Attributes
|
||||
partOf: undefined,
|
||||
next: undefined,
|
||||
prev: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASCollection.prototype._alwaysAvailable.concat([
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASCollection.prototype._mayNeedFetch.concat([
|
||||
'partOf', 'next', 'prev',
|
||||
]),
|
||||
})
|
||||
ASCollectionPage.prototype.constructor = ASCollectionPage
|
||||
|
||||
// ASOrderedCollectionPage prototype
|
||||
// Specification says that it inherits from both ASCollectionPage and ASOrderedCollection,
|
||||
// but ASOrderedCollection does not defines more fields than ASCollection,
|
||||
// so we only inherit from ASCollectionPage, which already inherits from ASCollection
|
||||
ASOrderedCollectionPage.prototype = Object.create(ASCollectionPage.prototype)
|
||||
Object.assign(ASOrderedCollectionPage.prototype,
|
||||
{
|
||||
// Attributes
|
||||
startIndex: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASCollectionPage.prototype._alwaysAvailable.concat([
|
||||
'startIndex',
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASCollectionPage.prototype._mayNeedFetch.concat([
|
||||
]),
|
||||
})
|
||||
ASOrderedCollectionPage.prototype.constructor = ASOrderedCollectionPage
|
||||
|
||||
// ASActor
|
||||
// Defined from the ActivityPub spec, which adds fields to actor types
|
||||
ASActor.prototype = Object.create(ASObject.prototype)
|
||||
Object.assign(ASActor.prototype,
|
||||
{
|
||||
// Attributes
|
||||
inbox: undefined,
|
||||
outbox: undefined,
|
||||
following: undefined,
|
||||
followers: undefined,
|
||||
liked: undefined,
|
||||
streams: undefined,
|
||||
preferredUsername: undefined,
|
||||
endpoints: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASObject.prototype._alwaysAvailable.concat([
|
||||
'preferredUsername', 'endpoints',
|
||||
// Note: endpoints should be in the _mayNeedFetch, but it is usually not represented with a vocabulary object, so no type associated
|
||||
// However, in the observed implementations, this structure is always provided in the actor
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASObject.prototype._mayNeedFetch.concat([
|
||||
'inbox', 'outbox', 'following', 'followers', 'liked', 'streams',
|
||||
]),
|
||||
})
|
||||
ASActor.prototype.constructor = ASActor
|
||||
|
||||
// Extended Types
|
||||
// Activity Types
|
||||
|
||||
// ASAccept prototype
|
||||
ASAccept.prototype = Object.create(ASActivity.prototype)
|
||||
ASAccept.prototype.constructor = ASAccept
|
||||
|
||||
// ASTentativeAccept prototype
|
||||
ASTentativeAccept.prototype = Object.create(ASAccept.prototype)
|
||||
ASAccept.prototype.constructor = ASAccept
|
||||
|
||||
// ASAdd prototype
|
||||
ASAdd.prototype = Object.create(ASActivity.prototype)
|
||||
ASAdd.prototype.constructor = ASAdd
|
||||
|
||||
// ASArrive prototype
|
||||
ASArrive.prototype = Object.create(ASIntransitiveActivity.prototype)
|
||||
ASArrive.prototype.constructor = ASArrive
|
||||
|
||||
// ASCreate prototype
|
||||
ASCreate.prototype = Object.create(ASActivity.prototype)
|
||||
ASCreate.prototype.constructor = ASCreate
|
||||
|
||||
// ASDelete prototype
|
||||
ASDelete.prototype = Object.create(ASActivity.prototype)
|
||||
ASDelete.prototype.constructor = ASDelete
|
||||
|
||||
// ASFollow prototype
|
||||
ASFollow.prototype = Object.create(ASActivity.prototype)
|
||||
ASFollow.prototype.constructor = ASFollow
|
||||
|
||||
// ASIgnore prototype
|
||||
ASIgnore.prototype = Object.create(ASActivity.prototype)
|
||||
ASIgnore.prototype.constructor = ASIgnore
|
||||
|
||||
// ASJoin prototype
|
||||
ASJoin.prototype = Object.create(ASActivity.prototype)
|
||||
ASJoin.prototype.constructor = ASJoin
|
||||
|
||||
// ASLeave prototype
|
||||
ASLeave.prototype = Object.create(ASActivity.prototype)
|
||||
ASLeave.prototype.constructor = ASLeave
|
||||
|
||||
// ASLike prototype
|
||||
ASLike.prototype = Object.create(ASActivity.prototype)
|
||||
ASLike.prototype.constructor = ASLike
|
||||
|
||||
// ASOffer prototype
|
||||
ASOffer.prototype = Object.create(ASActivity.prototype)
|
||||
ASOffer.prototype.constructor = ASOffer
|
||||
|
||||
// ASInvite prototype
|
||||
ASInvite.prototype = Object.create(ASOffer.prototype)
|
||||
ASInvite.prototype.constructor = ASInvite
|
||||
|
||||
// ASReject prototype
|
||||
ASReject.prototype = Object.create(ASActivity.prototype)
|
||||
ASReject.prototype.constructor = ASReject
|
||||
|
||||
// ASTentativeReject prototype
|
||||
ASTentativeReject.prototype = Object.create(ASReject.prototype)
|
||||
ASTentativeReject.prototype.constructor = ASTentativeReject
|
||||
|
||||
// ASRemove prototype
|
||||
ASRemove.prototype = Object.create(ASActivity.prototype)
|
||||
ASRemove.prototype.constructor = ASRemove
|
||||
|
||||
// ASUndo prototype
|
||||
ASUndo.prototype = Object.create(ASActivity.prototype)
|
||||
ASUndo.prototype.constructor = ASUndo
|
||||
|
||||
// ASUpdate prototype
|
||||
ASUpdate.prototype = Object.create(ASActivity.prototype)
|
||||
ASUpdate.prototype.constructor = ASUpdate
|
||||
|
||||
// ASView prototype
|
||||
ASView.prototype = Object.create(ASActivity.prototype)
|
||||
ASView.prototype.constructor = ASView
|
||||
|
||||
// ASListen prototype
|
||||
ASListen.prototype = Object.create(ASActivity.prototype)
|
||||
ASListen.prototype.constructor = ASListen
|
||||
|
||||
// ASRead prototype
|
||||
ASRead.prototype = Object.create(ASActivity.prototype)
|
||||
ASRead.prototype.constructor = ASRead
|
||||
|
||||
// ASMove prototype
|
||||
ASMove.prototype = Object.create(ASActivity.prototype)
|
||||
ASMove.prototype.constructor = ASMove
|
||||
|
||||
// ASTravel prototype
|
||||
ASTravel.prototype = Object.create(ASIntransitiveActivity.prototype)
|
||||
ASTravel.prototype.constructor = ASTravel
|
||||
|
||||
// ASAnnounce prototype
|
||||
ASAnnounce.prototype = Object.create(ASActivity.prototype)
|
||||
ASAnnounce.prototype.constructor = ASAnnounce
|
||||
|
||||
// ASBlock prototype
|
||||
ASBlock.prototype = Object.create(ASIgnore.prototype)
|
||||
ASBlock.prototype.constructor = ASBlock
|
||||
|
||||
// ASFlag prototype
|
||||
ASFlag.prototype = Object.create(ASActivity.prototype)
|
||||
ASFlag.prototype.constructor = ASFlag
|
||||
|
||||
// ASDislike prototype
|
||||
ASDislike.prototype = Object.create(ASActivity.prototype)
|
||||
ASDislike.prototype.constructor = ASDislike
|
||||
|
||||
// ASQuestion prototype
|
||||
ASQuestion.prototype = Object.create(ASIntransitiveActivity.prototype)
|
||||
Object.assign(ASQuestion.prototype,
|
||||
{
|
||||
// Attributes
|
||||
oneOf: undefined,
|
||||
anyOf: undefined,
|
||||
closed: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASIntransitiveActivity.prototype._alwaysAvailable.concat([
|
||||
'closed',
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASIntransitiveActivity.prototype._mayNeedFetch.concat([
|
||||
'oneOf', 'anyOf',
|
||||
]),
|
||||
})
|
||||
ASQuestion.prototype.constructor = ASQuestion
|
||||
|
||||
// Actor Types
|
||||
// ASApplication prototype
|
||||
ASApplication.prototype = Object.create(ASActor.prototype)
|
||||
ASApplication.prototype.constructor = ASApplication
|
||||
|
||||
// ASGroup prototype
|
||||
ASGroup.prototype = Object.create(ASActor.prototype)
|
||||
ASGroup.prototype.constructor = ASGroup
|
||||
|
||||
// ASOrganization prototype
|
||||
ASOrganization.prototype = Object.create(ASActor.prototype)
|
||||
ASOrganization.prototype.constructor = ASOrganization
|
||||
|
||||
// ASPerson prototype
|
||||
ASPerson.prototype = Object.create(ASActor.prototype)
|
||||
ASPerson.prototype.constructor = ASPerson
|
||||
|
||||
// ASService prototype
|
||||
ASService.prototype = Object.create(ASActor.prototype)
|
||||
ASService.prototype.constructor = ASService
|
||||
|
||||
// ASRelationship prototype
|
||||
ASRelationship.prototype = Object.create(ASObject.prototype)
|
||||
Object.assign(ASRelationship.prototype,
|
||||
{
|
||||
// Attributes
|
||||
subject: undefined,
|
||||
object: undefined,
|
||||
relationship: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASObject.prototype._alwaysAvailable.concat([
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASObject.prototype._mayNeedFetch.concat([
|
||||
'subject', 'object', 'relationship',
|
||||
]),
|
||||
})
|
||||
ASRelationship.prototype.constructor = ASRelationship
|
||||
|
||||
// ASArticle prototype
|
||||
ASArticle.prototype = Object.create(ASObject.prototype)
|
||||
ASArticle.prototype.constructor = ASArticle
|
||||
|
||||
// ASDocument prototype
|
||||
ASDocument.prototype = Object.create(ASObject.prototype)
|
||||
ASDocument.prototype.constructor = ASDocument
|
||||
|
||||
// ASAudio prototype
|
||||
ASAudio.prototype = Object.create(ASDocument.prototype)
|
||||
ASAudio.prototype.constructor = ASAudio
|
||||
|
||||
// ASImage prototype
|
||||
ASImage.prototype = Object.create(ASDocument.prototype)
|
||||
ASImage.prototype.constructor = ASImage
|
||||
|
||||
// ASVideo prototype
|
||||
ASVideo.prototype = Object.create(ASDocument.prototype)
|
||||
ASVideo.prototype.constructor = ASVideo
|
||||
|
||||
// ASNote prototype
|
||||
ASNote.prototype = Object.create(ASObject.prototype)
|
||||
ASNote.prototype.constructor = ASNote
|
||||
|
||||
// ASPage prototype
|
||||
ASPage.prototype = Object.create(ASDocument.prototype)
|
||||
ASPage.prototype.constructor = ASPage
|
||||
|
||||
// ASEvent prototype
|
||||
ASEvent.prototype = Object.create(ASObject.prototype)
|
||||
ASEvent.prototype.constructor = ASEvent
|
||||
|
||||
// ASPlace prototype
|
||||
ASPlace.prototype = Object.create(ASObject.prototype)
|
||||
Object.assign(ASPlace.prototype,
|
||||
{
|
||||
// Attributes
|
||||
accuracy: undefined,
|
||||
altitude: undefined,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
radius: undefined,
|
||||
units: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASObject.prototype._alwaysAvailable.concat([
|
||||
'accuracy', 'altitude', 'latitude', 'longitude',
|
||||
'radius', 'units',
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASObject.prototype._mayNeedFetch.concat([
|
||||
]),
|
||||
})
|
||||
ASPlace.prototype.constructor = ASPlace
|
||||
|
||||
// ASProfile prototype
|
||||
ASProfile.prototype = Object.create(ASObject.prototype)
|
||||
Object.assign(ASProfile.prototype,
|
||||
{
|
||||
// Attributes
|
||||
describes: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASObject.prototype._alwaysAvailable.concat([
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASObject.prototype._mayNeedFetch.concat([
|
||||
'describes'
|
||||
]),
|
||||
})
|
||||
ASProfile.prototype.constructor = ASProfile
|
||||
|
||||
// ASTombstone prototype
|
||||
ASTombstone.prototype = Object.create(ASObject.prototype)
|
||||
Object.assign(ASTombstone.prototype,
|
||||
{
|
||||
// Attributes
|
||||
formerType: undefined,
|
||||
deleted: undefined,
|
||||
// Array of properties that do not need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_alwaysAvailable: ASObject.prototype._alwaysAvailable.concat([
|
||||
'formerType', 'deleted',
|
||||
]),
|
||||
// Array of properties that may need to be fetched
|
||||
// This array should be overwritten when inheriting prototypes (by completing it)
|
||||
_mayNeedFetch: ASObject.prototype._mayNeedFetch.concat([
|
||||
]),
|
||||
})
|
||||
ASTombstone.prototype.constructor = ASTombstone
|
||||
|
||||
// Link Types
|
||||
// ASMention prototype
|
||||
ASMention.prototype = Object.create(ASLink.prototype)
|
||||
ASMention.prototype.constructor = ASMention
|
||||
|
||||
////
|
||||
// Exported structures
|
||||
// Fetcher
|
||||
exports.Fetcher = Fetcher
|
||||
// Core types
|
||||
exports.ASObject = ASObject
|
||||
exports.ASLink = ASLink
|
||||
exports.ASActivity = ASActivity
|
||||
exports.ASIntransitiveActivity = ASIntransitiveActivity
|
||||
exports.ASCollection = ASCollection
|
||||
exports.ASOrderedCollection = ASOrderedCollection
|
||||
exports.ASCollectionPage = ASCollectionPage
|
||||
exports.ASOrderedCollectionPage = ASOrderedCollectionPage
|
||||
exports.ASActor = ASActor
|
||||
// Activity types
|
||||
exports.ASAccept = ASAccept
|
||||
exports.ASAdd = ASAdd
|
||||
exports.ASAnnounce = ASAnnounce
|
||||
exports.ASArrive = ASArrive
|
||||
exports.ASBlock = ASBlock
|
||||
exports.ASCreate = ASCreate
|
||||
exports.ASDelete = ASDelete
|
||||
exports.ASDislike = ASDislike
|
||||
exports.ASFlag = ASFlag
|
||||
exports.ASFollow = ASFollow
|
||||
exports.ASIgnore = ASIgnore
|
||||
exports.ASInvite = ASInvite
|
||||
exports.ASJoin = ASJoin
|
||||
exports.ASLeave = ASLeave
|
||||
exports.ASLike = ASLike
|
||||
exports.ASListen = ASListen
|
||||
exports.ASMove = ASMove
|
||||
exports.ASOffer = ASOffer
|
||||
exports.ASQuestion = ASQuestion
|
||||
exports.ASReject = ASReject
|
||||
exports.ASRead = ASRead
|
||||
exports.ASRemove = ASRemove
|
||||
exports.ASTentativeReject = ASTentativeReject
|
||||
exports.ASTentativeAccept = ASTentativeAccept
|
||||
exports.ASTravel = ASTravel
|
||||
exports.ASUpdate = ASUpdate
|
||||
exports.ASView = ASView
|
||||
// Actor types
|
||||
exports.ASApplication = ASApplication
|
||||
exports.ASGroup = ASGroup
|
||||
exports.ASOrganization = ASOrganization
|
||||
exports.ASPerson = ASPerson
|
||||
exports.ASService = ASService
|
||||
// Object types
|
||||
exports.ASArticle = ASArticle
|
||||
exports.ASAudio = ASAudio
|
||||
exports.ASDocument = ASDocument
|
||||
exports.ASImage = ASImage
|
||||
exports.ASNote = ASNote
|
||||
exports.ASPage = ASPage
|
||||
exports.ASPlace = ASPlace
|
||||
exports.ASProfile = ASProfile
|
||||
exports.ASRelationship = ASRelationship
|
||||
exports.ASTombstone = ASTombstone
|
||||
exports.ASVideo = ASVideo
|
||||
// Link types
|
||||
exports.ASMention = ASMention
|
131
src/actor.js
131
src/actor.js
|
@ -1,42 +1,22 @@
|
|||
const {Fetcher, ASActor} = require('./activity-vocabulary.js')
|
||||
|
||||
// Actor class
|
||||
// ActorInfo structure
|
||||
var ActorInfo = function() {}
|
||||
ActorInfo.prototype = {
|
||||
display_name: undefined,
|
||||
summary: undefined,
|
||||
icon: undefined,
|
||||
type: undefined
|
||||
}
|
||||
// ActorUrls structure
|
||||
var ActorUrls = function() {}
|
||||
ActorUrls.prototype = {
|
||||
profile: undefined,
|
||||
outbox: undefined,
|
||||
inbox: undefined,
|
||||
followers: undefined,
|
||||
following: undefined,
|
||||
oauth_token: undefined,
|
||||
oauth_registration: undefined,
|
||||
oauth_authorization: undefined
|
||||
}
|
||||
// Actor structure
|
||||
var Actor = function() {
|
||||
this.info = new ActorInfo()
|
||||
this.urls = new ActorUrls()
|
||||
}
|
||||
// Add additional services around an ASActor
|
||||
const Actor = function() {}
|
||||
// Prototype
|
||||
Actor.prototype = {
|
||||
// Attributes
|
||||
name: undefined,
|
||||
server: undefined,
|
||||
info: undefined,
|
||||
urls: undefined,
|
||||
raw: undefined,
|
||||
data: undefined, // ASActor
|
||||
name: undefined, // Name of actor, should correspond to data.preferredUsername if data is present
|
||||
server: undefined, // Server of the actor, for use of name@server style
|
||||
valid: false,
|
||||
// Methods
|
||||
// Get the name used for display
|
||||
displayName: function() {
|
||||
if (this.info.display_name) {
|
||||
return this.info.display_name
|
||||
if (this.data && this.data.name) {
|
||||
return this.data.name
|
||||
} else if (this.data && this.data.preferredUsername) {
|
||||
return this.data.preferredUsername
|
||||
} else {
|
||||
return this.name
|
||||
}
|
||||
|
@ -45,6 +25,14 @@ Actor.prototype = {
|
|||
address: function() {
|
||||
return this.name + '@' + this.server
|
||||
},
|
||||
// Get the icon, or a default icon
|
||||
iconUrl: function(default_icon) {
|
||||
if (this.data && this.data.icon && this.data.icon.url) {
|
||||
return this.data.icon.url
|
||||
} else {
|
||||
return default_icon
|
||||
}
|
||||
},
|
||||
// Load from a "name@server" address.
|
||||
// Callback is a function accepting two arguments:
|
||||
// - a boolean indicating if the loading is complete or in failure,
|
||||
|
@ -88,61 +76,37 @@ Actor.prototype = {
|
|||
request.open('GET', 'https://' + this.server + '/.well-known/webfinger' + '?resource=acct:' + address, true)
|
||||
request.send()
|
||||
},
|
||||
// Indicate if the type is compatible with an actor
|
||||
isExpectedActorType: function(actorType) {
|
||||
return (actorType === 'Application') || (actorType === 'Group') || (actorType === 'Organization') || (actorType === 'Person') || (actorType === 'Service')
|
||||
},
|
||||
// Load from the link identifying the account
|
||||
// Callback is a function accepting two arguments:
|
||||
// - a boolean indicating if the loading is complete or in failure,
|
||||
// - a string indicating the failure
|
||||
loadFromProfileUrl: function(profile_url, callback) {
|
||||
this.urls.profile = profile_url
|
||||
// Use ActivityPub protocol to get the user infos
|
||||
const request = new XMLHttpRequest()
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState == 4 && request.status == 200) {
|
||||
const answer = JSON.parse(request.responseText)
|
||||
if (answer && this.isExpectedActorType(answer.type)) {
|
||||
this.raw = answer
|
||||
// name and server are previously filled if called from loadFromNameAndServer
|
||||
if (!this.name) {
|
||||
this.name = answer.preferredUsername
|
||||
}
|
||||
if (!this.server) {
|
||||
this.server = profile_url.replace('http://', '').replace('https://', '').split(/[/?#]/)[0]
|
||||
}
|
||||
if (answer.name) {
|
||||
this.info.display_name = answer.name
|
||||
} else {
|
||||
this.info.display_name = this.name
|
||||
}
|
||||
this.info.summary = answer.summary ? answer.summary : ""
|
||||
this.info.icon = answer.icon ? answer.icon.url : undefined
|
||||
this.info.type = answer.type
|
||||
this.urls.outbox = answer.outbox
|
||||
this.urls.inbox = answer.inbox
|
||||
this.urls.followers = answer.followers
|
||||
this.urls.following = answer.following
|
||||
if (answer.endpoints) {
|
||||
this.urls.oauth_token = answer.endpoints.oauthTokenEndpoint
|
||||
this.urls.oauth_registration = answer.endpoints.oauthRegistrationEndpoint
|
||||
this.urls.oauth_authorization = answer.endpoints.oauthAuthorizationEndpoint
|
||||
}
|
||||
this.valid = true
|
||||
callback(true, undefined)
|
||||
} else {
|
||||
callback(false, 'user profile: incorrect response from server')
|
||||
console.log(answer)
|
||||
}
|
||||
} else if (request.readyState == 4) {
|
||||
callback(false, 'user profile: server error')
|
||||
// Fetch the activity actor
|
||||
Fetcher.get(profile_url, undefined, function(load_ok, fetched_actor, failure_message) {
|
||||
if (load_ok) {
|
||||
this.data = fetched_actor
|
||||
// Fetch a few properties
|
||||
this.data.fetchAttributeList(
|
||||
['preferredUsername', 'name', 'summary', 'icon', 'endpoints'],
|
||||
undefined,
|
||||
function (ok, error) {
|
||||
if (ok) {
|
||||
if (!this.name) {
|
||||
this.name = this.data.preferredUsername
|
||||
}
|
||||
if (!this.server) {
|
||||
this.server = profile_url.replace('http://', '').replace('https://', '').split(/[/?#]/)[0]
|
||||
}
|
||||
this.valid = true
|
||||
callback(true, undefined)
|
||||
} else {
|
||||
callback(false, error)
|
||||
}
|
||||
}.bind(this))
|
||||
} else {
|
||||
callback(false, failure_message)
|
||||
}
|
||||
}.bind(this)
|
||||
request.open('GET', profile_url, true)
|
||||
request.setRequestHeader('Content-Type', 'application/activity+json')
|
||||
request.setRequestHeader('Accept', 'application/activity+json')
|
||||
request.send()
|
||||
}.bind(this))
|
||||
},
|
||||
// Fill the values of actor from fixed data
|
||||
// Usefull for displaying non-actors appearing in audience fields
|
||||
|
@ -150,8 +114,9 @@ Actor.prototype = {
|
|||
this.valid = true
|
||||
this.name = name
|
||||
this.server = server
|
||||
this.info.display_name = display_name
|
||||
this.urls.profile = url
|
||||
this.data = new ASActor()
|
||||
this.data.id = url
|
||||
this.data.name = display_name
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ const ConnectedUser = {
|
|||
callback(false, 'OAuth: server error when registering client.')
|
||||
}
|
||||
}
|
||||
request.open('POST', ConnectedUser.actor.urls.oauth_registration, true)
|
||||
request.open('POST', ConnectedUser.actor.data.endpoints.oauthRegistrationEndpoint, true)
|
||||
var data = new URLSearchParams()
|
||||
data.append('client_name', 'AP.Mail client')
|
||||
data.append('redirect_uris', 'urn:ietf:wg:oauth:2.0:oob')
|
||||
|
@ -129,7 +129,7 @@ const ConnectedUser = {
|
|||
callback(false, 'OAuth: cannot get user tokens')
|
||||
}
|
||||
}
|
||||
request.open('POST', ConnectedUser.actor.urls.oauth_token, true)
|
||||
request.open('POST', ConnectedUser.actor.data.endpoints.oauthTokenEndpoint, true)
|
||||
var data = new URLSearchParams()
|
||||
data.append('client_id', ConnectedUser.tokens.server.client_id)
|
||||
data.append('client_secret', ConnectedUser.tokens.server.client_secret)
|
||||
|
|
|
@ -11,10 +11,10 @@ const KnownActors = {
|
|||
set: function(profile, actor) {
|
||||
KnownActors.actors[profile] = actor
|
||||
// Make a false actor for followers
|
||||
if (actor.urls.followers) {
|
||||
if (actor.data.followers) {
|
||||
const followers = new Actor()
|
||||
followers.fromDummyData(actor.address(), 'followers', 'Followers of ' + actor.displayName(), actor.urls.followers)
|
||||
KnownActors.actors[actor.urls.followers] = followers;
|
||||
followers.fromDummyData(actor.address(), 'followers', 'Followers of ' + actor.displayName(), actor.data.followers)
|
||||
KnownActors.actors[actor.data.followers] = followers;
|
||||
}
|
||||
},
|
||||
// Retrieve an actor
|
||||
|
@ -37,7 +37,7 @@ const KnownActors = {
|
|||
// A representation of "everyone" in Actor class
|
||||
const publicActor = new Actor()
|
||||
publicActor.fromDummyData('', 'public', 'Anyone', 'https://www.w3.org/ns/activitystreams#Public')
|
||||
KnownActors.set(publicActor.urls.profile, publicActor)
|
||||
KnownActors.set(publicActor.data.id, publicActor)
|
||||
|
||||
// Exported structures
|
||||
exports.KnownActors = KnownActors
|
||||
|
|
Loading…
Reference in New Issue