No known key found for this signature in database
GPG Key ID: B8254FB7EC3D37F2
22 changed files with 456 additions and 30 deletions
-
4assets/js/embed.js
-
139assets/js/notifications.js
-
200assets/js/sse.js
-
12assets/js/subscribe_widget.js
-
3locales/ar.json
-
3locales/de.json
-
3locales/el.json
-
3locales/en-US.json
-
3locales/eo.json
-
5locales/es.json
-
3locales/eu.json
-
3locales/fr.json
-
3locales/it.json
-
3locales/nb_NO.json
-
5locales/nl.json
-
3locales/pl.json
-
3locales/ru.json
-
3locales/uk.json
-
36src/invidious/views/embed.ecr
-
28src/invidious/views/licenses.ecr
-
7src/invidious/views/preferences.ecr
-
14src/invidious/views/template.ecr
@ -0,0 +1,139 @@ |
|||
var notifications, delivered; |
|||
|
|||
function get_subscriptions(callback, failures) { |
|||
if (failures >= 10) { |
|||
return |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
xhr.responseType = 'json'; |
|||
xhr.timeout = 20000; |
|||
xhr.open('GET', '/api/v1/auth/subscriptions', true); |
|||
xhr.send(null); |
|||
|
|||
xhr.onreadystatechange = function () { |
|||
if (xhr.readyState === 4) { |
|||
if (xhr.status === 200) { |
|||
subscriptions = xhr.response; |
|||
callback(subscriptions); |
|||
} else { |
|||
console.log('Pulling subscriptions failed... ' + failures + '/10'); |
|||
get_subscriptions(callback, failures++) |
|||
} |
|||
} |
|||
} |
|||
|
|||
xhr.ontimeout = function () { |
|||
console.log('Pulling subscriptions failed... ' + failures + '/10'); |
|||
get_subscriptions(callback, failures++); |
|||
} |
|||
} |
|||
|
|||
function create_notification_stream(subscriptions) { |
|||
notifications = new SSE( |
|||
'/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', { |
|||
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.log('Got notification:', notification); |
|||
|
|||
if (start_time < notification.published && !delivered.includes(notification.videoId)) { |
|||
if (Notification.permission === 'granted') { |
|||
var system_notification = |
|||
new Notification((notification.liveNow ? notification_data.live_now_text : notification_data.upload_text).replace('`x`', notification.author), { |
|||
body: notification.title, |
|||
icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname, |
|||
img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname, |
|||
tag: notification.videoId |
|||
}); |
|||
|
|||
system_notification.onclick = function (event) { |
|||
window.open('/watch?v=' + event.currentTarget.tag, '_blank'); |
|||
} |
|||
} |
|||
|
|||
delivered.push(notification.videoId); |
|||
localStorage.setItem('notification_count', parseInt(localStorage.getItem('notification_count') || '0') + 1); |
|||
var notification_ticker = document.getElementById('notification_ticker'); |
|||
|
|||
if (parseInt(localStorage.getItem('notification_count')) > 0) { |
|||
notification_ticker.innerHTML = |
|||
'<span id="notification_count">' + localStorage.getItem('notification_count') + '</span> <i class="icon ion-ios-notifications"></i>'; |
|||
} else { |
|||
notification_ticker.innerHTML = |
|||
'<i class="icon ion-ios-notifications-outline"></i>'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
notifications.onerror = function (event) { |
|||
console.log('Something went wrong with notifications, trying to reconnect...'); |
|||
notifications.close(); |
|||
get_subscriptions(create_notification_stream); |
|||
} |
|||
|
|||
notifications.ontimeout = function (event) { |
|||
console.log('Something went wrong with notifications, trying to reconnect...'); |
|||
notifications.close(); |
|||
get_subscriptions(create_notification_stream); |
|||
} |
|||
|
|||
notifications.stream(); |
|||
} |
|||
|
|||
window.addEventListener('storage', function (e) { |
|||
if (e.key === 'stream' && !e.newValue) { |
|||
if (notifications) { |
|||
localStorage.setItem('stream', true); |
|||
} else { |
|||
setTimeout(function () { |
|||
if (!localStorage.getItem('stream')) { |
|||
get_subscriptions(create_notification_stream); |
|||
localStorage.setItem('stream', true); |
|||
} |
|||
}, Math.random() * 1000 + 10); |
|||
} |
|||
} else if (e.key === 'notification_count') { |
|||
var notification_ticker = document.getElementById('notification_ticker'); |
|||
|
|||
if (parseInt(e.newValue) > 0) { |
|||
notification_ticker.innerHTML = |
|||
'<span id="notification_count">' + e.newValue + '</span> <i class="icon ion-ios-notifications"></i>'; |
|||
} else { |
|||
notification_ticker.innerHTML = |
|||
'<i class="icon ion-ios-notifications-outline"></i>'; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
window.addEventListener('load', function (e) { |
|||
localStorage.setItem('notification_count', document.getElementById('notification_count') ? document.getElementById('notification_count').innerText : '0'); |
|||
|
|||
if (localStorage.getItem('stream')) { |
|||
localStorage.removeItem('stream'); |
|||
} else { |
|||
setTimeout(function () { |
|||
if (!localStorage.getItem('stream')) { |
|||
get_subscriptions(create_notification_stream); |
|||
localStorage.setItem('stream', true); |
|||
} |
|||
}, Math.random() * 1000 + 10); |
|||
} |
|||
}); |
|||
|
|||
window.addEventListener('unload', function (e) { |
|||
if (notifications) { |
|||
localStorage.removeItem('stream'); |
|||
} |
|||
}); |
@ -0,0 +1,200 @@ |
|||
/** |
|||
* Copyright (C) 2016 Maxime Petazzoni <maxime.petazzoni@bulix.org>. |
|||
* All rights reserved. |
|||
*/ |
|||
|
|||
var SSE = function (url, options) { |
|||
if (!(this instanceof SSE)) { |
|||
return new SSE(url, options); |
|||
} |
|||
|
|||
this.INITIALIZING = -1; |
|||
this.CONNECTING = 0; |
|||
this.OPEN = 1; |
|||
this.CLOSED = 2; |
|||
|
|||
this.url = url; |
|||
|
|||
options = options || {}; |
|||
this.headers = options.headers || {}; |
|||
this.payload = options.payload !== undefined ? options.payload : ''; |
|||
this.method = options.method || (this.payload && 'POST' || 'GET'); |
|||
|
|||
this.FIELD_SEPARATOR = ':'; |
|||
this.listeners = {}; |
|||
|
|||
this.xhr = null; |
|||
this.readyState = this.INITIALIZING; |
|||
this.progress = 0; |
|||
this.chunk = ''; |
|||
|
|||
this.addEventListener = function(type, listener) { |
|||
if (this.listeners[type] === undefined) { |
|||
this.listeners[type] = []; |
|||
} |
|||
|
|||
if (this.listeners[type].indexOf(listener) === -1) { |
|||
this.listeners[type].push(listener); |
|||
} |
|||
}; |
|||
|
|||
this.removeEventListener = function(type, listener) { |
|||
if (this.listeners[type] === undefined) { |
|||
return; |
|||
} |
|||
|
|||
var filtered = []; |
|||
this.listeners[type].forEach(function(element) { |
|||
if (element !== listener) { |
|||
filtered.push(element); |
|||
} |
|||
}); |
|||
if (filtered.length === 0) { |
|||
delete this.listeners[type]; |
|||
} else { |
|||
this.listeners[type] = filtered; |
|||
} |
|||
}; |
|||
|
|||
this.dispatchEvent = function(e) { |
|||
if (!e) { |
|||
return true; |
|||
} |
|||
|
|||
e.source = this; |
|||
|
|||
var onHandler = 'on' + e.type; |
|||
if (this.hasOwnProperty(onHandler)) { |
|||
this[onHandler].call(this, e); |
|||
if (e.defaultPrevented) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (this.listeners[e.type]) { |
|||
return this.listeners[e.type].every(function(callback) { |
|||
callback(e); |
|||
return !e.defaultPrevented; |
|||
}); |
|||
} |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
this._setReadyState = function (state) { |
|||
var event = new CustomEvent('readystatechange'); |
|||
event.readyState = state; |
|||
this.readyState = state; |
|||
this.dispatchEvent(event); |
|||
}; |
|||
|
|||
this._onStreamFailure = function(e) { |
|||
this.dispatchEvent(new CustomEvent('error')); |
|||
this.close(); |
|||
} |
|||
|
|||
this._onStreamProgress = function(e) { |
|||
if (this.xhr.status !== 200) { |
|||
this._onStreamFailure(e); |
|||
return; |
|||
} |
|||
|
|||
if (this.readyState == this.CONNECTING) { |
|||
this.dispatchEvent(new CustomEvent('open')); |
|||
this._setReadyState(this.OPEN); |
|||
} |
|||
|
|||
var data = this.xhr.responseText.substring(this.progress); |
|||
this.progress += data.length; |
|||
data.split(/(\r\n|\r|\n){2}/g).forEach(function(part) { |
|||
if (part.trim().length === 0) { |
|||
this.dispatchEvent(this._parseEventChunk(this.chunk.trim())); |
|||
this.chunk = ''; |
|||
} else { |
|||
this.chunk += part; |
|||
} |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
this._onStreamLoaded = function(e) { |
|||
this._onStreamProgress(e); |
|||
|
|||
// Parse the last chunk.
|
|||
this.dispatchEvent(this._parseEventChunk(this.chunk)); |
|||
this.chunk = ''; |
|||
}; |
|||
|
|||
/** |
|||
* Parse a received SSE event chunk into a constructed event object. |
|||
*/ |
|||
this._parseEventChunk = function(chunk) { |
|||
if (!chunk || chunk.length === 0) { |
|||
return null; |
|||
} |
|||
|
|||
var e = {'id': null, 'retry': null, 'data': '', 'event': 'message'}; |
|||
chunk.split(/\n|\r\n|\r/).forEach(function(line) { |
|||
line = line.trimRight(); |
|||
var index = line.indexOf(this.FIELD_SEPARATOR); |
|||
if (index <= 0) { |
|||
// Line was either empty, or started with a separator and is a comment.
|
|||
// Either way, ignore.
|
|||
return; |
|||
} |
|||
|
|||
var field = line.substring(0, index); |
|||
if (!(field in e)) { |
|||
return; |
|||
} |
|||
|
|||
var value = line.substring(index + 1).trimLeft(); |
|||
if (field === 'data') { |
|||
e[field] += value; |
|||
} else { |
|||
e[field] = value; |
|||
} |
|||
}.bind(this)); |
|||
|
|||
var event = new CustomEvent(e.event); |
|||
event.data = e.data; |
|||
event.id = e.id; |
|||
return event; |
|||
}; |
|||
|
|||
this._checkStreamClosed = function() { |
|||
if (this.xhr.readyState === XMLHttpRequest.DONE) { |
|||
this._setReadyState(this.CLOSED); |
|||
} |
|||
}; |
|||
|
|||
this.stream = function() { |
|||
this._setReadyState(this.CONNECTING); |
|||
|
|||
this.xhr = new XMLHttpRequest(); |
|||
this.xhr.addEventListener('progress', this._onStreamProgress.bind(this)); |
|||
this.xhr.addEventListener('load', this._onStreamLoaded.bind(this)); |
|||
this.xhr.addEventListener('readystatechange', this._checkStreamClosed.bind(this)); |
|||
this.xhr.addEventListener('error', this._onStreamFailure.bind(this)); |
|||
this.xhr.addEventListener('abort', this._onStreamFailure.bind(this)); |
|||
this.xhr.open(this.method, this.url); |
|||
for (var header in this.headers) { |
|||
this.xhr.setRequestHeader(header, this.headers[header]); |
|||
} |
|||
this.xhr.send(this.payload); |
|||
}; |
|||
|
|||
this.close = function() { |
|||
if (this.readyState === this.CLOSED) { |
|||
return; |
|||
} |
|||
|
|||
this.xhr.abort(); |
|||
this.xhr = null; |
|||
this._setReadyState(this.CLOSED); |
|||
}; |
|||
}; |
|||
|
|||
// Export our SSE module for npm.js
|
|||
if (typeof exports !== 'undefined') { |
|||
exports.SSE = SSE; |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue