@socialwebfoundation/ap-components
Version:
A collection of Web Components for displaying objects from the ActivityPub API
151 lines (131 loc) • 3.6 kB
JavaScript
import { LitElement, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
const AS2_NS = 'https://www.w3.org/ns/activitystreams#';
const AS2_PREFIX = 'as:';
function asMatch(obj, type) {
return obj.type === type ||
obj.type === `${AS2_PREFIX}${type}` ||
obj.type === `${AS2_NS}${type}`;
}
export class ActivityPubElement extends LitElement {
static styles = css`
:host {
--ap-min-height: 48px;
--ap-min-width: 48px;
}
`;
// Override fetchFunction to use a different fetch implementation
// For example, to use a custom fetch function that adds authentication headers
static fetchFunction = (url, options) => fetch(url, options);
static MEDIA_TYPES = [
'application/activity+json',
'application/ld+json',
'application/json'
];
static get properties() {
return {
objectId: { type: String, attribute: 'object-id' },
json: { type: Object, default: null },
_error: { type: String, state: true },
};
}
constructor() {
super();
}
connectedCallback() {
super.connectedCallback();
if (!this.json) {
if (this.objectId) {
this.fetchJson();
} else {
this._error = 'No object ID or JSON provided';
}
} else if (!this.objectId) {
this.objectId = this.json.id;
}
}
async fetchJson() {
try {
const fn = this.constructor.fetchFunction;
const headers = { Accept: this.constructor.MEDIA_TYPES.join(', ') };
const response = await fn(this.objectId, { headers });
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
const json = await response.json();
this.json = json;
} catch (error) {
this._error = error.message;
}
}
getId (ref) {
if (!ref) {
return null
} else if (typeof ref === 'string') {
return ref
} else if (typeof ref === 'object' && Array.isArray(ref)) {
return (ref.length > 0) ? this.getId(ref[0]) : null
} else if (typeof ref === 'object') {
return ref.id
}
}
getWebPage (ref) {
if (!ref) {
return null
} else if (typeof ref === 'string') {
return ref
} else if (Array.isArray(ref)) {
const webPages = ref.filter(link =>
typeof link === 'string' || (typeof link == 'object' && (!ref.mediaType || ref.mediaType === 'text/html'))
)
return (webPages.length > 0) ? this.getWebPage(webPages[0]) : null
} else if (typeof ref === 'object'
&& (!ref.mediaType || ref.mediaType === 'text/html')) {
return ref.href
} else {
return null
}
}
getIcon(json) {
if (!json) {
return null;
};
if (json.icon) {
if (typeof json.icon === 'string') {
return json.icon;
} else {
let iconObj;
if (Array.isArray(json.icon)) {
// TODO: pick best fit
iconObj = json.icon[0];
} else if (typeof json.icon === 'object') {
iconObj = json.icon;
}
if (iconObj) {
if (asMatch(iconObj, 'Image')) {
return iconObj.url;
} else if (asMatch(iconObj, 'Link')) {
return iconObj.href;
}
}
}
}
}
get icon() {
return this.getIcon(this.json);
}
get name() {
return this.json?.name;
}
get summary() {
return this.json?.summary;
}
get content() {
return this.json?.content;
}
get url() {
return this.getWebPage(this.json?.url);
}
get published() {
return this.json?.published;
}
}