Plugin with catch-all wildcard route map

AI-generated summary

The discussion revolves around creating a catch-all wildcard route map for a Discourse plugin. kleinfreund attempted to achieve this using various methods, but was unsuccessful due to Discourse’s existing catch-all route taking precedence.

kleinfreund explained that the goal is to handle unknown routes defined by plugin users, allowing arbitrary web applications to be served within a Discourse instance. The plugin needs to catch all requests not handled by Discourse and forward them to another URL.

eviltrout suggested that defining all routes might not be feasible, and pfaffman proposed using a single route with dynamic segments. However, kleinfreund explained that the paths are unknown before runtime and cannot be defined beforehand.

A possible workaround was found by kleinfreund, involving creating separate routes for single-segment and multi-segment paths, and exporting the main route implementation to both. However, kleinfreund asked if Discourse could provide a more straightforward way for plugins to accomplish this without a workaround. pfaffman suggested using a permalink redirect as an alternative solution.

Discourse has a catch-all route using a wildcard path.

app/assets/javascripts/discourse/mapping-router.js.es6:

this.route("unknown", { path: "*path" });

As far as I can tell, this stops plugins from having the same catch-all rule because Discourse’s rule is executed later, thus taking precedence. Atleast when commenting out Discourse’s rule temporarily, the following works as expected:

plugins/plugin/assets/javascripts/discourse/plugin-name-route-map.js.es6:

this.route('main-route', { path: '*wildcard' });

Is there a way to have a catch-all route for a plugin with only one route?


Attempt 1:

Nested routes.

plugins/plugin/assets/javascripts/discourse/plugin-name-route-map.js.es6:

export default function () {
  // Matches single-segment paths.
  this.route('main-route', { path: '/:path' }, function () {
    // Optionally, matches subsequent segments.
    this.route('sub-route', { path: '*wildcard_path' });
  });
}

This has the major issue that requests matching /:path will use the route main-route.index whereas requests matching /:path/*wildcard will use the route main-route.sub-route. I need all requests to go through main-route.index.


Attempt 2:

Multiple paths, same routes.

(This seems to have worked in older Ember version, but does no longer.)

plugins/plugin/assets/javascripts/discourse/plugin-name-route-map.js.es6:

export default function () {
  // Matches single-segment paths.
  this.route('main-route', { path: '/:path' });

  // Doesn’t match multi-segment paths.
  this.route('main-route', { path: '/:path/*wildcard_path' });
}

or

plugins/plugin/assets/javascripts/discourse/plugin-name-route-map.js.es6:

export default function () {
  // Matches multi-segment paths.
  this.route('main-route', { path: '/:path/*wildcard_path' });

  // Doesn’t match single-segment paths.
  this.route('main-route', { path: '/:path' });
}

I am not sure about this, but maybe @eviltrout can have a quick look.

I don’t think there’s a way to do this. Is there a reason you can’t define all the routes you’re using? I don’t see the downside.

The paths I want to direct to the same route are not known before. They’re created at run time.

Can you not use a single route and put the stuff that you want in the route into the load?

Technically, all routes are defined at run time (that’s how a route map works) but I assume you mean after the app has loaded?

Could you not use a dynamic segment for your case? If not, could you be more specific about what you are trying to achieve?

I’m working on a plugin which works as a kind of reverse proxy. It allows users to specify routes which upon requesting are forwarded to another URL. The goal is to allow arbitrary web applications to be served within a Discourse instance.

For this, I essentially need to handle all routes that were defined by the plugin users. Therefor, I thought, it’s necessary to catch all requests that aren’t handled by Discourse. In a nutshell, I have unknown paths (they might have multiple segments, query parameters, etc.) that I all want to use the same route for. The route then makes an AJAX call in order to forward the request and retrieve the web application’s content.

I do realize that this is a complex plugin and use case.


So far, I wasn’t able to make this work with any combination of dynamic path segments and wildcard path segments I tried.

What you are suggesting is quite complicated unfortunately. I suspect you will have to play with Ember’s routing internals to make it work.

(via Override existing catch-all wildcard route - #2 by ef4 - Routing - Ember.JS)

The following allows a plugin to have a sort of catch-all style route:

plugins/plugin/assets/javascripts/discourse/plugin-name-route-map.js.es6:

export default function () {
  this.route('main-route.single-segment', { path: '/:path' });
  this.route('main-route.multi-segment', { path: '/:path/*wildcard' });
}

plugins/plugin/assets/javascripts/discourse/routes/main-route-single-segment.js.es6:

export { default } from './main-route';

plugins/plugin/assets/javascripts/discourse/routes/main-route-multi-segment.js.es6:

export { default } from './main-route';

The main route is the one with an actual implementation. Also note that you need to export any controllers/templates in the same fashion if you want to duplicate their behavior.

Is there any chance Discourse could provide plugins the ability to accomplish this without a workaround?

Maybe you could use a permalink redirect.