rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Arbitrary Query Params

Open NullVoxPopuli opened this issue 5 years ago • 10 comments

rendered

Side-effecting goals by not defaulting to sticky params:

  • LinkTo / navigations can remove many query params all at once (currently this is only possible by specifying every query param in your link-to and setting to default value)
  • ...

Example of what happens today when the queryParams array is omitted: secret-query-params (Twiddle is Ember 3.18)

NullVoxPopuli avatar Jan 30 '21 18:01 NullVoxPopuli

How about sticky query params? Wouldn't exist in this mode, I assume, but what would a user-space implementation look like?

Same question for "default query params".

When doing refresh on a route, would the query params stay around? (and would the route model hook receive them)

sandstrom avatar Feb 18 '21 06:02 sandstrom

How about sticky query params? Wouldn't exist in this mode, I assume, but what would a user-space implementation look like?

Correct!

I'm thinking something like this:

// this could also live on some service
const stickyQPs = {};

export default MyRoute extends Route {
  @service router;
  
  async beforeModel({ to: { queryParams }}) {
    if (!stickyQPs.category) {
      stickyQPs.category = queryParams.category;
    }

    if (!queryParams.category) {
      this.router.transitionTo({ queryParams: { category: 'default value' }});
    }
  }

Same question for "default query params".

export default MyRoute extends Route {
  @service router;
  
  async beforeModel({ to: { queryParams }}) {
    if (!queryParams.category) {
      this.router.transitionTo({ queryParams: { category: 'default value' }});
    }
  }

When doing refresh on a route, would the query params stay around? (and would the route model hook receive them)

yup, the transition.to object has the intended query params on the URL -- as long as a transition away from those query params hasn't happened, they'll be available in the route model hooks


I should add these examples to here: https://github.com/emberjs/rfcs/pull/712

NullVoxPopuli avatar Feb 18 '21 23:02 NullVoxPopuli

Some followup questions:

With sticky, how would that work with links? A {{link-to wouldn't know that the remembered/sticky query param for route /users is ?sort=last-name. Basically, you couldn't render <a href="/users?sort=last-name">Users</a> since you won't know the sticky query param value before hand. I think that's a fairly big drawback.

With default query params, you'd get the link <a href='/stores'>View stores</a> but navigating into it would yield /stores?filtering=active. With current default query params behavior you'd get /stores but the query param property filtering would return 'active' as its default value (which would not be rendered to the url). I think this is less of a problem though, since it could be handled via e.g. a getter that converted null/undefined into the string 'active', such that the difference [between your proposal and existing QPs] would be invisible to the rest of the controller/route.

Also, triggering this.router.transitionTo in beforeModel hooks works, but behavior that rely on transitions to indicate some type of state change (analytics software, etc) would get confused. Seems like we're hijacking a method that's really meant for one thing (state changes) and using it fore something else (setting default query params or handling sticky query params). I'm not convinced that's a good idea.

I don't really know what I think of the overall proposal (I'm neither for nor against yet), but wanted to highlight some possible problems.

I agree with you that decoupling query params from controllers would be great. It's awesome that we're thinking about different ways of getting rid of them.

sandstrom avatar Feb 19 '21 09:02 sandstrom

With sticky, how would that work with links?

I think you'd want to make your own Link component and store the sticky data in a service and have your custom link component reference that service.

With current default query params behavior you'd get /stores but the query param property filtering would return 'active' as its default value (which would not be rendered to the url). I think this is less of a problem though, since it could be handled via e.g. a getter that converted null/undefined into the string 'active', such that the difference [between your proposal and existing QPs] would be invisible to the rest of the controller/route.

yeah, I don't think we should ever have QP state that isn't represented in the URL, so what you propose is totally valid, and I think the behavior absolutely should be left up to folks to decide for themselves in their apps :D

Seems like we're hijacking a method that's really meant for one thing (state changes) and using it fore something else (setting default query params or handling sticky query params)

the URL, including query params, are the state though -- but that's just a thing with my proposed whipped up implementation. If someone didn't want to transition, that's fine, too.

However, I agree with you that decoupling query params from controllers would be great. It's awesome that we're thinking about different ways of getting rid of them.

Yeah, this is really the last thing we need to stop using controllers, allowing for better composition patterns (I know this is subjective, but we can talk about that on Discord :p ). I don't think we need to provide a built-in solution for all the behavior today, and I think that scaling back the feature set, behind a flag / optional feature, will allow more flexibility for future iterations -- but in app/addon space.

NullVoxPopuli avatar Feb 19 '21 15:02 NullVoxPopuli

Coming from a more recent Ruby on Rails background, this change makes sense to me. You have access to all params on a Rails controller, which is usually quite useful.

However, I would would also like to ask/suggest if we could find a corresponding to Rail’s strong parameters (either by building something into the framework or suggest a good example of how to deny/allowlist parameters). Maybe together with https://github.com/emberjs/rfcs/pull/712, we could provide a documentation example of how to derive safe parameters (i.e. filtering out random parameters that we don’t want to pass on), something like this:

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

export default class MyDeeplyNestedComponent extends Component {
  @service router;

  get params() {
    const { page, sort, category } = this.router.currentRoute.queryParams;

    return { page, sort, category };
  }
}

gnclmorais avatar Apr 03 '21 21:04 gnclmorais

@gnclmorais the threat model for a single page app is somewhat different from Rails, so I don't know if something like strong params would necessarily make sense here. Common vectors are XSS, CSRF and a few others.

Sure, one can certainly make up a scenario where non-sanitized urls could allow an attacker to e.g. trick another user into doing something harmful, but params are overall much less sensitive on the front-end.

sandstrom avatar Apr 04 '21 17:04 sandstrom

You’re right @sandstrom, it’s not the same on the frontend side — I was providing a parallel with Rails mostly as a documentation example, a way we can put this PR and https://github.com/emberjs/rfcs/pull/712 together to provide more clarity.

Removing the queryParams = ['category'] bit will, in a way, remove documentation from the framework — they become less explicit in showing “these are the params we accept”, so the snippet I provided looked like a clean, self-documenting way of saying “from all the params we can get, these are the ones we want to handle”.

gnclmorais avatar Apr 05 '21 08:04 gnclmorais

@gnclmorais I agree regarding documentation, self-documenting code would definitely be useful + agree that whitelisting params is a good idea (but not primarily for security).

sandstrom avatar Apr 06 '21 08:04 sandstrom

@NullVoxPopuli where does this stand?

wagenet avatar Jul 23 '22 23:07 wagenet

It's something I think we absolutely need as a framework, but the timing of the concept of arbitrary query params may fit better with whatever overhauls the routing system may get with/post polaris. Would be good to talk to / hear from core about what's planned around that.

My primary belief around QPs is that opt-in by default is backwards, and this RFC reverses that.

NullVoxPopuli avatar Jul 23 '22 23:07 NullVoxPopuli

Thanks for spending the time writing this RFC! Given the significant number of concerns around routing, we’re going to be completely revamping the router as part of Polaris. This RFC won’t directly apply to the new router so we’re going to move this PR to FCP to Close. But don’t worry, your work here was not in vain! We will be keeping track of this RFC and others like it to make sure that the concerns it aims to address will be covered in the new router. Your work here does a significant service to us in this regard!

wagenet avatar Dec 06 '22 23:12 wagenet