ActionCable channel doesn't connect unless page is reloaded - ruby-on-rails-5

I have a cable that works great if I manually refresh the page, but if I navigate to it, it never starts streaming.
WHen working correctly, I see this in the Rails console:
ChecklistPdfChannel is transmitting the subscription confirmation
ChecklistPdfChannel is streaming from checklist_pdf_2-5a-ntiasohen-gattopuin
Here's the coffeescript:
document.addEventListener 'turbolinks:load', ->
if !App.presence
App.presence = App.cable.subscriptions.create({
channel: 'ChecklistPdfChannel',
issueable_id: window.location.pathname.split('/').splice(2,1).toString()
},
connected: ->
disconnected: ->
received: (data) ->
console.log(data)
$('#pdf_status').html "#{data.html}"
)
return
I've tried the following:
added meta tag: <meta name="turbolinks-cache-control" content="no-cache">
added 'data-turbolinks-track': 'reload' on the asset calls in the layout
the behavior is the same in both development and staging/production (Heroku).
Rails 5.2.2, ActionCable 5.2.2, Turbolinks 5.2.0

So the only way I was able to get this to work was to add this meta tag on the pages where I needed the cable active:
<meta name="turbolinks-visit-control" content="reload">

Related

GWT Bootstrap3 Openlayers offline

I am using GWT with Bootstrap3 and Openlayers Map. I have implemented my own OSM Map server.
My application does not start without internet connection. I need guidance.
I followed the instructions in boostrap3 V1.0.2 for offline applications.
However I only got a blank screen.
Starting with the Firefox debugger I got the following message in the console:
Uncaught ReferenceError: OpenLayers is not defined
<anonymous> http://www.openstreetmap.org/openlayers/OpenStreetMap.js:7
Starting with Google Chrome I get the following warning
[Deprecation] Application Cache API manifest selection is deprecated and will be removed in M85, around August 2020. See https://www.chromestatus.com/features/6192449487634432 for more details.
followed by
GET http://www.openlayers.org/api/OpenLayers.js net::ERR_INTERNET_DISCONNECTED
and
localhost/:1 Application Cache Error event: Invalid or missing manifest origin trial token: http://localhost:8090/simaso/simasoweb/appcache.manifest
Here is my basic setup
SiMaSoWeb.gwt.xml:
...
<inherits name='com.google.gwt.json.JSON'/>
<inherits name="com.google.web.bindery.autobean.AutoBean"/>
<inherits name="org.gwtbootstrap3.extras.cachemanifest.Offline"/>
...
<add-linker name="offline" />
SiMaSoWeb.html:
<!doctype html>
<html manifest="simasoweb/appcache.manifest">
<head>
<title>Sirene</title>
<script type="text/javascript" language="javascript" src="simasoweb/simasoweb.nocache.js"></script>
<script src="http://www.openlayers.org/api/OpenLayers.js"></script>
<script src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>
<link type="text/css" rel="stylesheet" href="SiMaSoWeb.css">
....
</html>
In ...\simasoweb\appcache.manifest I find:
CACHE MANIFEST
# Version: 1599380329409.0.6297069797290025
CACHE:
AF4477772D0DB53A10ABCF74A5AE0C4D.cache.js
fonts/fontawesome-webfont.woff
clear.cache.gif
fonts/FontAwesome.otf
css/bootstrap-notify-custom.min.cache.css
7192594CA2F468C2F793523022719FA0.cache.js
...
css/font-awesome-4.7.0.min.cache.css
NETWORK:
*
Finally
I compile all this . Resources seem to be included in the war file ..
Needless to say that with internet connection, only in the first 1-2 seconds of starting, all is running fine ..
As per the Google Chrome warning you included, App Cache is a deprecated standard and is being removed. It has already been removed from non-secure contexts.
You should be using Service Workers instead to cache resources for offline use. You may have to write your own linker or maybe you can use gwt-serviceworker-linker.
Thanks to ELEVATE I managed to move from AppCache to ServiceWorker. However the Openlayers couldnt be fixed this way. So here is what solved the issues:
ServiceWorker
I am still using Java 8
I upgraded to GWT 2.9
I added to .gwt.xml
<inherits name="org.realityforge.gwt.serviceworker.Linker"/>
<inherits name="elemental2.dom.Dom"/>
<inherits name="elemental2.promise.Promise"/>
<inherits name="jsinterop.base.Base"/>
...
and
<add-linker name="serviceworker"/>
<extend-configuration-property name="serviceworker_static_files" value="./"/>
<extend-configuration-property name="serviceworker_static_files" value="../SiMaSoWeb.html"/>
In my entry JAVA-routine right at the start of my onModuleLod I added
import static elemental2.dom.DomGlobal.*;
import elemental2.dom.DomGlobal;
public void onModuleLoad() {
...
initStatic();
...
and later in that module
public void initStatic() {
if ( null != navigator.serviceWorker )
{
navigator.serviceWorker.register("simasoweb/"+ GWT.getModuleName() + "-sw.js" ).then( registration -> {
console.log( "ServiceWorker registration successful with scope: " + registration.getScope() );
// Every minute attempt to update the serviceWorker. If it does update
// then the "controllerchange" event will fire.
DomGlobal.setInterval( v -> registration.update(), 60000 );
return null;
}, error -> {
console.log( "ServiceWorker registration failed: ", error );
return null;
} );
navigator.serviceWorker.addEventListener( "controllerchange", e -> {
// This fires when the service worker controlling this page
// changes, eg a new worker has skipped waiting and become
// the new active worker.
console.log( "ServiceWorker updated ", e );
} );
}
}
}
I had issues with accesing the right files in my gwt - war directory. Therefore I updated the navigator.serviceWorker.register... command.
Very useful was the google inherent debugger with CTRL+SHIFT+I. In the 'console'-tab you find the messages - red means bad - solve it!
As external jar libraries I had to include
elemental2-core
elemental2-dom
elemental2-promise
base
Now the openlayers issues... Needless to say that there might be a much more elegant way and further more, you need an offline-map, which I have rendered myself and available (150GB for Germany!).
In the HTML file
Note that I opened the openlayers and openstreetmap .js files in a browser copied them in a file and copied them into my war directory in the subdirectory src. Again the browser debugger can help find directory issues.
<script type="text/javascript" language="javascript" src="simasoweb/simasoweb.nocache.js"></script>
<script src="src/OpenLayers.js"></script>
<script src="src/OpenStreetMap.js"></script>
Copy hard
I downloaded the gwt-openlayers demo project GWT-OpenLayers-master.zip
and copied all files in GWT-OpenLayers-master\gwt-openlayers-showcase\src\main\resources\org\gwtopenmaps\demo\openlayers\public\openlayers into my ...war\src\ directory where my openlayers.js files lie.
Finally I am not sure if the service-worker point 1-3 really helped.

How do I load Sentry before the main JS bundle loads?

I use Sentry to track client-side errors. However, I loaded my web app in the Edge browser today only to find a blank page. Edge raised a TextEncoder is not defined error because one of the libraries in my bundle referenced TextEncoder which it does not support. Sentry did not report the error because the error occurred before Sentry was initialized.
I use vue-cli to create a Vue project with Sentry being initialized near the top of the main file:
import { init } from '#sentry/browser';
import { environment } from '#/constants';
import { Vue as VueIntegration } from '#sentry/integrations';
export default function(Vue) {
const debug = environment !== 'production';
init({
dsn: 'redacted',
environment,
debug,
integrations: [new VueIntegration({ Vue, logErrors: debug })],
});
}
I've been thinking of initializing Sentry manually with a script tag near the start of the <body> tag. However, the fact that I use the VueIntegration plugin complicates things. Would it be safe to initialize Sentry twice? Once before the main bundle loads and once as I'm doing in the example above?
I noticed there's something in the docs about managing multiple Sentry clients but I'm not sure if that's relevant to my specific case.
One idea I have is just a barebones window.onerror hook before anything else loads but I'm not really sure how to interact with Sentry without pulling in their #sentry/browser package. Ideally I would just communicate with their service using a simple XHR request and my DSN.
My question is what is the recommended way to track errors that occur before Sentry is initialized in the main JS bundle?
I ended up solving it by adding a barebones window.onerror hook that loads inline before the main bundle arrives. The error is immediately sent to our API and then to our Slack #alerts channel. I added rate limiting so people dont abuse it (too much).
index.html (generated by vue-cli except for the new script tag):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
/>
<title>App title</title>
</head>
<body>
<script>
// This gives us basic error tracking until the main app bundle loads and
// initializes Sentry. Allows us to catch errors that would surface before
// Sentry has a chance to catch them like Edge's `TextEncoder is not defined`.
(function() {
function sendBasicClientError(message, error) {
var xhr = new XMLHttpRequest();
var domain =
window.location.hostname === 'localhost'
? 'http://localhost:5000'
: 'https://example.com';
xhr.open('POST', domain + '/api/v1/basic_client_errors');
xhr.setRequestHeader(
'Content-Type',
'application/vnd.api+json; charset=utf-8'
);
xhr.send(
JSON.stringify({
data: {
type: 'basic_client_error',
attributes: {
error_message: 'Init error: ' + message + ' ' + navigator.userAgent,
error: error
? JSON.parse(
JSON.stringify(error, Object.getOwnPropertyNames(error))
)
: null,
},
},
})
);
}
window.onerror = function(message, filename, lineno, colno, error) {
sendBasicClientError(
message + ' ' + filename + ':' + lineno + ':' + colno,
error
);
};
})();
</script>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Right before Sentry loads we clear the hook:
// Clears the simple `window.onerror` from `index.html` so that Sentry can
// take over now that it's ready.
window.onerror = () => {};
init({
dsn: 'redacted',
environment,
debug,
integrations: [new VueIntegration({ Vue, logErrors: debug })],
});
Rails controller:
module Api
module V1
class BasicClientErrorsController < ApplicationController
def create
# Can comment out if not using the `pundit` gem.
skip_authorization
# We use `sidekiq` and `slack-ruby-client` gems here.
# Substitute whatever internal error tracking tool you use.
SlackNotifierWorker.perform_async(
basic_client_error_params[:error_message],
'#alerts'
)
head :accepted
end
private
def basic_client_error_params
# We use the `restful-jsonapi` gem to parse the JSON:API format.
restify_param(:basic_client_error).require(:basic_client_error).permit(
:error_message
)
end
end
end
end
Rate limiting with the rack-attack gem:
Rack::Attack.throttle('limit public basic client errors endpoint', limit: 1, period: 60.seconds.to_i) do |req|
req.ip if req.path.end_with?('/basic_client_errors') && req.post?
end
Have you tried this Laxy loading Sentry ?
It's mentioned here that if you set data-lazy to no, or use forceLoad, it'll try to fetch Sentry SDK as soon as possible.
This issue should be handled by the loader, as mentioned here :
Current limitations
Because we inject our SDK asynchronously, we will only monitor global errors and unhandled promise for you until the SDK is fully loaded. That means that we might miss breadcrumbs during the download.
For example, a user clicking on a button on your website is making an XHR request. We will not miss any errors, only breadcrumbs and only up until the SDK is fully loaded. You can reduce this time by manually calling forceLoad or set data-lazy="no".

Rails blueimp fileupload - cannot select files in Internet Explorer

In my Rails App (3.2.12) I'm using the jquery-fileupload-rails gem to enable users ti upload profile pictures. Everything works fine in Chrome and Safari, but in Internet Explorer (I tested it with version 10) I can't even select files to upload. When I click the 'Add Files'-Button, instead of showing a dialog to select files he instantly fires an empty request to the upload action, resulting in a json response showing an empty photo object. This is my current js to initialize the fileupload (I already added some code from issues with IE and the csrf-tokens):
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload({
dataType: 'json',
acceptFileTypes: /(\.|\/)(gif|jpe?g|png|tiff)$/i
});
// Enable iframe cross-domain access via redirect option:
$('#fileupload').fileupload(
'option',
'redirect',
window.location.href.replace(/\/[^\/]*$/, '/photos?%s')
);
//add csrf token manually for ie iframe transport
$('#fileupload').bind('fileuploadsend', function(event, data) {
auth_token = $('meta[name="csrf-token"]').attr('content');
data.url = data.url + '?authenticity_token=' + encodeURIComponent(auth_token);
$.blueimp.fileupload.prototype.options.send.call(this, event, data);
});
and my controller code for the response, in which I already (hopefully correct) set the content type to 'text/plain':
format.html {
render json: [#photo.to_jq_upload].to_json,
content_type: 'text/plain', #content_type: 'text/html',
layout: false
}
format.json {
render json: {files: [#photo.to_jq_upload]},
content_type: 'text/plain',
status: :created,
location: #photo
}
Does anyone know, how to get this to work in IE and can help me please? Thanks :)
It took me quite some time to figure it out but in the end it was actually pretty simple: When applying my own styles I replaced the span-tag around the Add-Files-Button with a button-tag. This had no effect in the webkit browsers, however led to an immediate form submit in Firefox and Internet Explorer. Changing it back finally solved the issue :)

Google Visualization not appearing in heroku production

I have implemented the google_visualr gem and it works perfectly in development. However when I push to heroku the visualization does not show up. All the javascript is present and there is no error or indication in the app logs.
Here is the javascript that is generated. It's exactly the same as dev. Do i have to do anything additional to get the google visualization to run in heroku environment?
<script type='text/javascript'>
google.load('visualization','1', {packages: ['corechart'], callback: function() {
var data_table = new google.visualization.DataTable();data_table.addColumn('string', 'Status');data_table.addColumn('number', 'Count');data_table.addRow([{v: 'Started Applications'}, {v: 10}]);data_table.addRow([{v: 'Completed Applications'}, {v: 1}]);
var chart = new google.visualization.PieChart(document.getElementById('pie_chart'));
chart.draw(data_table, {width: 360, height: 240, title: 'Applications Status Summary', is3D: true, legend: 'bottom'});
}});
</script>
I found the problem was that my call to the google jsapi was not secure so the browser was blocking the request.
<script src='http://www.google.com/jsapi'></script>
I was able to fix this by requesting the jsapi in secure mode using https
<script src='https://www.google.com/jsapi'></script>
There is nothing additional to be done to get the visualizations to work on Heroku.
The demo site is hosted on Heroku too: http://googlevisualr.herokuapp.com/examples/interactive/pie_chart
Just to check, is this in the head tag, at the top of the page?
<script src='http://www.google.com/jsapi'></script>

form :remote => true, not working in IE?

- form_for(#post, :remote => true, :id => 'post_form') do |f|
Works as expected in FF and Chrome, but IE just processes the submit action normally, without any ajax request.
Not really seeing any info on this on the rest of the internet so I imagine I've done something wrong somehow. Ive used both the default rails.js, and the jquery version from the github page
Well, I don't know why the default rails version doesn't work for me here on IE, but I wrote this as a workaround:
if ($.browser.msie) {
var form = $('form#new_post');
form.find('input#post_submit').bind('click', function(){
var data = form.serializeArray();
$.ajax({url: '/posts', type: 'POST', data: data});
return false
});
}
And now it's working correctly. Shouldn't something like this be included in rails.js if this is in fact a problem with Rails, and not something that I've somehow done?
In our Rails 3 app the form tagged as data-remote wasn't turned into an AJAX form any longer after we had upgraded to jquery-rails 1.0.19. IE7 wasn't able to load the jquery.js - there seems to be a problem with version 1.7.1 of jQuery currently. After downgrading to jquery-rails 1.0.18 the problem disappeared again.