-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better code ergonomics for URL rewriting use cases #5
Comments
I do think that the destinationEntry that's passed into the navigate event should NOT be the same Entry that you would read from currentEntry because in some ways it's prospective - it hasn't yet been serialized to history, and specifically if you do: appHistory.addEventListener("navigate", e => {
console.log(e.destinationEntry === appHistory.currentEntry);
}); this should be false - and I would expect it to be false. So I like the approach of passing a separate instanceof Object in destinationEntry, and I like the idea of making it mutable. But I think that the promise you pass to respondWith should also be able to resolve to that entry, or should be the entry itself. So in total: appHistory.addEventListener("navigate", e => {
const modifiedEntry = e.destinationEntry;
const url = new URL(modifiedEntry.url);
if (url.hostname === "store.example.com") {
url.queryParams.set("affiliateId", "ead21623-781e-442f-a2c4-6cc1b2a9fda2");
modifiedEntry.url = url.toString();
e.respondWith(Promise.resolve(modifiedEntry));
}
}); |
(Note: I recently renamed
Yep, that's already the plan. It's definitely a different object. The question is, whether it should be a different class. Right now
Hmm, this seems like it's mixing two paradigms to me. If you're already doing Similarly, if you need to do some async work to figure out the new URL, the mutable e.respondWith((async () => {
const stuff = await doStuff();
e.destination.url = stuff;
})()); |
It's just for me a question of the timing. If we say, for instance: appHistory.addEventListener("navigate", e => {
e.destination.url = ...;
e.respondWith((async () => {
await new Promise(resolve => setTimeout(resolve, 10));
e.destination.url = ...;
await new Promise(resolve => setTimeout(resolve, 10));
e.destination.url = ...;
await new Promise(resolve => setTimeout(resolve, 10));
e.destination.url = ...;
})());
}); Would you expect the URL to update on each new assignment to |
Great point. I don't like the look of that; causing many URL updates per navigation is tricky. So that inclines me more toward "expand respondWith". @annevk also was somewhat in favor of that alternative when we discussed offline. I do want to figure out #19 first since that impacts exactly when the URL updates. I'll come back to this after we have a model there... |
After #56 gave us more clarity on when Before we come up with a new solution, we need to clarify, in a similar style to #56, exactly when this kind of URL/state modification is allowed. Here's a first pass: it's allowed for
This is very similar to when With this on hand, here's a new menu of solutions: A new event with mutable
|
Also my favorite, except I think the name |
Redirect would be so good but doesn't make sense for the state-change-only case IMO. Modify is not bad. |
One thing that will likely help in clarifying this is to have a clear description of the primitives you end up exposing here. I guess the distinction you are drawing above is between "normal" navigation and "history" navigation. And then there's a difference between that being same origin and cross origin (though this only matters for "normal" as "history" is by definition same-origin as far as this API is concerned?). It seems to me that At least, from a high-level there's a set of primitive operations that you allow to be intercepted and responded to, but not all responses are acceptable everywhere. Perhaps these sets are completely non-overlapping and therefore two different APIs is better, but it's not clear that is the case? |
Still haven't processed all the comments and thoughts here yet, but some discussion with @csreis pointed out that the explainer's current solution of |
Right, I would expect a similar API to the fetch event in service workers. You get a NavigationRequest and you can create a new one to respond with. And then there's various policies about what is acceptable to read from these and what you get to set when creating a new one, to match security expectations. |
(Moved from slightlyoff/history_api#23 by @SetTrend, with me synthesizing and summarizing.)
This example, which was formerly in the readme, is an instance of a somewhat-common pattern, of wanting to do slight tweaks to the URL before navigating. I'll quote it here in full (translated to the
window.navigation
API):However, as noted there,
To expand on this, code like the above will generate a second
navigate
event. Which, actually, this code doesn't guard against: so as written this will cause an infinite loop. Oh dear.I also suspect that sometimes you want to do something like this, but with state, not URLs. For example, something like
This example, like the previous one, is unfortunate; canceling the navigation (with
e.preventDefault()
) then starting a new one feels wrong for this sort of minor tweak.The question is just what syntax would be better. Here are some ideas we've seen so far:
Expand
transitionWhile()
Right now
transitionWhile()
takes a promise for nothing; the browser is only interested in how long the promise takes to settle, and whether it fulfills or rejects. We could expand it to take a promise for{ url, state }
, so that you'd write the above examples asThis would also allow asynchronous calculation of the new URL and state, which I could imagine being useful.
Make
destination
mutableThis ties into larger questions about mutability (#7), so it could come in a few variants. But a simple version is to just have
NavigationDestination
'surl
andstate
properties be mutable. Then the above examples would becomeIntroduce a new
beforenavigate
event where these things are mutableHere the idea is to separate navigations into two phases: preparing for the navigation, where you might want to tweak the URL and state; and processing the navigation, where you don't want to change those but instead you simply want to act on it.
This could look a lot like "Make
destination
mutable", or maybe we'd put theurl
andstate
properties directly on theevent
object.This has a lot of consequences though that'd need to be thought through. Like, does
beforenavigate
get the same information asnavigate
? And, we'd need to reevaluate each of ournavigate
examples to figure out whether they'd be better off usingbeforenavigate
. I'm not sure there would always be a right answer, which would make this API hard to use.A tricky thing to consider is what happens when such mutations change the "type" of the navigation. The affiliate links example takes a cross-document cross-origin navigation, and just produces a slightly-different cross-document cross-origin navigation. But, what about a case like this?
Here we're starting with a same-origin same-document navigation. But the
navigate
handler wants to update it to a cross-origin navigation, and cross-origin navigations must be cross-document. (You can't have yourDocument
object claiming to be someone else's origin!)We could probably just throw in such cases, I suppose. You could always do a proper redirect in your
navigate
handler, i.e. canceling the current navigation and starting a new one vialocation.href
, if you wanted this.The reverse version:
does not have this problem; it transforms a cross-document cross-origin navigation into a cross-document same-origin navigation, which is fine. (It's just like link-rewriting.) But, we could also throw on that sort of thing for simplicity...
The text was updated successfully, but these errors were encountered: