mirror of
https://github.com/iv-org/invidious.git
synced 2025-06-28 09:38:31 +00:00
198 lines
5.8 KiB
JavaScript
198 lines
5.8 KiB
JavaScript
"use strict";
|
|
var notification_data = JSON.parse(
|
|
document.getElementById("notification_data").textContent,
|
|
);
|
|
|
|
/** Boolean meaning 'some tab have stream' */
|
|
const STORAGE_KEY_STREAM = "stream";
|
|
/** Number of notifications. May be increased or reset */
|
|
const STORAGE_KEY_NOTIF_COUNT = "notification_count";
|
|
|
|
var notifications, delivered;
|
|
var notifications_mock = { close: function () {} };
|
|
|
|
async function get_subscriptions_call() {
|
|
return new Promise((resolve) => {
|
|
helpers.xhr(
|
|
"GET",
|
|
"/api/v1/auth/subscriptions",
|
|
{
|
|
retries: 5,
|
|
entity_name: "subscriptions",
|
|
},
|
|
{
|
|
on200: function (subscriptions) {
|
|
create_notification_stream(subscriptions);
|
|
resolve(subscriptions);
|
|
},
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
// Start the retry mechanism
|
|
const get_subscriptions = exponential_backoff(get_subscriptions_call, 100, 1000);
|
|
|
|
function create_notification_stream(subscriptions) {
|
|
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers
|
|
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
|
|
notifications = new SSE("/api/v1/auth/notifications", {
|
|
withCredentials: true,
|
|
payload:
|
|
"topics=" +
|
|
subscriptions
|
|
.map(function (subscription) {
|
|
return subscription.authorId;
|
|
})
|
|
.join(","),
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
});
|
|
delivered = [];
|
|
|
|
var start_time = Math.round(new Date() / 1000);
|
|
|
|
notifications.onmessage = function (event) {
|
|
if (!event.id) return;
|
|
|
|
var notification = JSON.parse(event.data);
|
|
console.info("Got notification:", notification);
|
|
|
|
// Ignore not actual and delivered notifications
|
|
if (
|
|
start_time > notification.published ||
|
|
delivered.includes(notification.videoId)
|
|
)
|
|
return;
|
|
|
|
delivered.push(notification.videoId);
|
|
|
|
let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0;
|
|
notification_count++;
|
|
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
|
|
|
|
update_ticker_count();
|
|
|
|
// permission for notifications handled on settings page. JS handler is in handlers.js
|
|
if (window.Notification && Notification.permission === "granted") {
|
|
var notification_text = notification.liveNow
|
|
? notification_data.live_now_text
|
|
: notification_data.upload_text;
|
|
notification_text = notification_text.replace("`x`", notification.author);
|
|
|
|
var system_notification = new Notification(notification_text, {
|
|
body: notification.title,
|
|
icon: "/ggpht" + new URL(notification.authorThumbnails[2].url).pathname,
|
|
img: "/ggpht" + new URL(notification.authorThumbnails[4].url).pathname,
|
|
});
|
|
|
|
system_notification.onclick = function (e) {
|
|
open("/watch?v=" + notification.videoId, "_blank");
|
|
};
|
|
}
|
|
};
|
|
|
|
notifications.addEventListener("error", function (e) {
|
|
console.warn(
|
|
"Something went wrong with notifications, trying to reconnect...",
|
|
);
|
|
notifications = notifications_mock;
|
|
|
|
});
|
|
|
|
notifications.stream();
|
|
}
|
|
|
|
function update_ticker_count() {
|
|
var notification_ticker = document.getElementById("notification_ticker");
|
|
|
|
const notification_count = helpers.storage.get(STORAGE_KEY_STREAM);
|
|
if (notification_count > 0) {
|
|
notification_ticker.innerHTML =
|
|
'<span id="notification_count">' +
|
|
notification_count +
|
|
'</span> <i class="icon ion-ios-notifications"></i>';
|
|
} else {
|
|
notification_ticker.innerHTML =
|
|
'<i class="icon ion-ios-notifications-outline"></i>';
|
|
}
|
|
}
|
|
|
|
function start_stream_if_needed() {
|
|
// random wait for other tabs set 'stream' flag
|
|
setTimeout(
|
|
function () {
|
|
if (!helpers.storage.get(STORAGE_KEY_STREAM)) {
|
|
// if no one set 'stream', set it by yourself and start stream
|
|
helpers.storage.set(STORAGE_KEY_STREAM, true);
|
|
notifications = notifications_mock;
|
|
get_subscriptions();
|
|
}
|
|
},
|
|
Math.random() * 1000 + 50,
|
|
); // [0.050 .. 1.050) second
|
|
}
|
|
|
|
addEventListener("storage", function (e) {
|
|
if (e.key === STORAGE_KEY_NOTIF_COUNT) update_ticker_count();
|
|
|
|
// if 'stream' key was removed
|
|
if (
|
|
e.key === STORAGE_KEY_STREAM &&
|
|
!helpers.storage.get(STORAGE_KEY_STREAM)
|
|
) {
|
|
if (notifications) {
|
|
// restore it if we have active stream
|
|
helpers.storage.set(STORAGE_KEY_STREAM, true);
|
|
} else {
|
|
start_stream_if_needed();
|
|
}
|
|
}
|
|
});
|
|
|
|
addEventListener("load", function () {
|
|
var notification_count_el = document.getElementById("notification_count");
|
|
var notification_count = notification_count_el
|
|
? parseInt(notification_count_el.textContent)
|
|
: 0;
|
|
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
|
|
|
|
if (helpers.storage.get(STORAGE_KEY_STREAM))
|
|
helpers.storage.remove(STORAGE_KEY_STREAM);
|
|
start_stream_if_needed();
|
|
});
|
|
|
|
addEventListener("unload", function () {
|
|
// let chance to other tabs to be a streamer via firing 'storage' event
|
|
if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM);
|
|
});
|
|
|
|
function exponential_backoff(
|
|
fn,
|
|
maxRetries = 5,
|
|
initialDelay = 1000,
|
|
randomnessFactor = 0.5,
|
|
) {
|
|
let attempt = 0;
|
|
|
|
return function tryFunction() {
|
|
fn()
|
|
.then((response) => {
|
|
attempt = 0;
|
|
})
|
|
.catch((error) => {
|
|
if (attempt < maxRetries) {
|
|
attempt++;
|
|
let delay = initialDelay * Math.pow(2, attempt); // Exponential backoff
|
|
let randomMultiplier = 1 + Math.random() * randomnessFactor;
|
|
delay = delay * randomMultiplier;
|
|
console.log(
|
|
`Attempt ${attempt} failed. Retrying in ${(delay / 1000).toPrecision(2)} seconds...`,
|
|
);
|
|
setTimeout(tryFunction, delay); // Retry after delay
|
|
} else {
|
|
console.log("Max retries reached. Operation failed:", error);
|
|
}
|
|
});
|
|
}
|
|
}
|