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.
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.
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:
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.
It happens before this override tiddler is loaded, so when we are changing $tw.locationHash, it’s already too late.
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.
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.
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:
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!
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.
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.
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.