Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add p2p URL support across all content in the reader #20

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions actor-mini-profile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { db } from './dbInstance.js'
import { resolveP2PUrl } from './db.js'

class ActorMiniProfile extends HTMLElement {
static get observedAttributes () {
Expand Down Expand Up @@ -49,11 +48,10 @@ class ActorMiniProfile extends HTMLElement {
}

// Actor icon
const img = document.createElement('img')
img.className = 'profile-mini-icon'
img.src = resolveP2PUrl(iconUrl)
img.alt = actorInfo.name ? actorInfo.name : 'Actor icon'
clickableContainer.appendChild(img)
const p2pImage = document.createElement('p2p-image')
p2pImage.className = 'profile-mini-icon'
p2pImage.alt = actorInfo.name ? actorInfo.name : 'Actor icon'
clickableContainer.appendChild(p2pImage)

// Actor name
if (actorInfo.name) {
Expand Down
11 changes: 5 additions & 6 deletions actor-profile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { db } from './dbInstance.js'
import { resolveP2PUrl } from './db.js'

class ActorProfile extends HTMLElement {
static get observedAttributes () {
Expand Down Expand Up @@ -70,11 +69,11 @@ class ActorProfile extends HTMLElement {
}
}

const img = document.createElement('img')
img.classList.add('profile-icon')
img.src = resolveP2PUrl(iconUrl)
img.alt = actorInfo.name ? actorInfo.name : 'Actor icon'
actorContainer.appendChild(img) // Append to the actor container
const p2pImage = document.createElement('p2p-image')
p2pImage.setAttribute('src', iconUrl)
p2pImage.classList.add('profile-icon')
p2pImage.alt = actorInfo.name ? actorInfo.name : 'Actor icon'
actorContainer.appendChild(p2pImage) // Append to the actor container

if (actorInfo.name) {
const pName = document.createElement('div')
Expand Down
11 changes: 10 additions & 1 deletion db.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ export function isP2P (url) {
return url.startsWith(HYPER_PREFIX) || url.startsWith(IPNS_PREFIX)
}

export async function supportsP2P (url) {
try {
const response = await fetch(url)
return response.ok
} catch (error) {
console.log('P2P URL loading failed:', error)
return false
}
}

export function resolveP2PUrl (url) {
if (!url) return url

Expand Down Expand Up @@ -115,7 +125,6 @@ export class ActivityPubDB extends EventTarget {
if (url && typeof url === 'object') {
return url
}
url = resolveP2PUrl(url)

let response
// Try fetching directly for all URLs (including P2P URLs)
Expand Down
1 change: 1 addition & 0 deletions followed-accounts.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@
<script type="module" src="./sidebar.js"></script>
<script type="module" src="./followed-accounts.js"></script>
<script type="module" src="./actor-mini-profile.js"></script>
<script type="module" src="./p2p-media.js"></script>
<script type="module" src="./theme-selector.js"></script>
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<script type="module" src="./timeline.js"></script>
<script type="module" src="./outbox.js"></script>
<script type="module" src="./post.js"></script>
<script type="module" src="./p2p-media.js"></script>
<script type="module" src="./followed-accounts.js"></script>
<script type="module" src="./theme-selector.js"></script>
<script type="module" src="./error-message.js"></script>
102 changes: 102 additions & 0 deletions p2p-media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { supportsP2P, resolveP2PUrl } from './db.js'

class P2PImage extends HTMLElement {
constructor () {
super()

this.img = document.createElement('img')
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(this.img)
this.img.addEventListener('error', () => this.handleError())
}

static get observedAttributes () {
return ['src']
}

connectedCallback () {
if (this.hasAttribute('src')) {
this.loadImage(this.getAttribute('src'))
}
}

attributeChangedCallback (name, oldValue, newValue) {
if (name === 'src' && newValue !== oldValue) {
this.loadImage(newValue)
}
}

async loadImage (src) {
try {
const p2pSupported = await supportsP2P(src)
if (p2pSupported) {
this.img.src = src // Attempt to load the original P2P URL
} else {
this.handleError()
}
} catch (error) {
this.handleError()
}
}

async handleError () {
const fallbackSrc = resolveP2PUrl(this.getAttribute('src'))
console.log(`Failed to load image. Resolving to gateway URL: ${fallbackSrc}`)
this.img.src = fallbackSrc
}
}

customElements.define('p2p-image', P2PImage)

class P2PVideo extends HTMLElement {
constructor () {
super()
this.video = document.createElement('video')
this.video.controls = true
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(this.video)
}

static get observedAttributes () {
return ['src']
}

connectedCallback () {
if (this.hasAttribute('src')) {
this.loadVideo(this.getAttribute('src'))
}
}

attributeChangedCallback (name, oldValue, newValue) {
if (name === 'src' && newValue !== oldValue) {
this.loadVideo(newValue)
}
}

async loadVideo (src) {
this.video.innerHTML = '' // Clear any existing sources
const source = document.createElement('source')
source.src = src

try {
const p2pSupported = await supportsP2P(src)
if (!p2pSupported) {
throw new Error('P2P not supported for this URL')
}
} catch (error) {
this.handleError(source)
return // Skip setting the source if not supported
}

source.onerror = () => this.handleError(source)
this.video.appendChild(source)
}

handleError (source) {
const fallbackSrc = resolveP2PUrl(source.src)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, check if the current img.src is p2p

console.log(`Failed to load video source. Resolving to gateway URL: ${fallbackSrc}`)
source.src = fallbackSrc
}
}

customElements.define('p2p-video', P2PVideo)
1 change: 1 addition & 0 deletions post.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<script type="module" src="./sidebar.js"></script>
<script type="module" src="./timeline.js"></script>
<script type="module" src="./post.js"></script>
<script type="module" src="./p2p-media.js"></script>
<script type="module" src="./followed-accounts.js"></script>
<script type="module" src="./theme-selector.js"></script>
<script type="module" src="./error-message.js"></script>
60 changes: 28 additions & 32 deletions post.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,42 +44,39 @@ function insertImagesAndVideos (content) {
const parser = new DOMParser()
const contentDOM = parser.parseFromString(content, 'text/html')

// Replace all <img> tags with <p2p-image> tags
contentDOM.querySelectorAll('img').forEach(img => {
const originalSrc = img.getAttribute('src')
const p2pSrc = resolveP2PUrl(originalSrc)

img.onerror = () => {
console.log(`Failed to load image at ${originalSrc}. Attempting to resolve...`)
// Fallback to gateway URL only if it's a P2P URL and not already using a gateway
if (isP2P(originalSrc) && !originalSrc.includes('hypha.coop')) {
const fallbackSrc = resolveP2PUrl(originalSrc)
console.log(`Resolving to gateway URL due to error: ${fallbackSrc}`)
img.setAttribute('src', fallbackSrc)
}
}

img.setAttribute('src', p2pSrc)
console.log(`Set image src to: ${p2pSrc}`)
console.log(`Original img src: ${originalSrc}`)
const p2pImg = document.createElement('p2p-image')
p2pImg.setAttribute('src', originalSrc)
img.parentNode.replaceChild(p2pImg, img)
console.log(`Replaced img with p2p-image having src: ${p2pImg.getAttribute('src')}`)
})

contentDOM.querySelectorAll('video source').forEach(video => {
const originalSrc = video.getAttribute('src')
const p2pSrc = resolveP2PUrl(originalSrc)

video.onerror = () => {
console.log(`Failed to load video at ${originalSrc}. Attempting to resolve...`)
if (isP2P(originalSrc) && !originalSrc.includes('hypha.coop')) {
const fallbackSrc = resolveP2PUrl(originalSrc)
console.log(`Resolving to gateway URL due to error: ${fallbackSrc}`)
video.setAttribute('src', fallbackSrc)
}
// Replace all <video> tags with <p2p-video> tags
contentDOM.querySelectorAll('video').forEach(video => {
const p2pVideo = document.createElement('p2p-video')
if (video.hasAttribute('src')) {
const originalSrc = video.getAttribute('src')
p2pVideo.setAttribute('src', originalSrc)
}

video.setAttribute('src', p2pSrc)
console.log(`Set video src to: ${p2pSrc}`)
Array.from(video.children).forEach(source => {
if (source.tagName === 'SOURCE') {
const originalSrc = source.getAttribute('src')
console.log(`Original video src: ${originalSrc}`)
const srcType = source.getAttribute('type')
const p2pSource = document.createElement('source')
p2pSource.setAttribute('src', originalSrc)
p2pSource.setAttribute('type', srcType)
p2pVideo.appendChild(p2pSource)
console.log(`Replaced video with p2p-video having src: ${p2pSource.getAttribute('src')}`)
}
})
video.parentNode.replaceChild(p2pVideo, video)
})

return contentDOM.body.innerHTML // Return the modified HTML to be inserted
return contentDOM.body.innerHTML
}

// Define a class for the <distributed-post> web component
Expand All @@ -97,15 +94,14 @@ class DistributedPost extends HTMLElement {
this.renderErrorContent('No post URL provided')
return
}
postUrl = await resolveP2PUrl(postUrl)

try {
const content = await db.getNote(postUrl)
if (content && content.content) {
content.content = insertImagesAndVideos(content.content) // Resolve URLs before rendering
// Assuming JSON-LD content has a "summary" field
this.renderPostContent(content)
}
// Assuming JSON-LD content has a "summary" field
this.renderPostContent(content)
} catch (error) {
console.error(error)
this.renderErrorContent(error.message)
Expand Down
1 change: 1 addition & 0 deletions profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<script type="module" src="./sidebar.js"></script>
<script type="module" src="./actor-profile.js"></script>
<script type="module" src="./post.js"></script>
<script type="module" src="./p2p-media.js"></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have post.js import any of it's dependencies directly instead of doing it top level.

Similarly outbox should import post.

<script type="module" src="./outbox.js"></script>
<script type="module" src="./followed-accounts.js"></script>
<script type="module" src="./theme-selector.js"></script>
Expand Down