diff --git a/scripts/hab-build-and-push.sh b/scripts/hab-build-and-push.sh index f9ffb81ad1ef8e780bdc4efd8499eee9c0eab4c4..1f0b129f00851cfae504fc347b17adfdde7ffb3f 100755 --- a/scripts/hab-build-and-push.sh +++ b/scripts/hab-build-and-push.sh @@ -31,6 +31,7 @@ npm rebuild node-sass # HACK sometimes node-sass build fails npm run build mkdir dist/pages mv dist/*.html dist/pages +mv dist/hub.service.js dist/pages aws s3 sync --acl public-read --cache-control "max-age=31556926" dist/assets "$TARGET_S3_URL/assets" aws s3 sync --acl public-read --cache-control "no-cache" --delete dist/pages "$TARGET_S3_URL/pages/latest" diff --git a/src/hub.js b/src/hub.js index 4c1f5ca4186d920c339e1703ec7bfcd1069cdd1a..a6cb4084259c25a9d5f50cbab54e60956cde7526 100644 --- a/src/hub.js +++ b/src/hub.js @@ -293,7 +293,9 @@ document.addEventListener("DOMContentLoaded", async () => { if (navigator.serviceWorker) { navigator.serviceWorker.register("/hub.service.js"); - navigator.serviceWorker.ready.then(registration => subscriptions.setRegistration(registration)); + navigator.serviceWorker.ready + .then(registration => subscriptions.setRegistration(registration)) + .catch(() => subscriptions.setRegistrationFailed()); } const scene = document.querySelector("a-scene"); @@ -370,7 +372,8 @@ document.addEventListener("DOMContentLoaded", async () => { hmd: availableVREntryTypes.isInHMD }; - const joinPayload = { profile: store.state.profile, context }; + const pushSubscriptionEndpoint = await subscriptions.getCurrentEndpoint(); + const joinPayload = { profile: store.state.profile, push_subscription_endpoint: pushSubscriptionEndpoint, context }; const hubPhxChannel = socket.channel(`hub:${hubId}`, joinPayload); hubPhxChannel @@ -378,6 +381,8 @@ document.addEventListener("DOMContentLoaded", async () => { .receive("ok", async data => { hubChannel.setPhoenixChannel(hubPhxChannel); subscriptions.setHubChannel(hubChannel); + subscriptions.setSubscribed(data.subscriptions.web_push); + remountUI({ initialIsSubscribed: subscriptions.isSubscribed() }); await handleHubChannelJoined(entryManager, hubChannel, data); }) .receive("error", res => { diff --git a/src/subscriptions.js b/src/subscriptions.js index 1d863bd92c94ac3e9bea22b3b830138c64130a23..d9fef895df19593f91a47e2909dd3dc69208fecf 100644 --- a/src/subscriptions.js +++ b/src/subscriptions.js @@ -1,3 +1,5 @@ +import nextTick from "./utils/next-tick.js"; + // Manages web push subscriptions // function urlBase64ToUint8Array(base64String) { @@ -13,10 +15,6 @@ function urlBase64ToUint8Array(base64String) { return outputArray; } -// In local storage: Map of sid -> { endpoint: "<endpoint>" } -// If entry exists, it means there is a subscription to that room, wired to that endpoint. -const LOCAL_STORE_KEY = "___hubs_subscriptions"; - export default class Subscriptions { constructor(hubId) { this.hubId = hubId; @@ -30,55 +28,58 @@ export default class Subscriptions { this.registration = registration; }; - setVapidPublicKey = vapidPublicKey => { - this.vapidPublicKey = vapidPublicKey; + setRegistrationFailed = () => { + this.registration = null; }; - getSubscriptionsFromStorage = () => { - return JSON.parse(localStorage.getItem(LOCAL_STORE_KEY) || "{}"); + setVapidPublicKey = vapidPublicKey => { + this.vapidPublicKey = vapidPublicKey; }; - setSubscriptionsToStorage = subscriptions => { - return localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify(subscriptions)); + setSubscribed = isSubscribed => { + this._isSubscribed = isSubscribed; }; isSubscribed = () => { - if (typeof this._isSubscribed === "undefined") { - this._isSubscribed = !!this.getSubscriptionsFromStorage()[this.hubId]; - } - return this._isSubscribed; }; - toggle = async () => { - const subscriptions = this.getSubscriptionsFromStorage(); + getCurrentEndpoint = async () => { + if (!navigator.serviceWorker) return null; - if (this.isSubscribed()) { - const subscription = await this.registration.pushManager.getSubscription(); - this.hubChannel.unsubscribe(subscription); + // registration becomes null if failed, non null if registered + while (this.registration === undefined) await nextTick(); + if (!this.registration || !this.registration.pushManager) return null; + if ((await this.registration.pushManager.permissionState()) !== "granted") return null; + const sub = await this.registration.pushManager.getSubscription(); + if (!sub) return null; - delete subscriptions[this.hubId]; + return sub && sub.endpoint; + }; + + toggle = async () => { + if (this._isSubscribed) { + const pushSubscription = await this.registration.pushManager.getSubscription(); + const res = await this.hubChannel.unsubscribe(pushSubscription); - if (Object.keys(subscriptions).length === 0 && subscription) { - subscription.unsubscribe(); + if (res && res.has_remaining_subscriptions === false) { + pushSubscription.unsubscribe(); } } else { - let subscription = await this.registration.pushManager.getSubscription(); + let pushSubscription = await this.registration.pushManager.getSubscription(); - if (!subscription) { + if (!pushSubscription) { const convertedVapidKey = urlBase64ToUint8Array(this.vapidPublicKey); - subscription = await this.registration.pushManager.subscribe({ + pushSubscription = await this.registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: convertedVapidKey }); } - subscriptions[this.hubId] = { endpoint: subscription.endpoint }; - this.hubChannel.subscribe(subscription); + this.hubChannel.subscribe(pushSubscription); } - delete this._isSubscribed; - this.setSubscriptionsToStorage(subscriptions); + this._isSubscribed = !this._isSubscribed; }; } diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js index 63dcae408bb874d19c9b5dba47d4aa590cc0c56f..81714efc2fe069ce43cc8d0721c4550a2f42667d 100644 --- a/src/utils/hub-channel.js +++ b/src/utils/hub-channel.js @@ -96,7 +96,7 @@ export default class HubChannel { }; unsubscribe = subscription => { - this.channel.push("unsubscribe", { subscription }); + return new Promise(resolve => this.channel.push("unsubscribe", { subscription }).receive("ok", resolve)); }; sendMessage = (body, type = "chat") => {