How can I add request headers to external image requests?

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: ![](https://...).

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.