189 lines
6.7 KiB
JavaScript
189 lines
6.7 KiB
JavaScript
import { browser } from "@web/core/browser/browser";
|
|
import { useService } from "@web/core/utils/hooks";
|
|
import { patch } from "@web/core/utils/patch";
|
|
import { WebClient } from "@web/webclient/webclient";
|
|
import { onWillDestroy } from "@odoo/owl";
|
|
import { _t } from "@web/core/l10n/translation";
|
|
|
|
const USER_DEVICES_MODEL = "mail.push.device";
|
|
|
|
patch(WebClient.prototype, {
|
|
/**
|
|
* @override
|
|
*/
|
|
setup() {
|
|
super.setup();
|
|
this.orm = useService("orm");
|
|
this.notification = useService("notification");
|
|
if (this._canSendNativeNotification) {
|
|
this._subscribePush();
|
|
}
|
|
if (browser.navigator.permissions) {
|
|
let notificationPerm;
|
|
const onPermissionChange = () => {
|
|
if (this._canSendNativeNotification) {
|
|
this._subscribePush();
|
|
} else {
|
|
this._unsubscribePush();
|
|
}
|
|
};
|
|
browser.navigator.permissions.query({ name: "notifications" }).then((perm) => {
|
|
notificationPerm = perm;
|
|
notificationPerm.addEventListener("change", onPermissionChange);
|
|
});
|
|
onWillDestroy(() => {
|
|
notificationPerm?.removeEventListener("change", onPermissionChange);
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
*
|
|
* @returns {boolean}
|
|
* @private
|
|
*/
|
|
get _canSendNativeNotification() {
|
|
return browser.Notification?.permission === "granted";
|
|
},
|
|
|
|
/**
|
|
* Subscribe device from push notification
|
|
*
|
|
* @private
|
|
* @return {Promise<void>}
|
|
*/
|
|
async _subscribePush(numberTry = 1) {
|
|
const pushManager = await this.pushManager();
|
|
if (!pushManager) {
|
|
return;
|
|
}
|
|
let subscription = await pushManager.getSubscription();
|
|
const previousEndpoint = browser.localStorage.getItem(`${USER_DEVICES_MODEL}_endpoint`);
|
|
// This may occur if the subscription was refreshed by the browser,
|
|
// but it may also happen if the subscription has been revoked or lost.
|
|
if (!subscription) {
|
|
try {
|
|
subscription = await pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey: await this._getApplicationServerKey(),
|
|
});
|
|
} catch (error) {
|
|
console.warn(error);
|
|
this.notification.add(error.message, {
|
|
title: _t("Failed to enable push notifications"),
|
|
type: "danger",
|
|
sticky: true,
|
|
});
|
|
if (await navigator.brave?.isBrave()) {
|
|
this.notification.add(
|
|
_t(
|
|
"Brave: enable 'Google Services for Push Messaging' to enable push notifications"
|
|
),
|
|
{
|
|
type: "warning",
|
|
sticky: true,
|
|
}
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
browser.localStorage.setItem(`${USER_DEVICES_MODEL}_endpoint`, subscription.endpoint);
|
|
}
|
|
const kwargs = subscription.toJSON();
|
|
if (previousEndpoint && subscription.endpoint !== previousEndpoint) {
|
|
kwargs.previous_endpoint = previousEndpoint;
|
|
}
|
|
try {
|
|
kwargs.vapid_public_key = this._arrayBufferToBase64(
|
|
subscription.options.applicationServerKey
|
|
);
|
|
await this.orm.call(USER_DEVICES_MODEL, "register_devices", [], kwargs);
|
|
} catch (e) {
|
|
const invalidVapidErrorClass = "odoo.addons.mail.tools.jwt.InvalidVapidError";
|
|
const warningMessage = "Error sending subscription information to the server";
|
|
if (e.data?.name === invalidVapidErrorClass) {
|
|
const MAX_TRIES = 2;
|
|
if (numberTry < MAX_TRIES) {
|
|
await subscription.unsubscribe();
|
|
this._subscribePush(numberTry + 1);
|
|
} else {
|
|
console.warn(warningMessage);
|
|
}
|
|
} else {
|
|
console.warn(`${warningMessage}: ${e.data?.debug}`);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Unsubscribe device from push notification
|
|
*
|
|
* @private
|
|
* @return {Promise<void>}
|
|
*/
|
|
async _unsubscribePush() {
|
|
const pushManager = await this.pushManager();
|
|
if (!pushManager) {
|
|
return;
|
|
}
|
|
const subscription = await pushManager.getSubscription();
|
|
if (!subscription) {
|
|
return;
|
|
}
|
|
await this.orm.call(USER_DEVICES_MODEL, "unregister_devices", [], {
|
|
endpoint: subscription.endpoint,
|
|
});
|
|
await subscription.unsubscribe();
|
|
browser.localStorage.removeItem(`${USER_DEVICES_MODEL}_endpoint`);
|
|
},
|
|
|
|
/**
|
|
* Retrieve the PushManager interface of the Push API provides a way to receive notifications from third-party
|
|
* servers as well as request URLs for push notifications.
|
|
*
|
|
* @return {Promise<PushManager>}
|
|
*/
|
|
async pushManager() {
|
|
const registration = await browser.navigator.serviceWorker?.getRegistration();
|
|
return registration?.pushManager;
|
|
},
|
|
|
|
/**
|
|
*
|
|
* The Application Server Key is need to be an Uint8Array.
|
|
* This format is used when the exchanging secret key between client and server.
|
|
* This base64 to Uint8Array implementation is inspired by https://github.com/gbhasha/base64-to-uint8array
|
|
*
|
|
* @private
|
|
* @return {Uint8Array}
|
|
*/
|
|
async _getApplicationServerKey() {
|
|
const vapid_public_key_base64 = await this.orm.call(
|
|
USER_DEVICES_MODEL,
|
|
"get_web_push_vapid_public_key"
|
|
);
|
|
const padding = "=".repeat((4 - (vapid_public_key_base64.length % 4)) % 4);
|
|
const base64 = (vapid_public_key_base64 + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
const rawData = atob(base64);
|
|
const outputArray = new Uint8Array(rawData.length);
|
|
for (let i = 0; i < rawData.length; ++i) {
|
|
outputArray[i] = rawData.charCodeAt(i);
|
|
}
|
|
return outputArray;
|
|
},
|
|
|
|
/**
|
|
* Convert an ArrayBuffer to a base64 string without padding
|
|
* @param buffer {ArrayBuffer}
|
|
* @return {string}
|
|
* @private
|
|
*/
|
|
_arrayBufferToBase64(buffer) {
|
|
const bytes = new Uint8Array(buffer);
|
|
let binary = "";
|
|
for (let i = 0; i < bytes.byteLength; i++) {
|
|
binary += String.fromCharCode(bytes[i]);
|
|
}
|
|
return window.btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
},
|
|
});
|