Durandal JS: typing url without hash give HTTP Error 404.0 - Not Found - hash

I am building a SPA application using Durandaljs. When I enter a URL WITHOUT hash, it shows the error: HTTP error 404.0 - NOT FOUND. However, it is working fine WITH hash.
Example:
www.domain.com/page => HTTP error 404.0 - NOT FOUND
www.domain.com/#page = > working fine.
How can I map the www.domain.com/page to www.domain.com/#page?

In order for pushState to work correctly in your app (including deep linking), you must configure the router for pushState and tell the server to ignore all but certain paths/routes.
The server should ignore paths that contain resources or services used by your SPA, and should always send the Durandal.cshtml (or whatever you name it) for these ignored routes.
These routes should be ignored even if there's additional route info in the request. This will allow for deep linking using pushState-style URLs.
Here's the MVC routing from a Durandal-based app I recently completed:
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Ignore("{resourcedir}/{*resource}",
new {resourcedir = #"(api|breeze|app|scripts|content|assets|signalr)"});
routes.MapRoute(
name: "Default",
url: "{*sparoute}",
defaults: new { controller = "Durandal", action = "Index"});
}
Basically, we're telling the server to ignore any requests that start with:
api/
breeze/
app/
scripts/
content/
assets/
signalr/
We're ignoring api, breeze and singlar because WEBApi and SignalR will handle those requests. Also, app, scripts, content, and assets are ignored because the underlying ASP.NET engine will serve those requests.
The final statement (routes.MapRoute) causes all non-ignored requests to send back the default page (the only real page in the SPA).
For example, www.domain.com/page will send the same response from the server as if you'd asked for www.domain.com/. Once the SPA loads and Durandal's router/history has initialized, the suffix is converted to hash if needed (like you're using IE) and is then dispatched through the router.
Note that if your application is not rooted at /, you need to specify the root path. You should also include hashChange: true so that your application works in IE, like this:
router.activate({ pushState: true, hashChange: true, root: '/approot' })

You can, I'm using it in my latest Durandal site which is based on the DurandalAuth template.
When you are initializing your router, set pushState: true, like this:
return router.map([
{ route: 'somroute', moduleId: 'somroute', title: 'Home', nav: true, hash: "#somroute" }
])
.buildNavigationModel()
.mapUnknownRoutes("notfound", "notfound")
.activate({ pushState: true });
You can see a working example on my site: noadou

Related

Dynamic Links from a CMS - Error: "redirect" can not be returned from getStaticProps during prerendering

I have a Next JS app connected to a CMS and hosted on Vercel - all links are dynamic and the pages are created by the content authors.
I am trying to create dynamic redirects that will force URLs to adhere to formats that are better for SEO. For example:
Enforce lowercase URLs
Replace spaces with dashes
Remove trailing slashes
For example, /test/Author Name/ would redirect to /test/author-name
Since I need to trigger a 301 redirect for these wrong URLs, the only way to do this with Next JS from what I have found is to return a Redirect from getStaticProps, this is what I have so far:
export const getStaticProps: GetStaticProps = async (context) => {
let requestedUrl = '/';
if (context?.params?.path) {
requestedUrl = '/' + (context?.params?.path as string[]).join('/');
}
//check for URLs with uppercases, spaces, etc. and clean them up
let modifiedUrl = requestedUrl;
modifiedUrl = modifiedUrl.trim().toLowerCase().replace(/\s\s+/g, ' ').replace(/\s/g, '-');
if (modifiedUrl != requestedUrl) {
return {
redirect: {
destination: modifiedUrl,
permanent: true,
},
};
}
This works wonderfully well running locally and connected to the CMS - everything is working as it should and all "faulty" URLs are corrected with the correct response code.
Sadly, this does not work on build, I have spent so much time so far trying to find an alternative, but no matter what I do, the build on Vercel fails with the error:
"redirect" can not be returned from getStaticProps during prerendering
The next best potential solution is to use Middleware, but that requires v.12 at least. Due to limitations from the CMS connector, we are forced to use Node v.11 :(
The alternative that I have built is to use router.push on the client side, but this... just looks terrible. The page loads, returns a 200, and then loads again with the corrected URL. Not good for the user's experience.
Any advice or suggestions? I am baffled that something this simple is this complicated with Next JS!
I resolved the issue... it looks like redirects on statically generated pages are not possible unfortunately. I removed getStaticProps and getStaticPaths, and added getServerSideProps instead. The redirects are now working correctly, but the site is not as fast as we are losing out on SSG.

ASP.NET Core 5 route redirection

We have an ASP.NET Core 5 Rest API where we have used a pretty simple route:
[Route("api/[controller]")]
The backend is multi-tenant, but tenant-selection has been handled by user credentials.
Now we wish to add the tenant to the path:
[Route("api/{tenant}/{subtenant}/[controller]")]
This makes cross-tenant queries simpler for tools like Excel / PowerQuery, which unfortunately tend to store credentials per url
The problem is to redirect all existing calls to the old route, to the new. We can assume that the missing pieces are available in the credentials (user-id is on form 'tenant/subtenant/username')
I had hope to simply intercept the route-parsing and fill in the tenant/subtenant route values, but have had not luck so far.
The closes thing so far is to have two Route-attributes, but that unfortunately messes up our Swagger documentation; every method will appear with and without the tenant path
If you want to transparently change the incoming path on a request, you can add a middleware to set Path to a new value, for example:
app.Use(async (context,next) =>
{
var newPath = // Logic to determine new path
// Rewrite and continue processing
context.Request.Path = newPath;
await next();
});
This should be placed in the pipeline after you can determine the tenant and before the routing happens.

PWA multiple virtual paths with same backend code does not create separate installs

I have a generic common NodeJS app that multiple users access. The users are identified via the path. For example: https://someapp.web.app/abc can be one path while https://someapp.web.app/def can be another path.
On the NodeJS server path, I send the same server code by passing the path parameters to the program. The route appears something like this:
app.get('/*', async (req, res) => {
...
locals.path = req.path;
...
res.render('index', locals);
}
In the above index is a template that uses locals data for customisation
What I would like is that for each path there is a separate manifest and its associated icons and that on a single device (phone or desktop) multiple installations be possible. Thus, https://someapp.web.app/abc be one icon and https://someapp.web.app/def be another icon.
I am having difficulty in the placement and the scoping of the manifest and service worker. It always adds only one icon (the first path installed) to the home screen or desktop. My settings are:
In the public (root) folder I have each manifest viz. abc-manifest.json and def-manifest.json and a common sw.js.
The abc-manifest.json is:
'scope': '/abc',
'start_url': '/abc',
...
The access to the service-worker from the index.js is:
if (navigator.serviceWorker) {
navigator.serviceWorker.register('sw.js')
.then(function (registration) {
console.log('ServiceWorker registration succeeded');
}).catch(function (error) {
console.log('ServiceWorker registration failed:', error);
});
}
I have tried changing the paths of scope and start_url to / but it did not work. Since all requests to the public path are common and not within the virtual /abc path, I am unable to figure out how to get this working.
Thanks
Could that be an option to have a dedicated route that will redirect the user to /abc or /def?
In the manifest:
{
"start_url": "https://example.com/login",
"scope": "https://example.com/",
}
/login would make sure to redirect to /abc or /def.
This way you could keep one service worker, and one manifest.
And in the Service Worker, maybe try to return the specific icon based on file name.
self.addEventListener('fetch', e => {
// Serve correct icon
let url = new URL(e.request.url)
if (url.pathname.contains('/android-icon-512.png')) {
return respondWith(e, '/android-icon-512-abc.png')
}
// other ifs…
// Return from cache or fallback to network.
respondWith(e, e.request)
})
const respondWith = (e, url) =>
e.respondWith(caches.match(url)
.then(response => response || fetch(e.request).then(response => response))
)
Maybe you’ll need a specific header to do this, or use a URL parameter (icon.png?user=abc) to help query the right icon. I’m throwing idea, because it probably depends a lot on your app back-end and/or front-end architecture.
I once did this: the back-end (PHP / Laravel) handled the correct returning of the icon and manifest (I had one for each use case) based on other stuff.

Fetching album art from the Cover Art Archive (archive.org) API leads to CORS errors due to redirects

I'm developing a frontend for the MusicBrainz API that can search for artists as well as their releases (release groups, specifically). When I attempt to find a certain cover art from the release group's respective MusicBrainz ID (MBID) via their Cover Art Archive with the Axios library, I receive two CORS errors:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://ia802802.us.archive.org/9/items/mbid-<any MBID>/index.json. (Reason: CORS request external redirect not allowed).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://ia802802.us.archive.org/9/items/mbid-<any MBID>/index.json. (Reason: CORS request did not succeed).
What I've done so far
Following some research, I realised that the Cover Art Archive does not host their own images; rather, accessing their API leads to a redirect to the Internet Archive API. As such, I am instead going straight to the IA API in order to find the cover art I need, as I can find it with a MBID there, too. This itself would lead to a CORS error, but I am using a proxy (with Nuxt.js), meaning I can connect to the Internet Archive - at least initially - with no issue.
proxy: {
'/archive': {
target: 'https://archive.org/',
pathRewrite: {
'^/archive': ''
}
}
},
The main issue is that the IA then redirects again to a destination that is dynamic and often changes. Due to this, I am unable to predict where the redirect will go and I cannot avoid the aforementioned CORS errors, even via a proxy - Axios does not use it here, understandably.
I have researched this fairly extensively and I cannot find how to prevent these errors from appearing when I cannot stop the redirects from happening.
const getAlbumArtLocation = (release, index) => {
this.$axios
.get(
'/archive/download/mbid-' + release.id + '/index.json'
)
.then((res) => {
const imageURL = res.data.images[0].image
getAlbumArt(imageURL, index)
})
.catch((err) => {
// Try get another album artwork.
console.error(err)
index += 1
getAlbumArtCandidate(index)
})
}
The code for the file in question can be found here.
My Nuxt configuration can be found here.
It's worth noting that these errors only appear in Firefox and not other Chromium-based browsers.
EDIT
As you are using #nuxtjs/proxy middleware, which is using
http-proxy-middleware, you can configure the proxy to follow redirects (which is off by default) - so redirect response will not be returned to your client but proxy will follow the redirect on the server and return only final response...
Change your nuxt.config.js from:
'/archive': {
target: 'https://archive.org/',
pathRewrite: {
'^/archive': ''
}
}
to:
'/archive': {
target: 'https://archive.org/',
pathRewrite: {
'^/archive': ''
},
followRedirects: true
}

Reverse routing on an application deployed in a Tomcat context

I am developing a Play 1.2.5 application that will be installed in a Tomcat context:
http://mytomcat:8080/myapp
And in my routes file I have:
GET /companies/{companyID}/employees Employees.getForCompany
As per the instructions for deploying a Play application in a Tomcat context, I am generating URLs exlusively using the Reverse Router. This works fine, but I am not sure what to do in the case of a jQuery method such as an Ajax request of this style:
var companyID = $('#companies').find(":selected").val();
$.ajax({
url : "#{Employees.getForCompany(companyID)}",
...
});
Obviously the value of companyID isn't known at the time of the generation of the HTML and the resolution of routes by the reverse router.
The only solution that I can see is to reconfigure my routes file so that the URLs are different and the parameters are always at the end, but that makes the REST URLs less logical.
GET /companies/employees/{companyID} Employees.getForCompany
Is there a better way?
I have found the solution myself - Play includes the jsAction tag which generates a function that builds the correct URL:
var companyURL = #{jsAction #Employees.getForCompany(':companyID') /}
$.ajax({
url : companyURL({companyID:companyID}),
...
});
Like that I can maintain the preferred routes mapping:
GET /companies/{companyID}/employees Employees.getForCompany