How can I prevent TW from creating a tiddler based on the URL fragment (hash part)?

Hi,

I am setting up a Tiddlywiki instance for collaboration. Here is the current situation:

  • Single-file installation
  • Hosted on Microsoft Sharepoint (WebDAV)
  • I have added some code with a $:/tags/RawMarkup tiddler to use the Microsoft M365 (Entra) authentication and get the logged-in username from it, and automatically set $:/status/UserName.
  • I also added $:/status/* to the SaverFilter to mimic the behaviour of the SyncFilter and avoid saving the username.

(I know it is not secure/tamper-free since anyone can edit their UserName, but in the considered use case, it is fine.)

The last issue is: during the authentication, the browser goes to the identity provider (Microsoft) and then gets redirected to the wiki, with a fragment (#-part) in the URL, which holds an authentication code.

So, it works, but I end up with a draft tiddler full of gibberish in front of me.

Unfortunately, the library I use (Microsoft Graph Toolkit with MSAL2 provider) does not support setting the response_mode to form_post, for instance, which would avoid using the fragment.

I have a workaround which consists in using a popup for the authentication flow instead of redirections, but it is less user-friendly (some browsers block it, and I am worried it would not work in a Microsoft Teams tab, for instance).

How should I go if I want to adjust the behaviour in processing the fragment part of the URL? I looked at the $:/info/url/* tiddlers but they do not hold that part of the string unfortunately, they only store up to URL parameters (after the ? and before the #).

The ideal solution would be to be able to set a filter based on a regex for instance, so that I can ignore anything that looks like code=.+&state=.+ and preserve the otherwise functionality. But I have no idea where I could add that. I tried to add a hook on th-navigating but it does not seem to catch the draft creation.

Where would you look?

1 Like

Welcome @QTH to talk.tiddlywiki

I am not sure how to solve your problem (yet), but I have setup a single file wiki for multiple users on SharePoint for a client, the method I used was to leverage the SharePoint checkout mechanism. If they did not check it out first, they could not save changes.

  • I also made it open in a read only view with a method, for the official editors to switch it to edit mode, that when combined with checkout allowed them to make changes and save.
  • if the loading performance is poor I have some hacks that help.

I would be happy to help refine the methods using TiddlyWiki in SharePoint if you wish to collaborate, I have my own Office 365 tenancy.

Post script;
Can you share how to do this?

Then perhaps I would be in a position to replicate.

Also we could use a Zoom session to look at your installation and help you that way.

Sure, here you go: gist:2896b5bbd3d66402b8dbd1a5436bc5d9 · GitHub

I put this in a tiddler with the RawMarkup tag. You can change the LoginType.Redirect to LoginType.Popup for the workaround.

I would rather avoid the checkout mechanism. We are less than 5 users so the risk of race condition is quite low I would say.

I replied to you on GitHub.

maybe something like this?

/*\
title: overides/getLocationHash.js
type: application/javascript
module-type: utils

\*/
var bootfn=$tw.utils.getLocationHash;

function filterhash(hash) {  //filter goes in here
   if (hash=="#whatever") hash='#'; return hash; 
}

$tw.locationHash=filterhash($tw.locationHash);

exports.getLocationHash = function() {
   var hash = bootfn();
   hash = filterhash(hash);
   return hash;
	
};
1 Like

Yes I was looking at the source in boot.js and story.js and this looks like a possible way to do it.

I tried with a variation of that code you posted and the put it in a utils module. It appears in the Control Panel > Info > Loaded Modules page, but it seems to be totally ignored code-wise.

My guess is that:

  1. the code that does what we are looking for is using $tw.locationHash, not $tw.utils.getLocationHash. So it does not go through the filter.
  2. It happens before this override tiddler is loaded, so when we are changing $tw.locationHash, it’s already too late.

First, welcome to the community!

I don’t have time to try anything myself yet, but I was working in certain relevant code recently. I don’t know of any hooks to make this easier, but you should be able to modify this core code for your own purposes: core/modules/startup/story.

The trouble, of course, in modifying core code is that things are likely to break on upgrades. In fact, I believe the changes I was making are scheduled to be included in the next version, and they would definitely conflict with your changes. Nonetheless, it might be your best bet, and when you get it working we could later look at how to add a configuration mechanism for this behavior. Besides my work on simpler permalinks/permaviews, I have at least one other use for configurability here.

Hi QTH,

this line

$tw.locationHash=filterhash($tw.locationHash);

is to set the location hash at the begin of startup, when the utils module is instantiated. Note that the utils modules are loaded in

$:/core/modules/startup/load-modules.js

which is at the beginning of the startup chain and so this will take effect before the story is started up.

Alright. I read the code again and you seem to be right.

What I do not understand though is why the override is ignored. I added an alert() at the beginning of the source hoping to see it early, as soon as it is loaded, but it never comes up.

Any idea what could be wrong?

Here is what I did:

  • write the code in a tiddler
  • select application/javascript content type
  • add a module-type field with value utils:
/*\
title: $:/overrides/getLocationHash.js
type: application/javascript
module-type: utils

\*/
alert("getLocationHash override loading...");
var bootfn = $tw.utils.getLocationHash;
var auth_redirect_regex = /code=.+&state=.+/;

function filterhash(hash) {
	if hash.match(auth_redirect_regex) {
		alert("Auth redirect hash detected: " + hash);
		return '#';
	} else {
		alert("Returning unaltered hash: " + hash);
		return hash;
	}
}

$tw.locationHash = filterhash($tw.locationHash);

exports.getLocationHash = function() {
	alert("getLocationHash override called");
  var hash = bootfn();
	alert("pre-filter: "+hash);
  hash = filterhash(hash);
	alert("post-filter: "+hash);
  return hash;
};

The script does appear in the Control Panel view I mentioned in my previous post.

Thank you Scott. I was having a look at that part of the code too and I am able to patch the behaviour directly but, as you said, I would prefer to avoid this kind of direct patch to remain able to upgrade easily.

Maybe we could have a simple hook at that place, to make it easy to do whatever processing we want on the target string before it is actually used:

function openStartupTiddlers(options) {
	options = options || {};
	// Work out the target tiddler and the story filter. "null" means "unspecified"
	var target = null,
		storyFilter = null;
	if($tw.locationHash.length > 1) {
		var hash = $tw.locationHash.substr(1),
			split = hash.indexOf(":");
		if(split === -1) {
			target = $tw.utils.decodeURIComponentSafe(hash.trim());
		} else {
			target = $tw.utils.decodeURIComponentSafe(hash.substr(0,split).trim());
			storyFilter = $tw.utils.decodeURIComponentSafe(hash.substr(split + 1).trim());
		}
	}
	alert("Pre-hook target: "+target);
	target = $tw.hooks.invokeHook("th-setting-startup-target", target); // What I wish we had, and wish I could make work... (see below)
	const rx = /code=.+&state=.+&session_state=.+/; // The fragment format I want to ignore.
	if (rx.test(target)) { target = null; } // My current direct patch, which works fine.
	alert("Post-hook target: "+target);

The issue with the hook is: I do not know where to set it up! I tried using the piece of code I was referring to in my first post using the RawMarkup tag but I guess it is loaded too late.

The documentation actually warns about this here: https://tiddlywiki.com/dev/#HookMechanism in the Timing Registration paragraph but does not give any guidance or example on how to proceed the right way…

So, my questions to you would be:

  1. Where should I setup my hook so that it gets setup early enough?~
    → I created a startup module with exports.before=["story"] and it works!
  2. Should I propose this new hook invocation line in a GitHub PR?

Thanks for the discussion so far guys you are making this community very welcoming. :slight_smile:

Edit: @buggyj I looked at other examples and did some debugging on the snippet you shared. I ended up with this, which is working too, but I think the hook solution would be better, if it was merged in TW:

/*\
title: $:/overrides/getLocationHash.js
type: application/javascript
module-type: utils

Override the getLocationHash function to filter
unwanted garbage from authentication callbacks.

\*/

const bootfn = $tw.utils.getLocationHash;
const auth_redirect_regex = /code=.+&state=.+&session_state=.+/;

(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

// Export name and synchronous status
exports.name = "override-getLocationHash";

function filterhash(hash) {
	if (auth_redirect_regex.test(hash)) {
		return '#';
	} else {
		return hash;
	}
}

$tw.locationHash = filterhash($tw.locationHash);

exports.getLocationHash = function() {
  var hash = bootfn();
  console.log("override-getLocationHash.js - pre-filter: "+hash);
  hash = filterhash(hash);
  console.log("override-getLocationHash.js - post-filter: "+hash);
  return hash;
};

})();

I have an extremely busy week, and am not sure when I will get enough time to look at this more closely. But I would like to point out that when I used “hook”, I meant something more generic than TW’s hooks mechanism (which I’d forgotten even existed!) I simply meant that TW would offer a way to customize behavior at a certain point in its lifecycle. If that is hooks, great, but it doesn’t have to be.

I would want a slightly more generic hook than I think you are looking for. I would like to be able to replace the hash -> {target, storyFilter} logic, possibly completely on my own, and possibly by doing a transformation of the hash and calling the current logic. That would still let your version work, just by removing any part of or all of the hash if it meets your regex, and then calling the existing logic. But I haven’t thought through any details or implications of that idea.

I am happy with anything more generic as long as it covers my specific use case. :smiley:

For the moment I can live with the solution pointed to by @buggyj so I opened a discussion on github on this topic to go forward on how we could allow a more elegant implementation by improving TW: Add a hook to allow filtering of the target in URL before displaying the story · Jermolene/TiddlyWiki5 · Discussion #8165 · GitHub

If you look at startup/story.js you will see that once the target is identified, it gets the storyList, invokes a hook, and then adds the target to the storyList. So we could have a generic solution by moving the hook invocation after we insert the target tiddler title in the storyList.

But we might need to adapt the following bits of the code to make sure what gets logged in the History also takes into account what has been modified by the hooks. In the current state, it would not.