function make_trackable(url, callback, search_params_callback, allow_hash = false) {
	const tracked = new URL(url);

	Object.defineProperty(tracked, 'searchParams', {
		value: new Proxy(tracked.searchParams, {
			get(obj, key) {
				if (key === 'get' || key === 'getAll' || key === 'has') {
					return (/**@type {string}*/ param) => {
						search_params_callback(param);
						return obj[key](param);
					};
				}

				// if they try to access something different from what is in `tracked_search_params_properties`
				// we track the whole url (entries, values, keys etc)
				callback();

				const value = Reflect.get(obj, key);
				return typeof value === 'function' ? value.bind(obj) : value;
			}
		}),
		enumerable: true,
		configurable: true
	});

	/**
	 * URL properties that could change during the lifetime of the page,
	 * which excludes things like `origin`
	 */
	const tracked_url_properties = ['href', 'pathname', 'search', 'toString', 'toJSON'];
	if (allow_hash) tracked_url_properties.push('hash');

	for (const property of tracked_url_properties) {
		Object.defineProperty(tracked, property, {
			get() {
				callback();
				// @ts-expect-error
				return url[property];
			},

			enumerable: true,
			configurable: true
		});
	}

	if (!BROWSER) {
		// @ts-ignore
		tracked[Symbol.for('nodejs.util.inspect.custom')] = (depth, opts, inspect) => {
			return inspect(url, opts);
		};

		// @ts-ignore
		tracked.searchParams[Symbol.for('nodejs.util.inspect.custom')] = (depth, opts, inspect) => {
			return inspect(url.searchParams, opts);
		};
	}

	if ((DEV || !BROWSER) && !allow_hash) {
		disable_hash(tracked);
	}

	return tracked;
}
@param {URL} url
@param {() => void} callback
@param {(search_param: string) => void} search_params_callback
@param {boolean} [allow_hash]