fix: banner cycling causing scrolling jumps

breaking: remove recommendations [too much bandwidth wasted]
fix: remove all cors
perf: decrease load times by removing preflight, and caching user data
This commit is contained in:
ThaUnknown 2024-04-15 00:20:11 +02:00
parent 6abc544d01
commit d68aa05a0b
14 changed files with 96 additions and 99 deletions

View file

@ -5,7 +5,7 @@
export const logout = writable(false)
function confirm () {
localStorage.removeItem('ALtoken')
localStorage.removeItem('ALviewer')
location.hash = ''
location.reload()
}

View file

@ -13,7 +13,7 @@
const links = [
{
click: () => {
if (anilistClient.userID) {
if (anilistClient.userID?.viewer?.data?.Viewer) {
$logout = true
} else {
IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
@ -85,13 +85,9 @@
text: 'Settings'
}
]
if (anilistClient.userID) {
anilistClient.userID.then(result => {
if (result?.data?.Viewer) {
links[0].image = result.data.Viewer.avatar.medium
links[0].text = 'Logout'
}
})
if (anilistClient.userID?.viewer?.data?.Viewer) {
links[0].image = anilistClient.userID.viewer.data.Viewer.avatar.medium
links[0].text = 'Logout'
}
</script>

View file

@ -22,6 +22,8 @@
</script>
<div class='w-full h-450 position-relative gradient'>
<!-- really shit and hacky way of fixing scroll position jumping when banner changes height -->
<div class='position-absolute top-0 transparent h-450 opacity-0'>.</div>
{#await data}
<SkeletonBanner />
{:then { data }}
@ -33,4 +35,7 @@
.gradient {
background: linear-gradient(0deg, #17191D 0%, #0000 15%, #0000 100%), linear-gradient(90deg, #17191D 0%, rgba(23, 25, 29, 0.885417) 15%, rgba(25, 28, 32, 0) 72%);
}
.opacity-0 {
opacity: 0;
}
</style>

View file

@ -92,21 +92,21 @@ export type Media = {
}
}[]
}
recommendations?: {
edges?: {
node: {
media: {
id: number
title: {
userPreferred: string
}
coverImage?: {
medium: string
}
}
}
}[]
}
// recommendations?: {
// edges?: {
// node: {
// media: {
// id: number
// title: {
// userPreferred: string
// }
// coverImage?: {
// medium: string
// }
// }
// }
// }[]
// }
}
export type Following = {

View file

@ -150,23 +150,24 @@ relations {
}
}
}
},
recommendations{
edges{
node{
mediaRecommendation{
id,
title{
userPreferred
},
coverImage{
medium
}
}
}
}
}`
// recommendations{
// edges{
// node{
// mediaRecommendation{
// id,
// title{
// userPreferred
// },
// coverImage{
// medium
// }
// }
// }
// }
// }
class AnilistClient {
limiter = new Bottleneck({
reservoir: 90,
@ -181,7 +182,7 @@ class AnilistClient {
/** @type {import('simple-store-svelte').Writable<ReturnType<AnilistClient['getUserLists']>>} */
userLists = writable()
userID
userID = alToken
/** @type {Record<number, import('./al.d.ts').Media>} */
mediaCache = {}
@ -201,14 +202,7 @@ class AnilistClient {
return time
})
if (alToken) {
this.userID = this.viewer({ token: alToken }).then(result => {
const lists = result?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
if (!lists.includes('Watched using Miru')) {
this.customList({ lists })
}
return result
})
if (this.userID?.viewer?.data?.Viewer) {
this.userLists.value = this.getUserLists()
// update userLists every 15 mins
setInterval(() => { this.userLists.value = this.getUserLists() }, 1000 * 60 * 15)
@ -250,8 +244,10 @@ class AnilistClient {
* @param {Record<string, any>} variables
*/
alRequest (query, variables) {
/** @type {RequestInit} */
const options = {
method: 'POST',
credentials: 'omit',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
@ -267,7 +263,8 @@ class AnilistClient {
}
})
}
if (alToken) options.headers.Authorization = alToken
// @ts-ignore
if (alToken?.token) options.headers.Authorization = alToken.token
return this.handleRequest(options)
}
@ -440,7 +437,6 @@ class AnilistClient {
/** @returns {Promise<import('./al.d.ts').Query<{ Viewer: import('./al.d.ts').Viewer }>>} */
viewer (variables = {}) {
variables.id = alToken
const query = /* js */`
query{
Viewer{
@ -462,7 +458,7 @@ class AnilistClient {
/** @returns {Promise<import('./al.d.ts').Query<{ MediaListCollection: import('./al.d.ts').MediaListCollection }>>} */
async getUserLists (variables = {}) {
const userId = (await this.userID)?.data?.Viewer.id
const userId = this.userID?.viewer?.data?.Viewer.id
variables.id = userId
const query = /* js */`
query($id: Int){
@ -499,7 +495,7 @@ class AnilistClient {
/** @returns {Promise<import('./al.d.ts').Query<{ MediaList: { status: string, progress: number, repeat: number }}>>} */
async searchIDStatus (variables = {}) {
const userId = (await this.userID)?.data?.Viewer.id
const userId = this.userID?.viewer?.data?.Viewer.id
variables.id = userId
const query = /* js */`
query($id: Int, $mediaId: Int){

View file

@ -1,7 +1,10 @@
import { writable } from 'simple-store-svelte'
import { defaults } from './util.js'
import IPC from '@/modules/ipc.js'
export let alToken = localStorage.getItem('ALtoken') || null
import { anilistClient } from './anilist.js'
import { toast } from 'svelte-sonner'
/** @type {{viewer: import('./al').Query<{Viewer: import('./al').Viewer}>, token: string} | null} */
export let alToken = JSON.parse(localStorage.getItem('ALviewer')) || null
let storedSettings = { ...defaults }
@ -46,8 +49,18 @@ window.addEventListener('paste', ({ clipboardData }) => {
}
})
IPC.on('altoken', handleToken)
function handleToken (data) {
localStorage.setItem('ALtoken', data)
alToken = data
async function handleToken (token) {
alToken = { token, viewer: null }
const viewer = await anilistClient.viewer({ token })
if (!viewer.data?.Viewer) {
toast.error('Failed to sign in with AniList. Please try again.', { description: JSON.stringify(viewer) })
console.error(viewer)
return
}
const lists = viewer?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
if (!lists.includes('Watched using Miru')) {
await anilistClient.customList({ lists })
}
localStorage.setItem('ALviewer', JSON.stringify({ token, viewer }))
location.reload()
}

View file

@ -1,6 +1,6 @@
<script context='module'>
import SectionsManager, { sections } from '@/modules/sections.js'
import { alToken, settings } from '@/modules/settings.js'
import { settings } from '@/modules/settings.js'
import { anilistClient, currentSeason, currentYear } from '@/modules/anilist.js'
const bannerData = anilistClient.search({ method: 'Search', sort: 'POPULARITY_DESC', perPage: 15, onList: false, season: currentSeason, year: currentYear })
@ -15,7 +15,7 @@
for (const sectionTitle of settings.value.homeSections) manager.add(mappedSections[sectionTitle])
if (alToken) {
if (anilistClient.userID?.viewer?.data?.Viewer) {
const userSections = ['Continue Watching', 'Sequels You Missed', 'Your List', 'Completed List', 'Paused List', 'Dropped List', 'Currently Watching List']
anilistClient.userLists.subscribe(value => {

View file

@ -85,7 +85,7 @@
}
function loginButton () {
if (anilistClient.userID) {
if (anilistClient.userID?.viewer?.data?.Viewer) {
$logout = true
} else {
IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
@ -126,16 +126,13 @@
</div>
<div class='pointer my-5 rounded' use:click={loginButton}>
<div class='px-20 py-10 d-flex'>
{#if anilistClient.userID}
{#await anilistClient.userID}
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>login</span>
<div class='font-size-16'>Login With AniList</div>
{:then result}
<span class='material-symbols-outlined rounded mr-10'>
<img src={result.data.Viewer.avatar.medium} class='h-30 rounded' alt='logo' />
</span>
<div class='font-size-16 login-image-text'>Logout</div>
{/await}
{#if anilistClient.userID?.viewer?.data?.Viewer}
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>login</span>
<div class='font-size-16'>Login With AniList</div>
<span class='material-symbols-outlined rounded mr-10'>
<img src={anilistClient.userID.viewer.data.Viewer.avatar.medium} class='h-30 rounded' alt='logo' />
</span>
<div class='font-size-16 login-image-text'>Logout</div>
{:else}
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>login</span>
<div class='font-size-16'>Login With AniList</div>

View file

@ -1,17 +1,16 @@
<script>
import { alToken } from '@/modules/settings.js'
import { anilistClient } from '@/modules/anilist.js'
import { click } from '@/modules/click.js'
import IPC from '@/modules/ipc.js'
/** @type {import('@/modules/al.d.ts').Media} */
export let media
$: following = anilistClient.following({ id: media.id })
$: following = anilistClient.userID?.viewer?.data?.Viewer && anilistClient.following({ id: media.id })
</script>
{#await following then res}
{@const following = res.data.Page.mediaList}
{#if following?.length && alToken}
{@const following = res?.data?.Page?.mediaList}
{#if following?.length}
<div class='w-full d-flex flex-row align-items-center pt-20 mt-10'>
<hr class='w-full' />
<div class='font-size-18 font-weight-semi-bold px-20 text-white'>Following</div>

View file

@ -166,13 +166,13 @@
</div>
</ToggleList>
<Following {media} />
<ToggleList list={media.recommendations.edges.filter(edge => edge.node.mediaRecommendation)} let:item title='Recommendations'>
<!-- <ToggleList list={media.recommendations.edges.filter(edge => edge.node.mediaRecommendation)} let:item title='Recommendations'>
<div class='w-150 mx-15 my-10 rel pointer'
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.mediaRecommendation.id })).data.Media }}>
<img loading='lazy' src={item.node.mediaRecommendation.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
<h5 class='font-weight-bold text-white mb-5'>{item.node.mediaRecommendation.title.userPreferred}</h5>
</div>
</ToggleList>
</ToggleList> -->
<div class='w-full d-flex d-lg-none flex-row align-items-center pt-20 mt-10 pointer'>
<hr class='w-full' />
<div class='font-size-18 font-weight-semi-bold px-20 text-white'>Episodes</div>

View file

@ -26,10 +26,10 @@
atob('d3NzOi8vdHJhY2tlci5maWxlcy5mbTo3MDczL2Fubm91bmNl'),
atob('d3NzOi8vdHJhY2tlci5idG9ycmVudC54eXov')
], code)
p2pt.on('peerconnect', async peer => {
p2pt.on('peerconnect', peer => {
console.log(peer.id)
console.log('connect')
const user = (await anilistClient.userID)?.data?.Viewer || {}
const user = anilistClient.userID?.viewer?.data?.Viewer || {}
p2pt.send(peer,
JSON.stringify({
type: 'init',

View file

@ -1,6 +1,6 @@
{
"name": "Miru",
"version": "5.0.7",
"version": "5.0.8",
"private": true,
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"description": "Stream anime torrents, real-time with no waiting for downloads.",

View file

@ -18,6 +18,8 @@ function createWindow () {
webtorrentWindow = new BrowserWindow({
show: development,
webPreferences: {
webSecurity: false,
allowRunningInsecureContent: false,
nodeIntegration: true,
contextIsolation: false,
backgroundThrottling: false
@ -36,6 +38,8 @@ function createWindow () {
backgroundColor: '#17191c',
autoHideMenuBar: true,
webPreferences: {
webSecurity: false,
allowRunningInsecureContent: false,
enableBlinkFeatures: 'FontAccess, AudioVideoTracks',
backgroundThrottling: false,
preload: path.join(__dirname, '/preload.js')
@ -53,12 +57,6 @@ function createWindow () {
return { action: 'deny' }
})
mainWindow.webContents.session.webRequest.onHeadersReceived(({ responseHeaders }, fn) => {
delete responseHeaders['Access-Control-Allow-Origin']
responseHeaders['access-control-allow-origin'] = ['*']
fn({ responseHeaders })
})
const torrentLoad = webtorrentWindow.loadURL(development ? 'http://localhost:5000/background.html' : `file://${path.join(__dirname, '/background.html')}`)
mainWindow.loadURL(development ? 'http://localhost:5000/app.html' : `file://${path.join(__dirname, '/app.html')}`)
@ -94,8 +92,8 @@ function createWindow () {
mainWindow.webContents.on('render-process-gone', (e, { reason }) => {
if (reason === 'crashed') {
if (++crashcount > 10) {
dialog.showMessageBox({ message: 'Crashed too many times.', title: 'Miru', detail: 'App crashed too many times. For a fix visit https://github.com/ThaUnknown/miru/blob/master/docs/faq.md#miru-crashed-too-many-times', icon: '/renderer/public/logo_filled.png' }).then(() => {
shell.openExternal('https://github.com/ThaUnknown/miru/blob/master/docs/faq.md#miru-crashed-too-many-times')
dialog.showMessageBox({ message: 'Crashed too many times.', title: 'Miru', detail: 'App crashed too many times. For a fix visit https://miru.watch/faq/', icon: '/renderer/public/logo_filled.png' }).then(() => {
shell.openExternal('https://miru.watch/faq/')
app.quit()
})
} else {

View file

@ -5,20 +5,13 @@ export const development = process.env.NODE_ENV?.trim() === 'development'
const flags = [
// not sure if safe?
['disable-gpu-sandbox'],
// not sure if safe?
['disable-direct-composition-video-overlays'],
// not sure if safe?
['double-buffer-compositing'],
// not sure if safe?
['enable-zero-copy'],
// not sure if safe?
['ignore-gpu-blocklist'],
['disable-gpu-sandbox'], ['disable-direct-composition-video-overlays'], ['double-buffer-compositing'], ['enable-zero-copy'], ['ignore-gpu-blocklist'],
// should be safe
['enable-hardware-overlays', 'single-fullscreen,single-on-top,underlay'],
// dv, drdc display compositor uses 2 gpu threads instead of 1, rest is safe performance
['enable-features', 'PlatformEncryptedDolbyVision,EnableDrDc,CanvasOopRasterization,ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes,UseSkiaRenderer,WebAssemblyLazyCompilation'],
// disabling shit, vulkan rendering, native window occlusion calculation, widget layering aka right click context menus [I think] for macOS [I think]
['disable-features', 'Vulkan,CalculateNativeWinOcclusion,WidgetLayering'],
// safe performance stuff
['enable-features', 'PlatformEncryptedDolbyVision,CanvasOopRasterization,ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes,UseSkiaRenderer,WebAssemblyLazyCompilation'],
// disabling shit, vulkan rendering, widget layering aka right click context menus [I think] for macOS [I think]
['disable-features', 'Vulkan,WidgetLayering'],
// utility stuff, aka website security that's useless for a native app:
['autoplay-policy', 'no-user-gesture-required'], ['disable-notifications'], ['disable-logging'], ['disable-permissions-api'], ['no-sandbox'], ['no-zygote'], ['bypasscsp-schemes']
]