Skip to content

Commit 579d6d3

Browse files
authored
Support authentication with the Basic scheme for Invidious instances (FreeTubeApp#5569)
* Support authentication with the Basic scheme for Invidious instances * I forgot to change this line
1 parent fb4a4d1 commit 579d6d3

File tree

24 files changed

+262
-130
lines changed

24 files changed

+262
-130
lines changed

src/constants.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ const IpcChannels = {
3737
SHOW_VIDEO_STATISTICS: 'show-video-statistics',
3838

3939
PLAYER_CACHE_GET: 'player-cache-get',
40-
PLAYER_CACHE_SET: 'player-cache-set'
40+
PLAYER_CACHE_SET: 'player-cache-set',
41+
42+
SET_INVIDIOUS_AUTHORIZATION: 'set-invidious-authorization'
4143
}
4244

4345
const DBActions = {

src/main/index.js

Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -403,15 +403,19 @@ function runApp() {
403403
sameSite: 'no_restriction',
404404
})
405405

406-
// make InnerTube requests work with the fetch function
407-
// InnerTube rejects requests if the referer isn't YouTube or empty
408-
const innertubeAndMediaRequestFilter = { urls: ['https://www.youtube.com/youtubei/*', 'https://*.googlevideo.com/videoplayback?*'] }
409-
410-
session.defaultSession.webRequest.onBeforeSendHeaders(innertubeAndMediaRequestFilter, ({ requestHeaders, url, resourceType }, callback) => {
411-
requestHeaders.Referer = 'https://www.youtube.com/'
412-
requestHeaders.Origin = 'https://www.youtube.com'
406+
const onBeforeSendHeadersRequestFilter = {
407+
urls: ['https://*/*', 'http://*/*'],
408+
types: ['xhr', 'media', 'image']
409+
}
410+
session.defaultSession.webRequest.onBeforeSendHeaders(onBeforeSendHeadersRequestFilter, ({ requestHeaders, url, resourceType, webContents }, callback) => {
411+
const urlObj = new URL(url)
413412

414413
if (url.startsWith('https://www.youtube.com/youtubei/')) {
414+
// make InnerTube requests work with the fetch function
415+
// InnerTube rejects requests if the referer isn't YouTube or empty
416+
requestHeaders.Referer = 'https://www.youtube.com/'
417+
requestHeaders.Origin = 'https://www.youtube.com'
418+
415419
// Make iOS requests work and look more realistic
416420
if (requestHeaders['x-youtube-client-name'] === '5') {
417421
delete requestHeaders.Referer
@@ -430,41 +434,50 @@ function runApp() {
430434
requestHeaders['Sec-Fetch-Mode'] = 'same-origin'
431435
requestHeaders['X-Youtube-Bootstrap-Logged-In'] = 'false'
432436
}
433-
} else {
437+
} else if (urlObj.origin.endsWith('.googlevideo.com') && urlObj.pathname === '/videoplayback') {
438+
requestHeaders.Referer = 'https://www.youtube.com/'
439+
requestHeaders.Origin = 'https://www.youtube.com'
440+
434441
// YouTube doesn't send the Content-Type header for the media requests, so we shouldn't either
435442
delete requestHeaders['Content-Type']
436-
}
437443

438-
// YouTube throttles the adaptive formats if you request a chunk larger than 10MiB.
439-
// For the DASH formats we are fine as video.js doesn't seem to ever request chunks that big.
440-
// The legacy formats don't have any chunk size limits.
441-
// For the audio formats we need to handle it ourselves, as the browser requests the entire audio file,
442-
// which means that for most videos that are longer than 10 mins, we get throttled, as the audio track file sizes surpass that 10MiB limit.
444+
// YouTube throttles the adaptive formats if you request a chunk larger than 10MiB.
445+
// For the DASH formats we are fine as video.js doesn't seem to ever request chunks that big.
446+
// The legacy formats don't have any chunk size limits.
447+
// For the audio formats we need to handle it ourselves, as the browser requests the entire audio file,
448+
// which means that for most videos that are longer than 10 mins, we get throttled, as the audio track file sizes surpass that 10MiB limit.
443449

444-
// This code checks if the file is larger than the limit, by checking the `clen` query param,
445-
// which YouTube helpfully populates with the content length for us.
446-
// If it does surpass that limit, it then checks if the requested range is larger than the limit
447-
// (seeking right at the end of the video, would result in a small enough range to be under the chunk limit)
448-
// if that surpasses the limit too, it then limits the requested range to 10MiB, by setting the range to `start-${start + 10MiB}`.
449-
if (resourceType === 'media' && url.includes('&mime=audio') && requestHeaders.Range) {
450-
const TEN_MIB = 10 * 1024 * 1024
450+
// This code checks if the file is larger than the limit, by checking the `clen` query param,
451+
// which YouTube helpfully populates with the content length for us.
452+
// If it does surpass that limit, it then checks if the requested range is larger than the limit
453+
// (seeking right at the end of the video, would result in a small enough range to be under the chunk limit)
454+
// if that surpasses the limit too, it then limits the requested range to 10MiB, by setting the range to `start-${start + 10MiB}`.
455+
if (resourceType === 'media' && urlObj.searchParams.get('mime')?.startsWith('audio/') && requestHeaders.Range) {
456+
const TEN_MIB = 10 * 1024 * 1024
451457

452-
const contentLength = parseInt(new URL(url).searchParams.get('clen'))
458+
const contentLength = parseInt(new URL(url).searchParams.get('clen'))
453459

454-
if (contentLength > TEN_MIB) {
455-
const [startStr, endStr] = requestHeaders.Range.split('=')[1].split('-')
460+
if (contentLength > TEN_MIB) {
461+
const [startStr, endStr] = requestHeaders.Range.split('=')[1].split('-')
456462

457-
const start = parseInt(startStr)
463+
const start = parseInt(startStr)
458464

459-
// handle open ended ranges like `0-` and `1234-`
460-
const end = endStr.length === 0 ? contentLength : parseInt(endStr)
465+
// handle open ended ranges like `0-` and `1234-`
466+
const end = endStr.length === 0 ? contentLength : parseInt(endStr)
461467

462-
if (end - start > TEN_MIB) {
463-
const newEnd = start + TEN_MIB
468+
if (end - start > TEN_MIB) {
469+
const newEnd = start + TEN_MIB
464470

465-
requestHeaders.Range = `bytes=${start}-${newEnd}`
471+
requestHeaders.Range = `bytes=${start}-${newEnd}`
472+
}
466473
}
467474
}
475+
} else if (webContents) {
476+
const invidiousAuthorization = invidiousAuthorizations.get(webContents.id)
477+
478+
if (invidiousAuthorization && url.startsWith(invidiousAuthorization.url)) {
479+
requestHeaders.Authorization = invidiousAuthorization.authorization
480+
}
468481
}
469482

470483
// eslint-disable-next-line n/no-callback-literal
@@ -488,8 +501,10 @@ function runApp() {
488501
const imageCache = new ImageCache()
489502

490503
protocol.handle('imagecache', (request) => {
504+
const [requestUrl, rawWebContentsId] = request.url.split('#')
505+
491506
return new Promise((resolve, reject) => {
492-
const url = decodeURIComponent(request.url.substring(13))
507+
const url = decodeURIComponent(requestUrl.substring(13))
493508
if (imageCache.has(url)) {
494509
const cached = imageCache.get(url)
495510

@@ -499,9 +514,22 @@ function runApp() {
499514
return
500515
}
501516

517+
let headers
518+
519+
if (rawWebContentsId) {
520+
const invidiousAuthorization = invidiousAuthorizations.get(parseInt(rawWebContentsId))
521+
522+
if (invidiousAuthorization && url.startsWith(invidiousAuthorization.url)) {
523+
headers = {
524+
Authorization: invidiousAuthorization.authorization
525+
}
526+
}
527+
}
528+
502529
const newRequest = net.request({
503530
method: request.method,
504-
url
531+
url,
532+
headers
505533
})
506534

507535
// Electron doesn't allow certain headers to be set:
@@ -548,19 +576,20 @@ function runApp() {
548576
})
549577
})
550578

551-
const imageRequestFilter = { urls: ['https://*/*', 'http://*/*'] }
579+
const imageRequestFilter = { urls: ['https://*/*', 'http://*/*'], types: ['image'] }
552580
session.defaultSession.webRequest.onBeforeRequest(imageRequestFilter, (details, callback) => {
553581
// the requests made by the imagecache:// handler to fetch the image,
554582
// are allowed through, as their resourceType is 'other'
555-
if (details.resourceType === 'image') {
556-
// eslint-disable-next-line n/no-callback-literal
557-
callback({
558-
redirectURL: `imagecache://${encodeURIComponent(details.url)}`
559-
})
560-
} else {
561-
// eslint-disable-next-line n/no-callback-literal
562-
callback({})
583+
584+
let redirectURL = `imagecache://${encodeURIComponent(details.url)}`
585+
586+
if (details.webContents) {
587+
redirectURL += `#${details.webContents.id}`
563588
}
589+
590+
callback({
591+
redirectURL
592+
})
564593
})
565594

566595
// --- end of `if experimentsDisableDiskCache` ---
@@ -1011,6 +1040,21 @@ function runApp() {
10111040
await asyncFs.writeFile(filePath, new Uint8Array(value))
10121041
})
10131042

1043+
/** @type {Map<number, { url: string, authorization: string }>} */
1044+
const invidiousAuthorizations = new Map()
1045+
1046+
ipcMain.on(IpcChannels.SET_INVIDIOUS_AUTHORIZATION, (event, authorization, url) => {
1047+
if (!isFreeTubeUrl(event.senderFrame.url)) {
1048+
return
1049+
}
1050+
1051+
if (!authorization) {
1052+
invidiousAuthorizations.delete(event.sender.id)
1053+
} else if (typeof authorization === 'string' && typeof url === 'string') {
1054+
invidiousAuthorizations.set(event.sender.id, { authorization, url })
1055+
}
1056+
})
1057+
10141058
// ************************************************* //
10151059
// DB related IPC calls
10161060
// *********** //
@@ -1376,6 +1420,12 @@ function runApp() {
13761420
}
13771421
})
13781422

1423+
app.on('web-contents-created', (_, webContents) => {
1424+
webContents.once('destroyed', () => {
1425+
invidiousAuthorizations.delete(webContents.id)
1426+
})
1427+
})
1428+
13791429
/*
13801430
* Check if an argument was passed and send it over to the GUI (Linux / Windows).
13811431
* Remove freetube:// protocol if present

src/renderer/components/ft-list-channel/ft-list-channel.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ export default defineComponent({
3333
}
3434
},
3535
computed: {
36-
currentInvidiousInstance: function () {
37-
return this.$store.getters.getCurrentInvidiousInstance
36+
currentInvidiousInstanceUrl: function () {
37+
return this.$store.getters.getCurrentInvidiousInstanceUrl
3838
},
3939
listType: function () {
4040
return this.$store.getters.getListType
@@ -81,7 +81,7 @@ export default defineComponent({
8181
// Can be prefixed with `https://` or `//` (protocol relative)
8282
const thumbnailUrl = this.data.authorThumbnails[2].url
8383

84-
this.thumbnail = youtubeImageUrlToInvidious(thumbnailUrl, this.currentInvidiousInstance)
84+
this.thumbnail = youtubeImageUrlToInvidious(thumbnailUrl, this.currentInvidiousInstanceUrl)
8585

8686
this.channelName = this.data.author
8787
this.id = this.data.authorId

src/renderer/components/ft-list-playlist/ft-list-playlist.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ export default defineComponent({
3737
backendPreference: function () {
3838
return this.$store.getters.getBackendPreference
3939
},
40-
currentInvidiousInstance: function () {
41-
return this.$store.getters.getCurrentInvidiousInstance
40+
currentInvidiousInstanceUrl: function () {
41+
return this.$store.getters.getCurrentInvidiousInstanceUrl
4242
},
4343

4444
quickBookmarkPlaylistId() {
@@ -131,7 +131,7 @@ export default defineComponent({
131131
parseInvidiousData: function () {
132132
this.title = this.data.title
133133
if (this.thumbnailCanBeShown) {
134-
this.thumbnail = this.data.playlistThumbnail.replace('https://i.ytimg.com', this.currentInvidiousInstance).replace('hqdefault', 'mqdefault')
134+
this.thumbnail = this.data.playlistThumbnail.replace('https://i.ytimg.com', this.currentInvidiousInstanceUrl).replace('hqdefault', 'mqdefault')
135135
}
136136
this.channelName = this.data.author
137137
this.channelId = this.data.authorId
@@ -159,7 +159,7 @@ export default defineComponent({
159159
if (this.thumbnailCanBeShown && this.data.videos.length > 0) {
160160
const thumbnailURL = `https://i.ytimg.com/vi/${this.data.videos[0].videoId}/mqdefault.jpg`
161161
if (this.backendPreference === 'invidious') {
162-
this.thumbnail = thumbnailURL.replace('https://i.ytimg.com', this.currentInvidiousInstance)
162+
this.thumbnail = thumbnailURL.replace('https://i.ytimg.com', this.currentInvidiousInstanceUrl)
163163
} else {
164164
this.thumbnail = thumbnailURL
165165
}

src/renderer/components/ft-list-video/ft-list-video.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ export default defineComponent({
150150
return this.$store.getters.getBackendPreference
151151
},
152152

153-
currentInvidiousInstance: function () {
154-
return this.$store.getters.getCurrentInvidiousInstance
153+
currentInvidiousInstanceUrl: function () {
154+
return this.$store.getters.getCurrentInvidiousInstanceUrl
155155
},
156156

157157
showPlaylists: function () {
@@ -182,7 +182,7 @@ export default defineComponent({
182182
},
183183

184184
invidiousUrl: function () {
185-
let videoUrl = `${this.currentInvidiousInstance}/watch?v=${this.id}`
185+
let videoUrl = `${this.currentInvidiousInstanceUrl}/watch?v=${this.id}`
186186
// `playlistId` can be undefined
187187
if (this.playlistSharable) {
188188
// `index` seems can be ignored
@@ -192,7 +192,7 @@ export default defineComponent({
192192
},
193193

194194
invidiousChannelUrl: function () {
195-
return `${this.currentInvidiousInstance}/channel/${this.channelId}`
195+
return `${this.currentInvidiousInstanceUrl}/channel/${this.channelId}`
196196
},
197197

198198
youtubeUrl: function () {
@@ -338,7 +338,7 @@ export default defineComponent({
338338

339339
let baseUrl
340340
if (this.backendPreference === 'invidious') {
341-
baseUrl = this.currentInvidiousInstance
341+
baseUrl = this.currentInvidiousInstanceUrl
342342
} else {
343343
baseUrl = 'https://i.ytimg.com'
344344
}

src/renderer/components/ft-playlist-selector/ft-playlist-selector.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ export default defineComponent({
4747
backendPreference: function () {
4848
return this.$store.getters.getBackendPreference
4949
},
50-
currentInvidiousInstance: function () {
51-
return this.$store.getters.getCurrentInvidiousInstance
50+
currentInvidiousInstanceUrl: function () {
51+
return this.$store.getters.getCurrentInvidiousInstanceUrl
5252
},
5353
toBeAddedToPlaylistVideoList: function () {
5454
return this.$store.getters.getToBeAddedToPlaylistVideoList
@@ -129,7 +129,7 @@ export default defineComponent({
129129
if (this.playlist.videos.length > 0) {
130130
const thumbnailURL = `https://i.ytimg.com/vi/${this.playlist.videos[0].videoId}/mqdefault.jpg`
131131
if (this.backendPreference === 'invidious') {
132-
this.thumbnail = thumbnailURL.replace('https://i.ytimg.com', this.currentInvidiousInstance)
132+
this.thumbnail = thumbnailURL.replace('https://i.ytimg.com', this.currentInvidiousInstanceUrl)
133133
} else {
134134
this.thumbnail = thumbnailURL
135135
}

src/renderer/components/ft-profile-channel-list/ft-profile-channel-list.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ export default defineComponent({
4343
backendPreference: function () {
4444
return this.$store.getters.getBackendPreference
4545
},
46-
currentInvidiousInstance: function () {
47-
return this.$store.getters.getCurrentInvidiousInstance
46+
currentInvidiousInstanceUrl: function () {
47+
return this.$store.getters.getCurrentInvidiousInstanceUrl
4848
},
4949
profileList: function () {
5050
return this.$store.getters.getProfileList
@@ -76,7 +76,7 @@ export default defineComponent({
7676
})
7777
subscriptions.forEach((channel) => {
7878
if (this.backendPreference === 'invidious') {
79-
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
79+
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstanceUrl)
8080
}
8181
channel.selected = false
8282
})
@@ -92,7 +92,7 @@ export default defineComponent({
9292
})
9393
subscriptions.forEach((channel) => {
9494
if (this.backendPreference === 'invidious') {
95-
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
95+
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstanceUrl)
9696
}
9797
channel.selected = false
9898
})

src/renderer/components/ft-profile-filter-channels-list/ft-profile-filter-channels-list.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export default defineComponent({
3636
backendPreference: function () {
3737
return this.$store.getters.getBackendPreference
3838
},
39-
currentInvidiousInstance: function () {
40-
return this.$store.getters.getCurrentInvidiousInstance
39+
currentInvidiousInstanceUrl: function () {
40+
return this.$store.getters.getCurrentInvidiousInstanceUrl
4141
},
4242
profileList: function () {
4343
return this.$store.getters.getProfileList
@@ -71,7 +71,7 @@ export default defineComponent({
7171
return index === -1
7272
}).map((channel) => {
7373
if (this.backendPreference === 'invidious') {
74-
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
74+
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstanceUrl)
7575
}
7676
channel.selected = false
7777
return channel
@@ -92,7 +92,7 @@ export default defineComponent({
9292
return index === -1
9393
}).map((channel) => {
9494
if (this.backendPreference === 'invidious') {
95-
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstance)
95+
channel.thumbnail = youtubeImageUrlToInvidious(channel.thumbnail, this.currentInvidiousInstanceUrl)
9696
}
9797
channel.selected = false
9898
return channel

0 commit comments

Comments
 (0)