Skip to content

Instantly share code, notes, and snippets.

@shimx
Last active October 5, 2018 06:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shimx/79530bc2da030511d962fac6e4ce2195 to your computer and use it in GitHub Desktop.
Save shimx/79530bc2da030511d962fac6e4ce2195 to your computer and use it in GitHub Desktop.
Rails4におけるブラウザプッシュ通知(Web Push Notifications)実装 ref: https://qiita.com/shimx/items/d23bb45b34c89f879db3
{
"name": "WEB APP NAME",
"short_name": "WEB APP",
"start_url": "/"
}
var CACHE_VERSION = 'v1';
var CACHE_NAME = CACHE_VERSION + ':sw-cache-';
function onSWInstall(event) {
return event.waitUntil(
caches.open(CACHE_NAME).then(function prefill(cache) {
return cache.addAll([]);
})
);
}
function onSWActivate(event) {
return event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
// Return true if you want to remove this cache,
// but remember that caches are shared across
// the whole origin
return cacheName.indexOf(CACHE_VERSION) !== 0;
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
}
function onSWPush(event) {
// event.data are null when Firefox's debugger push
// event.data are PushMessageData object and cannot exec event.data.json, event.data.text => 'Test push message from DevTools.'
var json = event.data ? event.data.json() : {"title" : "DEBUG TITLE", "body" : "DEBUG BODY"};
return event.waitUntil(
self.registration.showNotification(json.title, {
body: json.body,
icon: json.icon,
data: {
target_url: json.target_url
}
})
);
}
function onSWNotificationClick(event) {
event.notification.close();
return event.waitUntil(
clients.openWindow(event.notification.data != null ? event.notification.data.target_url : '/')
);
}
self.addEventListener('install', onSWInstall);
self.addEventListener('activate', onSWActivate);
self.addEventListener("push", onSWPush);
self.addEventListener("notificationclick", onSWNotificationClick);
// javascript
if (Notification.permission == "denied") {
console.log('ブロックされています');
}
require 'webpush'
payload = {
endpoint: @endpoint, # ブラウザでregistration.pushManager.getSubscription()で取得したsubscriptionのendpoint
p256dh: @p256dh, # 同じくsubscriptionのp256dh
auth: @auth, # 同じくsubscriptionのauth
ttl: @ttl, # 任意の値
vapid: {
subject: 'mailto:info@example.org', # APPサーバのコンタクト用URIとか('mailto:' もしくは 'https://')
public_key: Rails.application.secrets.web_push[:vapid_public_key],
private_key: Rails.application.secrets.web_push[:vapid_private_key]
},
message: {
icon: 'https://example.com/images/demos/icon-512x512.png',
title: title,
body: body,
target_url: target_url # 任意のキー、値
}.to_json
}
# NOTE: payload_sendの例外
# RuntimeError の場合 Webpush::Error
# Error の場合 Webpush::ConfigurationError
# Net::HTTPGone 410 の場合 Webpush::InvalidSubscription
# Net::HTTPSuccess 2XX 以外の場合 Webpush::ResponseError
Webpush.payload_send(payload)
function onSWPush(event) {
var json = event.data.json(); // payloadのmessage
navigator.serviceWorker
#<Net::HTTPBadRequest 400 UnauthorizedRegistration readbody=true>
expiration: 12 * 60 * 60 # defaultは 24 * 60 * 60
# lib/webpush/request.rb
def jwt_payload
{
aud: audience,
exp: Time.now.to_i + expiration,
sub: subject,
}
end
def expiration
@vapid_options.fetch(:expiration, 24*60*60)
end
data: {
target_url: json.target_url
}
function onSWNotificationClick(event) { ... }
# Gemfile
gem 'serviceworker-rails'
gem 'webpush'
require 'webpush'
vapid_key = Webpush.generate_key
puts vapid_key.public_key # 公開鍵
puts vapid_key.private_key # 秘密鍵
WEB_PUSH_VAPID_PUBLIC_KEY="公開鍵"
WEB_PUSH_VAPID_PRIVATE_KEY="秘密鍵"
web_push:
:vapid_public_key: <%= ENV['WEB_PUSH_VAPID_PUBLIC_KEY'] %>
:vapid_private_key: <%= ENV['WEB_PUSH_VAPID_PRIVATE_KEY'] %>
Rails.application.configure do
config.serviceworker.routes.draw do
match "/serviceworker-user.js" => "user/serviceworker/serviceworker.js"
end
end
<!-- 自身が設置した manifest.json のパスを記述する -->
<link rel="manifest" href="/manifest.json">
// WebPushが使えるか
var isEnableWebPushBrowser = function() {
// SWが使えないブラウザ
if (!navigator.serviceWorker) {
return false;
}
var ua = navigator.userAgent.toLowerCase();
// UAにChromeが含まれている為、明示的にEdgeとOpera(Webkit)を弾く
if (ua.indexOf("edge") >= 0 || ua.indexOf("opr") >= 0) {
return false;
}
// Chrome 52 以降OK
if (ua.match(/chrom(e|ium)/)) {
var raw = ua.match(/chrom(e|ium)\/([0-9]+)\./);
if (raw && parseInt(raw[2], 10) >= 52) {
return true;
}
}
// Firefox 48 以降OK
if (ua.indexOf("firefox") >= 0) {
var raw = ua.match(/firefox\/([0-9]+)\./);
if (raw && parseInt(raw[1], 10) >= 48) {
return true;
}
}
return false;
};
// WebPushが使えるブラウザの場合
// NOTE: 今回はWebPushだけなので。他でSW使いたかったら要変更
if (isEnableWebPushBrowser()) {
var convertWebPushSubscriptionJson = function(subscription) {
var jsonData = {};
if (subscription) {
jsonData = {
endpoint: subscription.endpoint,
key: btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))).replace(/\+/g, '-').replace(/\//g, '_'),
auth: btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth')))).replace(/\+/g, '-').replace(/\//g, '_')
};
}
return jsonData;
};
// ブラウザPush通知を有効にする
var webPushSubscribe = function() {
return navigator.serviceWorker.getRegistration().then(function(registration) {
// ブラウザPush通知用VAPID
var vapidPublicKey = new Uint8Array(<%= Base64.urlsafe_decode64(Rails.application.secrets.web_push[:vapid_public_key]).bytes %>);
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey
}).then(function(subscription) {
// API叩いてDBに登録するなどの処理
console.log(convertWebPushSubscriptionJson(subscription));
return;
})["catch"](function(err) {
return console.log(err);
});
});
};
// ブラウザPush通知を無効にする
var webPushUnSubscribe = function() {
return navigator.serviceWorker.getRegistration().then(function(registration) {
return registration.pushManager.getSubscription().then(function(subscription) {
if (!subscription) {
return;
}
return subscription.unsubscribe().then(function(success) {
// API叩いて無効にするなどの処理
return;
})["catch"](function(error) {
return console.log(error);
});
});
});
};
// SWインストール
// NOTE: SWインストール済みかどうかはブラウザ側で判断される
navigator.serviceWorker.register('/serviceworker-user.js', { scope: '/' });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment