PWA multiple virtual paths with same backend code does not create separate installs - progressive-web-apps

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.

Related

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.

Meteor - Password recovery / Email confirmation dynamic url

Basically, I'm using the accounts-base package on meteor and on meteor startup, I set up what template the server should use for the password recovery mail, email confirmation mail, etc.
For example, in my server/startup.js on meteor startup I do many things like :
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl(`verify-email/${token}`);
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
return EmailService.render.email_verification(user, url);
};
The problem is that my app is hosted on multiple host names like company1.domain.com, company2.domain.com, company3.domain.com and if a client wants to reset his password from company1.domain.com, the recovery url provided should be company1.domain.com/recovery.
If another client tried to connect on company2.domain.com, then the recovery url should be company2.domain.com.
From my understanding, this is not really achievable because the method used by the Accounts Package is "Meteor.absoluteUrl()", which returns the server ROOT_URL variable (a single one for the server).
On the client-side, I do many things based on the window.location.href but I cannot seem, when trying to reset a password or when trying to confirm an email address, to send this url to the server.
I'm trying to find a way to dynamically generate the url depending on the host where the client is making the request from, but since the url is generated server-side, I cannot find an elegent way to do so. I'm thinking I could probably call a meteor server method right before trying to reset a password or create an account and dynamically set the ROOT_URL variable there, but that seems unsafe and risky because two people could easily try to reset in the same timeframe and potentially screw things up, or people could abuse it.
Isn't there any way to tell the server, from the client side, that the URL I want generated for the current email has to be the client current's location ? I would love to be able to override some functions from the account-base meteor package and achieve something like :
Accounts.urls.verifyEmail = function (token, clientHost) {
return `${clientHost}/verify-email/${token}`;
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
return EmailService.render.email_verification(user, url);
};
But I'm not sure if that's possible, I don't have any real experience when it comes to overriding "behind the scene" functionalities from base packages, I like everything about what is happening EXCEPT that the url generated is always the same.
Okay so I managed to find a way to achieve what I was looking for, it's a bit hack-ish, but hey..
Basically, useraccounts has a feature where any hidden input in the register at-form will be added to the user profile. So I add an hidden field to store the user current location.
AccountsTemplates.addField({
_id: 'signup_location',
type: 'hidden',
});
When the template is rendered, I fill in this hidden input with jQuery.
Template.Register.onRendered(() => {
this.$('#at-field-signup_location').val(window.location.href);
});
And then, when I'm actually sending the emailVerification email, I can look up this value if it is available.
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl(`verify-email/${token}`);
};
Accounts.emailTemplates.verifyEmail.html = function (user, url) {
const signupLocation = user.profile.signup_location;
if (signupLocation) {
let newUrl = url.substring(url.indexOf('verify-email'));
newUrl = `${signupLocation}/${newUrl}`;
return EmailService.render.email_verification(user, newUrl);
}
return EmailService.render.email_verification(user, url);
};
So this fixes it for the signUp flow, I may use the a similar concept for resetPassword and resendVerificationUrl since the signupLocation is now in the user profile.
You should probably keep an array of every subdomains in your settings and keep the id of the corresponding one in the user profile, so if your domain changes in the future then the reference will still valid and consistent.

make meteor restful api/web-service

I have created a new url/route in my app where I need to write a web-service. I need to write a service that deletes user according to the parameters passed in the service. For now, anyone should be able to call that service (will make it secure at later stage). App is built on meteor.
My url is : loaclhost:3000/deleteUser. Now one should be able to call my delete user function defined on this page and pass json structure data as an argument to it. If the data is valid, then the user should be deleted.
Using simple:rest package
Meteor.publish("delUser", function (a, b) {
UserDetails.remove({}); //delete user according to data received
}, {
url: "/testing/delUser", //url where third party will call the function
getArgsFromRequest: function (request) {
// Let's say we want this function to accept a form-encoded request
// with fields named `a` and `b`.
console.log('received : ' + JSON.stringify(request.body) );
var content = request.body;
// Since form enconding doesn't distinguish numbers and strings, we need
// to parse it manually
return [content.a, content.b];
}
})
How to access the function, delUser from a thrid party? I also need to add authentication at a later stage.
Personnally, I use this :
simple:rest
simple:json-routes
simple:rest-accounts-password
I find it easier to implement.
even iron:router comes with server side routes where you can build your own functions and api calls.
http://iron-meteor.github.io/iron-router/#restful-routes
Sample (Server side code) :
Router.map(function () {
this.route("api", {path: "/api/:paramsYouNeed",
where: "server",
action: function(){
this.response.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
if (this.request.method == 'POST') {
var response;
//do whatever you want to do
this.response.end(response);
}
}
});
The other user can call this by making a http.post request to the above url (http:www.a****a.com/api/params)
The easiest way to do this is use the restivus package.
https://atmospherejs.com/nimble/restivus
Restivus makes building REST APIs in Meteor 0.9.0+ easier than ever
before! The package is inspired by RestStop2 and Collection API, and
is built on top of Simple JSON Routes to provide:
A simple interface for creating REST APIs
Easy setup of CRUD endpoints for Mongo Collections
User authentication via the API
Optional login and logout endpoints
Access to this.user in authenticated endpoints
Custom authentication if needed
Role permissions for limiting access to specific endpoints
Works alongside the alanning:roles package - Meteor's accepted role permission package

Meteor FlowRouter: replace path in history for restricted route

I'm using FlowRouter in a Meteor app. In one case, a resource is not available until a certain date/time, so we redirect to another route. Is there anyway to replace the route to the restricted resource with the path to the redirect such that the restricted resource route will not appear in the browser history. Doing will make the history (using back, forward) more UX friendly.
I can achieve this in FlowRouter's triggersEnter for the route, by stepping outside of FlowRouter with something like:
if(restricted) {
return window.location.replace(`/waitingroom/${resourceId}/user/${Meteor.userId()}`);
}
...but this causes a page reload, which is sort of undesirable.
Any idears?
Functions pass to triggersEnter have the 2nd param named redirect you can use it to redirect to other pages without reloading the page and having a clean history:
FR.route('/restricted-route', {
name: 'RestrictedRoute',
triggersEnter: [function(context, redirect) {
redirect('/replace-route');
}]
});
FR.route('/replace-route', {
name: 'ReplaceRoute',
action() {
// ...
}
});
Updated
I am not sure why it's required to be sync. Anyway FlowRouter uses Page.js behind the scene to do navigation, if you can not use redirect then this should work:
FR.route('/restricted-route', {
name: 'RestrictedRoute',
triggersEnter: [function(context, redirect) {
Meteor.setTimeout(() => {
FlowRouter._page.replace('/replace-route');
}, 1000);
}]
});
Note: this is not the public API, therefore you should test it carefully before using in production.

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

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