Cache own Nostr avatar image
This commit is contained in:
@@ -8,6 +8,7 @@ import UserMenu from '#components/user-menu';
|
|||||||
import SearchBox from '#components/search-box';
|
import SearchBox from '#components/search-box';
|
||||||
import CategoryChips from '#components/category-chips';
|
import CategoryChips from '#components/category-chips';
|
||||||
import { and } from 'ember-truth-helpers';
|
import { and } from 'ember-truth-helpers';
|
||||||
|
import cachedImage from '../modifiers/cached-image';
|
||||||
|
|
||||||
export default class AppHeaderComponent extends Component {
|
export default class AppHeaderComponent extends Component {
|
||||||
@service storage;
|
@service storage;
|
||||||
@@ -71,7 +72,7 @@ export default class AppHeaderComponent extends Component {
|
|||||||
(and this.nostrAuth.isConnected this.nostrData.profile.picture)
|
(and this.nostrAuth.isConnected this.nostrData.profile.picture)
|
||||||
}}
|
}}
|
||||||
<img
|
<img
|
||||||
src={{this.nostrData.profile.picture}}
|
{{cachedImage this.nostrData.profile.picture}}
|
||||||
class="user-avatar"
|
class="user-avatar"
|
||||||
alt="User Avatar"
|
alt="User Avatar"
|
||||||
/>
|
/>
|
||||||
|
|||||||
64
app/modifiers/cached-image.js
Normal file
64
app/modifiers/cached-image.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { modifier } from 'ember-modifier';
|
||||||
|
|
||||||
|
const CACHE_NAME = 'nostr-image-cache-v1';
|
||||||
|
|
||||||
|
export default modifier((element, [url]) => {
|
||||||
|
let objectUrl = null;
|
||||||
|
|
||||||
|
async function loadImage() {
|
||||||
|
if (!url) {
|
||||||
|
element.src = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cache = await caches.open(CACHE_NAME);
|
||||||
|
const cachedResponse = await cache.match(url);
|
||||||
|
|
||||||
|
if (cachedResponse) {
|
||||||
|
const blob = await cachedResponse.blob();
|
||||||
|
objectUrl = URL.createObjectURL(blob);
|
||||||
|
element.src = objectUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in cache, try to fetch it
|
||||||
|
// eslint-disable-next-line warp-drive/no-external-request-patterns
|
||||||
|
const response = await fetch(url, {
|
||||||
|
mode: 'cors', // Required to read the blob for caching
|
||||||
|
credentials: 'omit',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Clone the response before reading the blob because a response stream can only be read once
|
||||||
|
const cacheResponse = response.clone();
|
||||||
|
await cache.put(url, cacheResponse);
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
objectUrl = URL.createObjectURL(blob);
|
||||||
|
element.src = objectUrl;
|
||||||
|
} else {
|
||||||
|
// Fetch failed (e.g. 404), fallback to standard browser loading
|
||||||
|
element.src = url;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// CORS errors or network failures will land here.
|
||||||
|
// Fallback to letting the browser handle it directly.
|
||||||
|
console.warn(
|
||||||
|
`Failed to cache image ${url}, falling back to standard src`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
element.src = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage();
|
||||||
|
|
||||||
|
// Cleanup: revoke the object URL when the element is destroyed or the URL changes
|
||||||
|
return () => {
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
objectUrl = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user