I’m developing a WebDAV-based plugin that uploads image files to a WebDAV server when they are dragged or pasted into a note (similar to image-auto-upload-plugin, but without requiring an additional PicGo
). After uploading, the plugin inserts a link to the image in the note like: 
.
Most of the features are working great, but there’s an issue when fetching the images: the WebDAV server requires authentication via the Authorization
header, otherwise it returns a 401 error. However, it seems I’m unable to add custom headers to the image request.
I could configure the WebDAV server to allow all access to these images, but I’m concerned about privacy. I also tried appending a token to the URL (e.g., ?token=...
) to handle authentication, but I’d prefer not to pollute the URLs if possible.
Is there a better solution to this problem?
Putting the secret key into the main text and copying multiple copies may not be a good idea?
I think the easiest is to set up a reverse proxy that redirects your image requests to the appropriate servers with proper authorization
Yeah, I had the same idea. But actually, I wrote this plugin in order to achieve something like image-auto-upload
without running any additional programs. If I need to run a local proxy service, then it’s essentially no different from running PicGo
.
So I tried using Electron’s WebRequestFilter to proxy obsidian’s request, but I don’t know why it didn’t work, the callback function I set was just never triggered when a request was send.
I implemented an alternative approach. In short, before an image is rendered, retrieve src
, manually fetch
the image(with headers), create a Blob URL
, and then set it back to the src
. I also added some lazy loading logic:
type WebdavSettings = {
url: string;
username: string;
password: string;
};
// // Fetch image via webdav basic authentication
class WebdavLoader implements PluginValue {
settings: WebdavSettings;
intersectionObserver: IntersectionObserver;
mutationObserver: MutationObserver;
// key: <img>
// value: blob url
blobs: Map<HTMLImageElement, string>;
constructor(view: EditorView, settings: WebdavSettings) {
this.settings = settings;
this.blobs = new Map();
this.initIntersectionObserver();
this.initMutationObserver(view);
}
initMutationObserver(view: EditorView) {
this.mutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type !== "childList") {
continue;
}
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLImageElement) {
this.observeImage(node);
}
});
}
});
this.mutationObserver.observe(view.dom, {
childList: true,
subtree: true,
});
}
initIntersectionObserver() {
this.intersectionObserver = new IntersectionObserver(
async (entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) {
continue;
}
const el = entry.target as HTMLImageElement;
if (this.blobs.has(el)) {
continue;
}
try {
await this.loadImage(el);
} catch (e) {
console.error(`Error loading file: ${e}`);
}
this.intersectionObserver.unobserve(el);
}
}
);
}
destroy() {
this.intersectionObserver.disconnect();
this.mutationObserver.disconnect();
// free memory
for (const url of this.blobs.values()) {
URL.revokeObjectURL(url);
}
this.blobs.clear();
}
observeImage(el: HTMLImageElement) {
if (this.blobs.has(el)) {
return;
}
// only handle images from the WebDAV URL
if (!el.src.startsWith(this.settings.url)) {
return;
}
// lazy loading
el.setAttribute("data-src", el.src);
el.src = "";
this.intersectionObserver.observe(el);
}
getToken() {
const { username, password } = this.settings;
return Buffer.from(`${username}:${password}`).toString("base64");
}
async loadImage(el: HTMLImageElement) {
let url = el.getAttribute("data-src");
if (url == null) {
return;
}
const token = this.getToken();
const resp = await fetch(url, {
method: "GET",
headers: { Authorization: `Basic ${token}` },
});
const blob = await resp.blob();
el.src = URL.createObjectURL(blob);
this.blobs.set(el, url);
}
}
export const createWebdavLoader = (settings: WebdavSettings) => {
return ViewPlugin.define((view) => {
return new WebdavLoader(view, settings);
});
};
It can load images correctly and is cache-friendly(probably). But I’m not very familiar with javascript, are there any potential performance issues with this approach, or is there a better alternative?
A few days passed, I’ve already submit my plugin on Github, and take a pull request to the official repo, but I’m still not sure if there’s a better way.