Ruby on Rails & service worker - javascript

I'm trying to use a service worker in my Ruby on Rails application.
I need to use some erb features in my app/javascripts/service-worker.js.erb file. The service worker registration script looks like this:
var registerServiceWorker = function() {
navigator.serviceWorker.register(
'<%= asset_path('service-worker.js') %>',
{ scope: '/assets/' }
)
.then(function() {
console.info('Service worker successfully registered');
})
.catch(function(error) {
console.warn('Cannot register sercie worker. Error = ', error);
});
}
This does not work; I never get promise here:
navigator.serviceWorker.ready.then
I also tried ./ and / scopes but I got this error:
DOMException: Failed to register a ServiceWorker: The path of the
provided scope ('/') is not under the max scope allowed ('/assets/').
Adjust the scope, move the Service Worker script, or use the
Service-Worker-Allowed HTTP header to allow the scope.
If I move my service-worker.js to the public folder, remove the .erb extension and change scope to ./, everything works great, but I have no template engine there.
Any suggestions?

There's now a gem, serviceworker-rails that will allow you to proxy requests for serviceworker scripts in the asset pipeline. Because of the way browsers register serviceworker scripts, it's best to avoid caching them.
Sprockets will fingerprint assets and Rails (or your webserver) typically serves the compiled files with aggressive caching headers from the /assets directory.
What we want is the ability to customize service worker paths and response headers and still get Sprockets preprocessing, i.e. CoffeeScript/ES2015 to JavaScript transpilation.
The serviceworker-rails gem inserts middleware to your Rails stack that will proxy a request for /serviceworker.js (for example) to the corresponding fingerprinted, compiled asset in production. In development, you get the auto-reloading behavior you would expect. You can set up multiple serviceworkers at different scopes as well.
https://github.com/rossta/serviceworker-rails

Because of the security purpose, you can't register ServiceWorker in higher scope than from where it was executed.
If you really need template engine, you may try to dynamically load JS file from file in your /public folder (How do I include a JavaScript file in another JavaScript file?). Currently Service-Worker-Allowed HTTP header is not implemented yet in Firefox (https://bugzilla.mozilla.org/show_bug.cgi?id=1130101)

I'm running into this problem now as well. Based on my research, I think the proper way for a Rails application to handle service workers is to serve the service worker javascript file normally using the Asset pipeline, scope the service worker using the scope option (like you have done), and then use the Service-Worker-Allowed HTTP header to explicitly allow the new scope.
Unfortunately, at the moment there are several barriers to implementing this method:
Rails 4 (apparently) does not allow adding HTTP headers to Assets it serves, other than the cache-control header. To work around this you could add a Rack plugin as detailed in this S.O. answer, or in production if you are using an Asset server such as Nginx, you can get around this by having Nginx add the HTTP header. Though this doesn't help during development.
Rails 5 does allow adding HTTP headers to Assets however, as detailed in this blog post.
Browser support for service workers and the Service-Worker-Allowed HTTP header is currently poor.

Just do it in the root directory. For example, I put a route
get 'service-worker(.format)', to: 'core#service_worker'
Then just put in your controller (core_controller.rb in my example)
def service_worker
end
Then create app/views/core/service_worker.js.erb and put your JS there (including any <%= stuff %>).

I recently wrote an article about how to do that entirely with Webpacker. Please find my guide to a Progressive Web App with Rails.
In short:
add the webpacker-pwa npm package with yarn add webpacker-pwa and edit environment.js with:
const { resolve } = require('path');
const { config, environment, Environment } = require('#rails/webpacker');
const WebpackerPwa = require('webpacker-pwa');
new WebpackerPwa(config, environment);
module.exports = environment;
This will allow you to use service workers. Please check the README of the project as well: https://github.com/coorasse/webpacker-pwa/tree/master/npm_package

Leave the service worker file in whatever directory imposed by your project structure and set the scope option to / and add the Service-Worker-Allowed HTTP header to the response of your service worker file.
In ASP.Net, I added the following to web.config file:
<location path="assets/serviceWorker.js">
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Service-Worker-Allowed" value="/" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
And register the worker by:
navigator.serviceWorker.register('/assets/serviceWorker.js', { scope: '/' })
.then(function (registration)
{
console.log('Service worker registered successfully');
}).catch(function (e)
{
console.error('Error during service worker registration:', e);
});
Tested on Firefox and Chrome.
Refer to the examples in Service worker specification

Related

PWA serviceWorker not always registered

We have in-house built CMS and recently we added PWA to it. Now, when accessing the home page www.ourdomain.com everything is fine, but when accessing an article, we are a news website, someting like www.ourdomain.com/section/article a message appears:
Failed to register a ServiceWorker: A bad HTTP response code (404) was
gwreceived when fetching the script.
First the scope was
./
and in that case ServiceWorker wasn't registered at all.
Now I changed the scope to
/
and not it is registered for the home page but not for any article. I went through docs, reading questions and answers but doesn't seem able to solve this. By they way, PWA installs well on a mobile an works well.
Your service worker js file should preferably be in your root public directory of your app since you're just starting out with PWAs, and specifying the correct scope is essential, Here's a perfect example of installing your service worker from Google PWA guide.
main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Registration successful, scope is:', registration.scope);
})
.catch(function(error) {
console.log('Service worker registration failed, error:', error);
});
}
service-worker.js
Expecting that you have /service-worker.js and main.js in your root directory, and if you get to work with manifest, you render the same scope as your start_url
You can try the above installation snippet on your app public root directory.

Load JS script in the app running in local context

I have HTML/JS based UWP application. The app is running in local context, so my "Start page" in package manifest contains: index.html.
index.html contains following line:
<script src="ms-appdata:///temp/myScript.js"></script>
So myScript is placed in temporary (TempState) folder. I am aware that files in the temp folder can be deleted by system anytime.
When I launch the app I can see following error:
CSP14312: Resource violated directive 'script-src ms-appx: 'unsafe-eval' blob:' in Host Defined Policy: ms-appdata:///temp/myScript.js. Resource will be blocked.
I know that when I switch to web context by using ms-appx-web, it will work, however, is there any other way to load any JS script in the app running in local context?
UPDATE:
I just forgot to point out that the script is provided by somebody else and cannot be included in the app package.
This is a CSP error, it's not quite relevant to the web/local context. You need to configure your CSP in <meta/> tag on the index.html. Please see the Content Security Policy (CSP)
for more details.

Signalr hosting client scripts for javascript client in server

I have SingnalR (OWIN self-hosted) server hub working with .net client. Now i'm preparing to write web client. I see that hub scripts are served http://localhost:10102/signalr/hubs but cannot see Scripts/jquery-.min.js and Scripts/jquery.signalR-.min.js.
I assume those scripts are not served from server hub (but by default included by nuget to solution) - am i right or missing something?
Is there a way to reference those scripts directly form server (not to copy and host them on javascript client side)?
General:
https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client:
A JavaScript client requires references to jQuery and the SignalR core
JavaScript file. The jQuery version must be 1.6.4 or major later
versions, such as 1.7.2, 1.8.2, or 1.9.1. If you decide to use the
generated proxy, you also need a reference to the SignalR generated
proxy JavaScript file. The following example shows what the references
might look like in an HTML page that uses the generated proxy.
You have only to add the following scripts to your index.html (take care about the versions):
<script src="Scripts/jquery-1.10.2.min.js"></script>
<script src="Scripts/jquery.signalR-2.1.0.min.js"></script>
<script src="signalr/hubs"></script>
Serve these files from server:
Create directory in server project where you place these JS Files
Configure your server that he serves theses files. For that add app.UseFileServer(); to you Configure(...) method in Startup class. (See details about service files: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files)
Add the required scripts in client. There is an example (change adresses and script file to your files and you server adress:
<script type="text/javascript" src="http://localhost:10310/scripts/signalr-clientES5-1.0.0-alpha2-final.js></script>
Actually, you don't need to have scripts to implement SygnalR service (backend part). To make a connection between client index.html and your service, you need to have some kind of client lib that works with SygnalR to establish a connection.
Here's the EXACT solution I came into, based on answers inside this thread:
Static files (like additional javascript files) can be served within same host with configuration of below. Scripts will be available at http://{yourhost}/scripts/{scriptName} when placed inside \Scripts folder iniside solution ('copy if newer' has to be set for 'Copy To Output Directory' for each of the files).
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
// Serving javascript libraries required for client as static content from Scripts folder
app.UseStaticFiles(new StaticFileOptions() {
RequestPath = new PathString("/scripts"),
FileSystem = new PhysicalFileSystem(#".\Scripts"),
});
}
}
IT Man

Confusion about web-application ports

I have a project that is already deep in development, and there is a problem with the ports.
The Client is SPA written in backbone, that uses Sails as a server.
Problem is in the fact that Client is running in Express on port 80, while Sails is run on 1337.
I would like to host this backbone application within the Sails, not ouside the sails.
A bit more details:
When I fire the Fiddler, I am seeing requests being made to localhost:1337/get/user.
I need it to reside on port 80 as well.
Backbone is written using standard. I have app.js and main.js with all of the common folders (JS, LIBS, CSS). In other words, I have index.html that has data-main using require.js...
I have not problems running the client in separate node.js... how to run it within Sails.js?
Where do I put my index.html???
Trying to serve index.html as a static file won't work. Instead, try the following:
1. Serve your index.html from Sails
Just serve index.html as a combination of views/layout.ejs and views/home/index.ejs, which are mounted to the root / for default newly created Sails project.
2. Set up a catch-all route
In config/routes.js put something like this:
module.exports.routes = {
'/': {
view: 'home/index'
},
'/:unknownRoute': {
view: 'home/index'
}
}
This way you'll be able, for example, to use simple one-level pushstate routing within your SPA: routes like /products or /news will still give you your index.html (if you are using something more complex though, you may want to play a little bit more with your Sails routes).
3. Serve your API with a prefix
In your config/controllers.js put, for example:
module.exports.controllers = {
...
prefix: '/api',
...
}
This will let you serve your API with a prefix and have both /api/products (JSON API) and /products (your SPA) routes available.
4. Use any port you want
You can change the default port via config/local.js, even to 80 (if you don't have anything else running on 80, of course).
In production though, it would probably be a better idea to just proxy to default Sails' or any other port with Nginx, for example.

socket.io.js not loading

I know there are a bunch of questions on this already, but none have answered it for me, and plus mine is slightly different.
I'm starting the socket.io server in node using this:
var io = require('socket.io').listen(8000);
My terminal says everything is ok:
info - socket.io started
Now I am trying to load the .js file in my clientside browser using this url:
http://<hostname>:8000/socket.io/socket.io.js
I dont get a 404, it just hangs forever. I've also used a network utility to ping port 8000, and it seems to be open fine.
I installed node and socket.io just yesterday, so they should be the latest versions. Can anyone shed any light on this? Thanks!
Turns out the reason I could never request the .js file was because my company network blocks all ports except the usual ones (80, 21, etc), so `I would never be able to communicate with port 8000.
Use express.js. Place the socket.io file in public/javascripts folder and add this line to your html
<script src="/javascripts/socket.io.js"></script>
I think this is the best way. When you're writing http://<hostname>:8000/socket.io/socket.io.js
node tries to find a folder named socket.io in your project's public folder. And the file socket.io.js in it.
If you don't want to use express.js you should catch the request and try to load a file if no routes were found for your request (what actually express does) because node doesn't know what to do for requests which don't match any routes in your server.
And I recommend to use the socket.io.min.js file (it's smaller and it's in folder node_modules\socket.io\node_modules\socket.io-client\dist)
You have to start an http/https server to access it via http/https. Simply starting an socket.io server won't do. Do the following:
var http = require('http');
var app = http.createServer(),
io = require('socket.io').listen(app);
app.listen(7000, "0.0.0.0");
Then I can access the file http://localhost:7000/socket.io/socket.io.js
sockets.io uses websocket protocol (ws://). See the wikipedia page.
You need to get at least 3 pieces working together.
Serve some HTML (/index.html will do just fine) so there's a web page. This file should contain the socket.io client <script> tag. For this you need the http server portion of the starter examples. You are missing this and that's why browsing to your server just hangs.
Serve the socket.io client. Socket.io will do this for you automatically when you pass in your http server function to it. You don't need full express as this can be done with just node's http module as per the first example on the socket.io docs.
Some javascript to actually do something with the socket. This can be the content of a <script> tag in index.html or a separate file. If it's a separate file, you need to set up your http server to actually serve it.

Categories