Closed tomgallagher closed 5 years ago
I thought I'd add the relevant bits of JS and ruby
registration.pushManager.subscribe({ userVisibleOnly: true })
.then(function(subscription) {
var subData = new Object();
subData.endpoint = subscription.endpoint;
subData.p256dh = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))).replace(/\+/g, '-').replace(/\//g, '_');
subData.auth = btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth')))).replace(/\+/g, '-').replace(/\//g, '_');
console.log(subData);
var subscriptionURL = "/push_subscribe";
$.ajax({
type: "POST",
contentType: 'application/json; charset=UTF-8',
dataType: 'json',
url: subscriptionURL,
data: JSON.stringify(subData),
cache: false,
success: function(response) {
console.log("Successful Posting of Subscription Data");
},
error: function (jqXHR, exception) {
var msg = '';
if (jqXHR.status === 0) {
msg = 'Verify network connection.';
} else if (jqXHR.status == 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status == 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error:' + jqXHR.responseText;
}
console.log(msg);
}
});
});
def push_subscribe
@user = current_user
session[:subscription_endpoint] = params[:endpoint]
session[:P256DH_key] = params[:p256dh]
session[:auth_key] = params[:auth]
head :ok
welcome_message
end
def welcome_message
@user = current_user
endpoint = session[:subscription_endpoint]
p256dh = session[:P256DH_key]
auth = session[:auth_key]
puts endpoint
puts p256dh
puts auth
if endpoint.include? "googleapis.com"
api_key = ENV['Google_Messaging_Key']
else
api_key = ""
end
puts api_key
message = {
title: "Notification",
body: "Hello #{@user.name}, the time is #{Time.zone.now}",
icon: "https://s3-eu-west-1.amazonaws.com/images/THE_ICON.png"
}
Webpush.payload_send( message: JSON.generate(message), endpoint: endpoint, p256dh: p256dh, auth: auth, key: api_key)
#Webpush.payload_send( endpoint: endpoint, api_key: api_key)
end
@tomgallagher Some combination of endpoint
, p256dh
, and/or auth
is invalid.
First, you could try resubscribing in your browser, which would create a new set of values for those variables.
If you still get the same error, you could try following my tutorial on the subject and make some changes where you see relevant differences between your code and my example. I'm particularly suspicious of these lines:
btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))).replace(/\+/g, '-').replace(/\//g, '_')
btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth')))).replace(/\+/g, '-').replace(/\//g, '_');
I've seen that code in older tutorials that I don't believe is necessary anymore, at least in Chrome or Firefox last time I checked. You should be able to simply call subscription.toJSON()
to serialize it for transport to your backend; you can see how I did this in my blog post.
Hope that's helpful.
Hi - thanks very much for getting back.
I've followed your tutorial already - in fact that was what made me realize that push notifications might now be possible from my Rails 4 app.
Ironically, I couldn't get your example to work, and it was the functionally equivalent lines in Ruby to the ones you don't like in the above Javascript.
JSON.parse(Base64.urlsafe_decode64(encoded_subscription)).with_indifferent_access
I'm trying to introduce this into existing code. I have wrap parameters for JSON in my controller, and I suspected that, with the wrap parameters, I did not need the JSON dump, but the fact that it was all encoded as well meant I lost my way.
I'll try your method again.
So just to be get a better understanding of what's going on, I have three elements:
1) an endpoint, like this:
https://android.googleapis.com/gcm/send/cUzAjNfC37k:APA91bFhy7mgDaVJpohn4k-4_jJG52dX0xIVGog4Gcjt8-zLHYVb_yRyhSpm8UVaxMRxURnspURmAhgmjBjx6O017sAsHAFAZXppF6SGI9zB8v7pNiHR7RMrgfvMfMi62qdp52OJ-VVj
2) a 'p256dh', like this:
BK7oMqjuY1utS6dBzPAbqQgvR12NdRu49IO-j9g6MdxcPZc1nKSW76fY-TSCE9ZO6x5KOkoyNYvWgyQ8KdQ1GJY=
3) and an 'auth' element, like this:
msd8p7ydAkewsyu4ml0PyA==
Am I right in saying that all of these are encoded in some way when I get the subscription from the service worker? Or is it just the keys that are encoded? So how I transport them back, whether .toJSON() or JSON.stringify, doesn't matter? So I am looking to decode them before they get sent as part of the payload web push?
Could you talk a little bit more about the decoding part and this line in particular?
JSON.parse(Base64.urlsafe_decode64(encoded_subscription)).with_indifferent_access
What I don't understand is why any decoding needs to be done at all. I get two values p256dh and auth that are encoded in a variant of Base64 that Chrome call URL-Safe Base64. Are you then saying that these strings need to be decoded before I can use them as inputs for the Webpush.payload_send?
I'm encoding/decoding the entire subscription JSON string to/from the session. This technique is irrelevant to your problem. You should feel free to continue using three separate session values for your app.
Then subscription.toJSON()
will convert your subscription object into the following:
{
endpoint: "https://android.googleapis.com/gcm/send/a-subscription-id",
keys: {
auth: "16ByteString",
p256dh: "65ByteString"
}
}
I suggested that as a convenience. You're right, how you transport them doesn't matter. Just be aware you don't have to do any sort of encoding/decoding on the client or server. The raw values you extract from the subscription object are the same values you pass to the web push API call.
Does that help?
Your note lead me to a "bug" in the tutorial with the session encoding/decoding step - thanks!.
To simplify things for session storage, I'm going to edit the tutorial as follows (I'll need to test it out first, but it "should work" fine):
# app/controllers/subscriptions_controller.rb
class SubscriptionsController < ApplicationController
def create
session[:subscription] = JSON.dump(params.fetch(:subscription, {}))
head :ok
end
end
# app/controllers/push_notifications_controller.rb
class PushNotificationsController < ApplicationController
# ...
def fetch_subscription
subscription = session.fetch(:subscription) do
raise "Cannot create notification: no :subscription in params or session"
end
JSON.parse(subscription).with_indifferent_access
end
end
So, no more base64 encoding to avoid confusion.
Hi - thanks for getting back.
I've just had a look at the code in the gem and webpush.rb has these lines in it.
GCM_URL = 'https://android.googleapis.com/gcm/send'
TEMP_GCM_URL = 'https://gcm-http.googleapis.com/gcm'
and then in the payload_send function:
def payload_send(endpoint:, message: "", p256dh: "", auth: "", **options)
endpoint = endpoint.gsub(GCM_URL, TEMP_GCM_URL)
payload = build_payload(message, p256dh, auth)
Webpush::Request.new(endpoint, options.merge(payload: payload)).perform
end
So I'm not actually sending to the address in my endpoint. Doesn't this make a difference?
In the code it says:
It is temporary URL until supported by the GCM server.
Are you the author? Do you know why this is there?
Thanks a lot for taking the time to help me with all this.
The android
url is the "old" version, still but I believe is still valid. The gcm-http
url I believe is the proper one to use. @zaru may be able to speak to the gsub
line, but it may simply be a future proofing step.
Related SO question: http://stackoverflow.com/questions/32418171/correct-url-for-message-to-gcm-device
Rats. I was hoping that might be the cause of my problems. Sometimes with these API issues, it's simply a case of "turning them on", for example in the Google Developers Console. But I can't find anything related to the GCM API that looks like it needs to be enabled. I've got no filters on my allowed HTTP addresses, the API is current and appears in my list of APIs. It's really quite frustrating!
A simple curl request to the endpoint retrieved from my subscription info returns "Unauthorized Registration" as well. So it's not the gem.
curl "https://android.googleapis.com/gcm/send/fcYabJTn9OU:APA91bGljSEtDZsSk9GZYNpLpFS6LgO5qeq4lPZfPLh8Bk-_r7Iq0dAq3xZ1yL1Ck6FeZ9SdkaUaGITSXb82X9Dwox-xbXqn2Lpv6MbFSAxc2CCzOeR9F1EnKkeyO_AnKfT5fkTv7BNb" --request POST --header "TTL: 60" --header "Content-Length: 0"
Looks like Google have abandoned GCM, with the advent of the Web Push Protocol and VAPID. Maybe that's why I'm having problems with a new project and API
Possible though I'd be really surprised if GCM support was dropped that quickly. Wondering out loud if there may also be some bugs in handling the deprecated protocol as they transition to VAPID...
OK, look thanks for everything. Could you let me know on here if you hear anything? I'd be most grateful! Also, I've checked out the commercial ones, PushCrew and OneSignal - they don't offer the flexibility in terms of responding to push events that I need. So I'm in for the long haul....
@tomgallagher There is an open issue for this library to support VAPID #13 so we may have that soon as a next step.
What helped me solve 400 UnauthorizedRegistration error was migrating from GCM to FCM and updating the API key as explained here: http://stackoverflow.com/questions/37789264/api-key-for-gcm-is-suddenly-invalid-unauthorized-401-error
@rossta hello! I've tried your branch of webpush
gem - https://github.com/rossta/webpush/tree/vapid-spike and I'm receiving #<Net::HTTPCreated 201 Created readbody=true>
but event didn't appear in my browser's service worker as if I send it with same subscription object and keys through node web-push
library. Maybe you know what the problem could be? How can I help you with fixing webpush
gem?
Btw I've found this fresh gem and it works: https://github.com/miyucy/web_push
@gazay Thanks for offering to help! I cleaned up my vapid spike and opened a PR at https://github.com/zaru/webpush/pull/26. I can get webpush working in FFNightly without any issues, but consistently get the UnauthorizedRegistration
400 error in Chrome. I'm not sure yet if this is because of the library or my web app setup (like the manifest or FCM app settings).
If you have time to test it out yourself, let me know what you see. My PR is not well-documented yet, but it looks like you've figured out the usage already. I'll try to add some usage info to the README in the PR soon.
@tomgallagher VAPID support has landed for this gem as of version 0.3.0
. The README provides some instructions for setting it up. Hope that helps with the issues you've been having in Chrome.
That's great! Thanks very much I'll give it a try
@rossta I've been having this exact InvalidSubscription problem with your serviceworker-rails-sandbox
in a weird way, everything works on my laptop through localhost but when I upload the web app to a server I get this error for every single push request on Chrome. On Firefox however everything works fine. Any ideas on how to solve this?
@jorgecuevas92 Hard to say without more info about your setup... do you have the most recent checkout from my project? Have you tried clearing the serviceworker(s) for the app?
@jorgecuevas92 Have you tried unsubscribing and resubscribing on the front-end? Here's a one way to do it from the sandbox.
@rossta @jorgecuevas92 Looks like I have the exact same problem - It works fine through localhost but not in production. I'm wondering if using a Load Balancer (and marshalling headers) isn't part of our problem. And i've tried unsubscribe / resubscribe thing but none of the notifications are going through anyway. :( Don't really know where to debug know ! Any ideas ?
I solved temporarily my problem by switching back to non VAPID method. Also, my chrome was not detecting my manifest file, even tough I placed the "<link rel='manifest'..." in my head section. I had to move it right after the "<title" declaration.
@rossta @dfabreguette-ap I tried unsubscribing and subscribing again with no luck, but it seems that is not related to that since I tried on different devices and the subscription was new on them.
I also noticed that on Opera 42 the result is the same when trying to send a notification.
After that I tried on Chromium and the result was exactly the same.
Since chrome and opera are chromium based browsers it seems safe to say that it is an issue that only happens on chromium based browsers.
I noticed this Sucker Punch error on the application logs, it happens on the three browsers:
Sucker Punch job error for class: 'ActiveJob::QueueAdapters::SuckerPunchAdapter::JobWrapper' args:[{"job_class"=>"WebpushJob", "job_id"=>"73ea0a87-43cd-4a11-bfeb-c901442459eb", "queue_name"=>"default", "priority"=>nil, "arguments"=>["Received a Notification ", {"endpoint"=>"https://fcm.googleapis.com/fcm/send/eCWMA_kR1cY:APA91bFlYt8TSdhBa4yupEcLAPAd62QVh0a8TJ1dO-Hlw-ROBqPFKG4DRgLaBk1N6JBV-Esy-B98VcuMuXB7Dan0jOzThg_1GgzrterZXx3Os4Vxtdnty_2oL4hEVTfZ-OHbuD370", "p256dh"=>"BILYUEKOIvXFI6_R2rL68lfu_A0df5J6RnCB_aiuf7E8tImghAJRSLjTKO0CT3wEYlhn20jojY9HpkilA0E BILYUEKOIvXFI6_R2rL68lfu_A8IgIeoW5J6RnCB_aiuf7E8tImghAJRSLjTKO0CT3wEYlhn20jojY9HpkilA0E=", "auth"=>"j1nRpCySPtryvUFOUrTy5P7YAw==", "_aj_symbol_keys"=>["endpoint", "p256dh", "auth"]}], "locale"=>"en"}]
As for the system setup:
Not sure what else would be useful, let me know so I can post it.
By the way sorry for the late response I got caught up on some other projects :D
Same issue here
same issue here, found the root cause: our server time is not sync with NTP:
https://blog.mozilla.org/services/2016/04/04/using-vapid-with-webpush/
The “expiration” date is the UTC time in seconds when the claim should expire. This should not be longer than 24 hours from the time the request is made. For instance, in Javascript you can use: Math.floor(Date.now() * .001 + 86400).
https://github.com/zaru/webpush/blob/master/lib/webpush/request.rb#L109
Closing due to inactivity and several new releases in the interim.
Hi
I'm making a first venture in push notifications using gcm and your timely gem.
I'm suffering from the error above, even when I send without payload.
Double and triple checked, 1) the id in manifest.json 2) the endpoint, the keys 3) the apikey.
I'm getting everything back from the service worker as I expect but I'm failing on this bit:
Webpush.payload_send( message: JSON.generate(message), endpoint: endpoint, p256dh: p256dh, auth: auth, api_key: api_key)
Any ideas of further things I could try? I'm kind of stuck.
Thanks!
Tom