mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-19 03:52:04 +00:00
wip: abandoned as capacitor doesn't support transfering messagechannels which are required for callbacks
this is doable by exposing on both ends, but that's too much work for now
This commit is contained in:
parent
45b03c068a
commit
d47aa0cd23
43 changed files with 509 additions and 432 deletions
|
|
@ -15,6 +15,9 @@
|
|||
"node": true,
|
||||
"serviceworker": true
|
||||
},
|
||||
"rules": {
|
||||
"n/no-callback-literal": 0
|
||||
},
|
||||
"extends": ["plugin:svelte/recommended", "standard"],
|
||||
"plugins": ["svelte"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import { App } from '@capacitor/app'
|
|||
import { Browser } from '@capacitor/browser'
|
||||
import IPC from './ipc.js'
|
||||
|
||||
IPC.on('open', url => Browser.open({ url }))
|
||||
|
||||
App.addListener('appUrlOpen', ({ url }) => handleProtocol(url))
|
||||
|
||||
// schema: miru://key/value
|
||||
|
|
|
|||
|
|
@ -1,25 +1,52 @@
|
|||
import { NodeJS } from 'capacitor-nodejs'
|
||||
import EventEmitter from 'events'
|
||||
import { NodeJS } from 'capacitor-nodejs'
|
||||
import { wrap } from 'comlink'
|
||||
|
||||
const ready = NodeJS.whenReady()
|
||||
|
||||
const main = new EventEmitter()
|
||||
|
||||
export default main
|
||||
|
||||
main.on('portRequest', async () => {
|
||||
globalThis.port = {
|
||||
onmessage: cb => {
|
||||
NodeJS.addListener('ipc', ({ args }) => cb(args[0]))
|
||||
function capacitorEndpoint (nep) {
|
||||
const listeners = new WeakMap()
|
||||
return {
|
||||
postMessage: async (...args) => {
|
||||
await ready
|
||||
nep.send({ eventName: 'message', args })
|
||||
},
|
||||
postMessage: (data, b) => {
|
||||
NodeJS.send({ eventName: 'ipc', args: [{ data }] })
|
||||
addEventListener: (_, eh) => {
|
||||
const l = (data) => {
|
||||
if ('handleEvent' in eh) {
|
||||
eh.handleEvent({ data })
|
||||
} else {
|
||||
eh({ data })
|
||||
}
|
||||
}
|
||||
const handle = nep.addListener('message', ({ args }) => l(...args))
|
||||
listeners.set(eh, handle)
|
||||
},
|
||||
removeEventListener: (_, eh) => {
|
||||
const l = listeners.get(eh)
|
||||
if (!l) return
|
||||
nep.removeListener(l)
|
||||
listeners.delete(eh)
|
||||
}
|
||||
}
|
||||
await ready
|
||||
NodeJS.send({ eventName: 'port-init', args: [localStorage.getItem('settings')] })
|
||||
main.emit('port')
|
||||
})
|
||||
}
|
||||
export default {
|
||||
discord: {
|
||||
on () {},
|
||||
handleDiscordStatus () {},
|
||||
showDiscordStatus () {}
|
||||
},
|
||||
protocol: new EventEmitter(),
|
||||
updater: new EventEmitter(),
|
||||
close () {},
|
||||
open () {},
|
||||
doh () {},
|
||||
angle () {},
|
||||
dialog () {},
|
||||
async version () {}
|
||||
}
|
||||
|
||||
export const TorrentClient = wrap(capacitorEndpoint(NodeJS))
|
||||
|
||||
const [_platform, arch] = navigator.platform.split(' ')
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import TorrentClient from 'common/modules/webtorrent.js'
|
|||
import { channel } from 'bridge'
|
||||
import { env } from 'node:process'
|
||||
import { statfs } from 'fs/promises'
|
||||
import { expose } from 'comlink'
|
||||
|
||||
async function storageQuota (directory) {
|
||||
const { bsize, bavail } = await statfs(directory)
|
||||
|
|
@ -16,19 +17,34 @@ if (typeof localStorage === 'undefined') {
|
|||
}
|
||||
}
|
||||
|
||||
channel.on('port-init', data => {
|
||||
localStorage.setItem('settings', data)
|
||||
const port = {
|
||||
onmessage: _ => {},
|
||||
postMessage: data => {
|
||||
channel.send('ipc', { data })
|
||||
// localStorage.setItem('settings', data)
|
||||
|
||||
function capacitorEndpoint (nep) {
|
||||
const listeners = new WeakMap()
|
||||
return {
|
||||
postMessage: (...args) => {
|
||||
nep.send('message', ...args)
|
||||
},
|
||||
addEventListener: (_, eh) => {
|
||||
const l = (data) => {
|
||||
if ('handleEvent' in eh) {
|
||||
eh.handleEvent({ data })
|
||||
} else {
|
||||
eh({ data })
|
||||
}
|
||||
}
|
||||
nep.on('message', l)
|
||||
listeners.set(eh, l)
|
||||
},
|
||||
removeEventListener: (_, eh) => {
|
||||
const l = listeners.get(eh)
|
||||
if (!l) return
|
||||
nep.removeListener('message', l)
|
||||
listeners.delete(eh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channel.on('ipc', a => port.onmessage(a))
|
||||
channel.emit('port', ({
|
||||
ports: [port]
|
||||
}))
|
||||
})
|
||||
globalThis.client = new TorrentClient(storageQuota, 'node', { torrentPath: env.TMPDIR })
|
||||
|
||||
globalThis.client = new TorrentClient(channel, storageQuota, 'node', { torrentPath: env.TMPDIR })
|
||||
expose(globalThis.client, capacitorEndpoint(channel))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script context='module'>
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { proxy } from 'comlink'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
|
||||
|
|
@ -10,10 +11,11 @@
|
|||
view.set(null)
|
||||
view.set((await anilistClient.searchIDSingle({ id: anime })).data.Media)
|
||||
}
|
||||
IPC.on('open-anime', handleAnime)
|
||||
IPC.on('schedule', () => {
|
||||
IPC.protocol.on('open-anime', proxy(handleAnime))
|
||||
IPC.protocol.on('schedule', proxy(() => {
|
||||
page.set('schedule')
|
||||
})
|
||||
}))
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
if (anilistClient.userID) {
|
||||
$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
|
||||
IPC.open('https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token')
|
||||
if (platformMap[window.version.platform] === 'Linux') {
|
||||
toast('Support Notification', {
|
||||
description: "If your linux distribution doesn't support custom protocol handlers, you can simply paste the full URL into the app.",
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
},
|
||||
{
|
||||
click: () => {
|
||||
IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/')
|
||||
IPC.open('https://github.com/sponsors/ThaUnknown/')
|
||||
},
|
||||
icon: 'favorite',
|
||||
text: 'Support This App',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
if (anilistClient.userID) {
|
||||
$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
|
||||
IPC.open('https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
|
||||
if (platformMap[window.version.platform] === 'Linux') {
|
||||
toast('Support Notification', {
|
||||
description: "If your linux distribution doesn't support custom protocol handlers, you can simply paste the full URL into the app.",
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
},
|
||||
{
|
||||
click: () => {
|
||||
IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/')
|
||||
IPC.open('https://github.com/sponsors/ThaUnknown/')
|
||||
},
|
||||
icon: 'favorite',
|
||||
text: 'Support This App',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"three": ["./types.d.ts"],
|
||||
"rxjs": ["./types.d.ts"],
|
||||
},
|
||||
"checkJs": true,
|
||||
"target": "ESNext",
|
||||
|
|
@ -17,5 +19,9 @@
|
|||
"types": ["./types.d.ts"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "build", "git_modules", ".svelte-kit", "**/node_modules", "**/dist", "**/build", "**/git_modules", "**/.svelte-kit", "**/node_modules/*", "**/dist/*", "**/build/*", "**/git_modules/*", "**/.svelte-kit/*"]
|
||||
"exclude": [
|
||||
"node_modules", "dist", "build", "git_modules", ".svelte-kit", "public", "android", "@types/three",
|
||||
"**/node_modules", "**/dist", "**/build", "**/git_modules", "**/.svelte-kit", "**/public", "**/android", "**/@types/three",
|
||||
"**/node_modules/*", "**/dist/*", "**/build/*", "**/git_modules/*", "**/.svelte-kit/*", "**/public/*", "**/android/*", "**@types/three/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -1 +1,3 @@
|
|||
export default window.IPC
|
||||
export default globalThis.Native
|
||||
|
||||
export const TorrentClient = globalThis.Torrent
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export default class Parser {
|
|||
parsed = false
|
||||
/** @type {Metadata} */
|
||||
metadata = null
|
||||
/** @type {import('./webtorrent.js').default} */
|
||||
client = null
|
||||
file = null
|
||||
destroyed = false
|
||||
|
|
@ -19,13 +20,13 @@ export default class Parser {
|
|||
this.parsed = true
|
||||
this.destroy()
|
||||
} else {
|
||||
this.client.dispatch('tracks', tracks)
|
||||
this.client.emit('tracks', tracks)
|
||||
}
|
||||
})
|
||||
|
||||
this.metadata.getChapters().then(chapters => {
|
||||
if (this.destroyed) return
|
||||
this.client.dispatch('chapters', chapters)
|
||||
this.client.emit('chapters', chapters)
|
||||
})
|
||||
|
||||
this.metadata.getAttachments().then(files => {
|
||||
|
|
@ -33,14 +34,14 @@ export default class Parser {
|
|||
for (const file of files) {
|
||||
if (fontRx.test(file.filename) || file.mimetype.toLowerCase().includes('font')) {
|
||||
// this is cursed, but required, as capacitor-node's IPC hangs for 2mins when runnig on 32bit android when sending uint8's
|
||||
this.client.dispatch('file', { data: JSON.stringify([...file.data]) })
|
||||
this.client.emit('file', { data: JSON.stringify([...file.data]) })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.metadata.on('subtitle', (subtitle, trackNumber) => {
|
||||
if (this.destroyed) return
|
||||
this.client.dispatch('subtitle', { subtitle, trackNumber })
|
||||
this.client.emit('subtitle', { subtitle, trackNumber })
|
||||
})
|
||||
|
||||
if (this.file.name.endsWith('.mkv') || this.file.name.endsWith('.webm')) {
|
||||
|
|
|
|||
|
|
@ -35,16 +35,7 @@ async function updatePeerCounts (entries) {
|
|||
const id = crypto.randomUUID()
|
||||
|
||||
const updated = await Promise.race([
|
||||
new Promise(resolve => {
|
||||
function check ({ detail }) {
|
||||
if (detail.id !== id) return
|
||||
client.removeListener('scrape', check)
|
||||
resolve(detail.result)
|
||||
console.log(detail)
|
||||
}
|
||||
client.on('scrape', check)
|
||||
client.send('scrape', { id, infoHashes: entries.map(({ hash }) => hash) })
|
||||
}),
|
||||
client.scrape({ id, infoHashes: entries.map(({ hash }) => hash) }),
|
||||
sleep(5000)
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { writable } from 'simple-store-svelte'
|
||||
import { proxy } from 'comlink'
|
||||
import { defaults } from './util.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
export let alToken = localStorage.getItem('ALtoken') || null
|
||||
|
|
@ -45,7 +46,7 @@ window.addEventListener('paste', ({ clipboardData }) => {
|
|||
}
|
||||
}
|
||||
})
|
||||
IPC.on('altoken', handleToken)
|
||||
IPC.protocol.on('altoken', proxy(handleToken))
|
||||
function handleToken (data) {
|
||||
localStorage.setItem('ALtoken', data)
|
||||
alToken = data
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { proxy } from 'comlink'
|
||||
import JASSUB from 'jassub'
|
||||
import { toTS, subRx, videoRx } from './util.js'
|
||||
import { settings } from '@/modules/settings.js'
|
||||
|
|
@ -39,14 +40,14 @@ export default class Subtitles {
|
|||
this.videoFiles = files.filter(file => videoRx.test(file.name))
|
||||
this.subtitleFiles = []
|
||||
this.timeout = null
|
||||
this.handleFile = ({ detail }) => {
|
||||
this.handleFile = (detail) => {
|
||||
if (this.selected) {
|
||||
const uint8 = new Uint8Array(JSON.parse(detail.data))
|
||||
this.fonts.push(uint8)
|
||||
this.renderer?.addFont(uint8)
|
||||
}
|
||||
}
|
||||
this.handleSubtitle = ({ detail }) => {
|
||||
this.handleSubtitle = (detail) => {
|
||||
const { subtitle, trackNumber } = detail
|
||||
if (this.selected) {
|
||||
const string = JSON.stringify(subtitle)
|
||||
|
|
@ -59,7 +60,7 @@ export default class Subtitles {
|
|||
}
|
||||
}
|
||||
|
||||
this.handleTracks = ({ detail }) => {
|
||||
this.handleTracks = (detail) => {
|
||||
if (this.selected) {
|
||||
for (const track of detail) {
|
||||
if (!this.tracks[track.number]) {
|
||||
|
|
@ -110,14 +111,14 @@ export default class Subtitles {
|
|||
if (subRx.test(file.name)) this.addSingleSubtitleFile(file)
|
||||
}
|
||||
}
|
||||
this.handleSubtitleFile = ({ detail }) => {
|
||||
this.handleSubtitleFile = (detail) => {
|
||||
this.addSingleSubtitleFile(new File([detail.data], detail.name))
|
||||
}
|
||||
|
||||
client.on('tracks', this.handleTracks)
|
||||
client.on('subtitle', this.handleSubtitle)
|
||||
client.on('file', this.handleFile)
|
||||
client.on('subtitleFile', this.handleSubtitleFile)
|
||||
client.on('tracks', proxy(this.handleTracks))
|
||||
client.on('subtitle', proxy(this.handleSubtitle))
|
||||
client.on('file', proxy(this.handleFile))
|
||||
client.on('subtitleFile', proxy(this.handleSubtitleFile))
|
||||
clipboard.on('text', this.handleClipboardText)
|
||||
clipboard.on('files', this.handleClipboardFiles)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,68 +2,52 @@ import { files, media } from '../views/Player/MediaHandler.svelte'
|
|||
import { page } from '@/App.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import clipboard from './clipboard.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import 'browser-event-target-emitter'
|
||||
import { TorrentClient } from '@/modules/ipc.js'
|
||||
import { proxy } from 'comlink'
|
||||
|
||||
const torrentRx = /(^magnet:){1}|(^[A-F\d]{8,40}$){1}|(.*\.torrent$){1}/i
|
||||
|
||||
class TorrentWorker extends EventTarget {
|
||||
constructor () {
|
||||
super()
|
||||
this.ready = new Promise(resolve => {
|
||||
IPC.once('port', () => {
|
||||
this.port = window.port
|
||||
this.port.onmessage(this.handleMessage.bind(this))
|
||||
resolve()
|
||||
})
|
||||
IPC.emit('portRequest')
|
||||
})
|
||||
clipboard.on('text', ({ detail }) => {
|
||||
for (const { text } of detail) {
|
||||
if (torrentRx.exec(text)) {
|
||||
media.set(null)
|
||||
add(text)
|
||||
}
|
||||
}
|
||||
})
|
||||
clipboard.on('files', async ({ detail }) => {
|
||||
for (const file of detail) {
|
||||
if (file.name.endsWith('.torrent')) {
|
||||
media.set(null)
|
||||
add(new Uint8Array(await file.arrayBuffer()))
|
||||
}
|
||||
}
|
||||
})
|
||||
clipboard.on('text', ({ detail }) => {
|
||||
for (const { text } of detail) {
|
||||
if (torrentRx.exec(text)) {
|
||||
media.set(null)
|
||||
add(text)
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage ({ data }) {
|
||||
this.emit(data.type, data.data)
|
||||
})
|
||||
clipboard.on('files', async ({ detail }) => {
|
||||
for (const file of detail) {
|
||||
if (file.name.endsWith('.torrent')) {
|
||||
media.set(null)
|
||||
add(new Uint8Array(await file.arrayBuffer()))
|
||||
}
|
||||
}
|
||||
|
||||
async send (type, data, transfer) {
|
||||
await this.ready
|
||||
console.info('Torrent: sending message', { type, data })
|
||||
this.port.postMessage({ type, data }, transfer)
|
||||
}
|
||||
}
|
||||
|
||||
export const client = new TorrentWorker()
|
||||
|
||||
client.send('load', localStorage.getItem('torrent'))
|
||||
|
||||
client.on('files', ({ detail }) => {
|
||||
files.set(detail)
|
||||
})
|
||||
|
||||
client.on('error', ({ detail }) => {
|
||||
globalThis.ddd = TorrentClient
|
||||
|
||||
/** @type {import('comlink').Remote<import('@/modules/webtorrent.js').default>} */
|
||||
export const client = TorrentClient
|
||||
|
||||
queueMicrotask(() => client.loadLastTorrent(localStorage.getItem('torrent')))
|
||||
|
||||
window.dd = client
|
||||
window.aa = proxy
|
||||
|
||||
client.on('files', proxy((detail) => {
|
||||
files.set(detail)
|
||||
}))
|
||||
|
||||
client.on('error', proxy((detail) => {
|
||||
console.error(detail)
|
||||
toast.error('Torrent Error', { description: detail.message || detail })
|
||||
})
|
||||
}))
|
||||
|
||||
client.on('warn', ({ detail }) => {
|
||||
client.on('warn', proxy((detail) => {
|
||||
console.error(detail)
|
||||
toast.warning('Torrent Warning', { description: detail.message || detail })
|
||||
})
|
||||
}))
|
||||
|
||||
export async function add (torrentID, hide) {
|
||||
if (torrentID) {
|
||||
|
|
@ -71,6 +55,6 @@ export async function add (torrentID, hide) {
|
|||
localStorage.setItem('torrent', torrentID)
|
||||
files.set([])
|
||||
if (!hide) page.set('player')
|
||||
client.send('torrent', torrentID)
|
||||
client.addTorrent(torrentID)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { EventEmitter } from 'node:events'
|
||||
import WebTorrent from 'webtorrent'
|
||||
import HTTPTracker from 'bittorrent-tracker/lib/client/http-tracker.js'
|
||||
import { hex2bin, arr2hex, text2arr } from 'uint8-util'
|
||||
|
|
@ -30,12 +31,13 @@ try {
|
|||
storedSettings = JSON.parse(localStorage.getItem('settings')) || {}
|
||||
} catch (error) {}
|
||||
|
||||
export default class TorrentClient extends WebTorrent {
|
||||
export default class TorrentClient {
|
||||
static excludedErrorMessages = ['WebSocket', 'User-Initiated Abort, reason=', 'Connection failed.']
|
||||
|
||||
constructor (ipc, storageQuota, serverMode, settingOverrides = {}, controller) {
|
||||
emitter = new EventEmitter()
|
||||
client
|
||||
constructor (storageQuota, serverMode, settingOverrides = {}, controller) {
|
||||
const settings = { ...defaults, ...storedSettings, ...settingOverrides }
|
||||
super({
|
||||
this.client = new WebTorrent({
|
||||
dht: !settings.torrentDHT,
|
||||
maxConns: settings.maxConns,
|
||||
downloadLimit: settings.torrentSpeed * 1048576 || 0,
|
||||
|
|
@ -43,18 +45,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
torrentPort: settings.torrentPort || 0,
|
||||
dhtPort: settings.dhtPort || 0
|
||||
})
|
||||
this._ready = new Promise(resolve => {
|
||||
ipc.on('port', ({ ports }) => {
|
||||
this.message = ports[0].postMessage.bind(ports[0])
|
||||
ports[0].onmessage = ({ data }) => {
|
||||
if (data.type === 'load') this.loadLastTorrent(data.data)
|
||||
if (data.type === 'destroy') this.destroy()
|
||||
this.handleMessage({ data })
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
ipc.on('destroy', this.destroy.bind(this))
|
||||
})
|
||||
|
||||
this.settings = settings
|
||||
|
||||
this.serverMode = serverMode
|
||||
|
|
@ -64,19 +55,17 @@ export default class TorrentClient extends WebTorrent {
|
|||
this.parsed = false
|
||||
|
||||
setInterval(() => {
|
||||
this.dispatch('stats', {
|
||||
numPeers: (this.torrents.length && this.torrents[0].numPeers) || 0,
|
||||
uploadSpeed: (this.torrents.length && this.torrents[0].uploadSpeed) || 0,
|
||||
downloadSpeed: (this.torrents.length && this.torrents[0].downloadSpeed) || 0
|
||||
this.emit('stats', {
|
||||
numPeers: (this.client.torrents.length && this.client.torrents[0].numPeers) || 0,
|
||||
uploadSpeed: (this.client.torrents.length && this.client.torrents[0].uploadSpeed) || 0,
|
||||
downloadSpeed: (this.client.torrents.length && this.client.torrents[0].downloadSpeed) || 0,
|
||||
progress: (this.client.torrents[0]?.pieces && this.current?.progress) || 0
|
||||
})
|
||||
}, 200)
|
||||
setInterval(() => {
|
||||
if (this.torrents[0]?.pieces) this.dispatch('progress', this.current?.progress)
|
||||
}, 2000)
|
||||
this.on('torrent', this.handleTorrent.bind(this))
|
||||
}, 1000)
|
||||
this.client.on('torrent', this.handleTorrent.bind(this))
|
||||
|
||||
const createServer = controller => {
|
||||
this.server = this.createServer({ controller }, serverMode)
|
||||
this.server = this.client.createServer({ controller }, serverMode)
|
||||
this.server.listen(0, () => {})
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +80,24 @@ export default class TorrentClient extends WebTorrent {
|
|||
}
|
||||
|
||||
process.on('uncaughtException', this.dispatchError.bind(this))
|
||||
this.on('error', this.dispatchError.bind(this))
|
||||
this.client.on('error', this.dispatchError.bind(this))
|
||||
}
|
||||
|
||||
emit (event, ...args) {
|
||||
if (!this.events[event]?.length) return
|
||||
for (const cb of this.events[event]) {
|
||||
cb(...args)
|
||||
}
|
||||
}
|
||||
|
||||
off (event) {
|
||||
delete this.events[event]
|
||||
}
|
||||
|
||||
events = {}
|
||||
|
||||
on (event, cb) {
|
||||
(this.events[event] ||= []).push(cb)
|
||||
}
|
||||
|
||||
loadLastTorrent (t) {
|
||||
|
|
@ -123,19 +129,19 @@ export default class TorrentClient extends WebTorrent {
|
|||
for (const file of torrent.files) {
|
||||
file.deselect()
|
||||
}
|
||||
this.dispatch('warn', 'Detected Large Torrent! To Conserve Drive Space Files Will Be Downloaded Selectively Instead Of Downloading The Entire Torrent.')
|
||||
this.emit('warn', 'Detected Large Torrent! To Conserve Drive Space Files Will Be Downloaded Selectively Instead Of Downloading The Entire Torrent.')
|
||||
}
|
||||
this.dispatch('files', files)
|
||||
this.dispatch('magnet', { magnet: torrent.magnetURI, hash: torrent.infoHash })
|
||||
this.emit('files', files)
|
||||
this.emit('magnet', { magnet: torrent.magnetURI, hash: torrent.infoHash })
|
||||
localStorage.setItem('torrent', JSON.stringify([...torrent.torrentFile]))
|
||||
|
||||
if (torrent.length > await this.storageQuota(torrent.path)) {
|
||||
this.dispatch('error', 'Torrent Too Big! This Torrent Exceeds The Selected Drive\'s Available Space. Change Download Location In Torrent Settings To A Drive With More Space And Restart The App!')
|
||||
this.emit('error', 'Torrent Too Big! This Torrent Exceeds The Selected Drive\'s Available Space. Change Download Location In Torrent Settings To A Drive With More Space And Restart The App!')
|
||||
}
|
||||
}
|
||||
|
||||
async findFontFiles (targetFile) {
|
||||
const files = this.torrents[0].files
|
||||
const files = this.client.torrents[0].files
|
||||
const fontFiles = files.filter(file => fontRx.test(file.name))
|
||||
|
||||
const map = {}
|
||||
|
|
@ -151,12 +157,12 @@ export default class TorrentClient extends WebTorrent {
|
|||
for (const file of Object.values(map)) {
|
||||
const data = await file.arrayBuffer()
|
||||
if (targetFile !== this.current) return
|
||||
this.dispatch('file', { data: new Uint8Array(data) }, [data])
|
||||
this.emit('file', { data: new Uint8Array(data) }, [data])
|
||||
}
|
||||
}
|
||||
|
||||
async findSubtitleFiles (targetFile) {
|
||||
const files = this.torrents[0].files
|
||||
const files = this.client.torrents[0].files
|
||||
const videoFiles = files.filter(file => videoRx.test(file.name))
|
||||
const videoName = targetFile.name.substring(0, targetFile.name.lastIndexOf('.')) || targetFile.name
|
||||
// array of subtitle files that match video name, or all subtitle files when only 1 vid file
|
||||
|
|
@ -166,22 +172,24 @@ export default class TorrentClient extends WebTorrent {
|
|||
for (const file of subfiles) {
|
||||
const data = await file.arrayBuffer()
|
||||
if (targetFile !== this.current) return
|
||||
this.dispatch('subtitleFile', { name: file.name, data: new Uint8Array(data) }, [data])
|
||||
this.emit('subtitleFile', { name: file.name, data: new Uint8Array(data) }, [data])
|
||||
}
|
||||
}
|
||||
|
||||
_scrape ({ id, infoHashes }) {
|
||||
this.trackers.cat._request(this.trackers.cat.scrapeUrl, { info_hash: infoHashes.map(infoHash => hex2bin(infoHash)) }, (err, data) => {
|
||||
if (err) {
|
||||
this.dispatch('error', err)
|
||||
return this.dispatch('scrape', { id, result: [] })
|
||||
}
|
||||
const { files } = data
|
||||
const result = []
|
||||
for (const [key, data] of Object.entries(files || {})) {
|
||||
result.push({ hash: key.length !== 40 ? arr2hex(text2arr(key)) : key, ...data })
|
||||
}
|
||||
this.dispatch('scrape', { id, result })
|
||||
scrape ({ id, infoHashes }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.trackers.cat._request(this.trackers.cat.scrapeUrl, { info_hash: infoHashes.map(infoHash => hex2bin(infoHash)) }, (err, data) => {
|
||||
if (err) {
|
||||
this.emit('warn', err)
|
||||
return resolve({ id, result: [] })
|
||||
}
|
||||
const { files } = data
|
||||
const result = []
|
||||
for (const [key, data] of Object.entries(files || {})) {
|
||||
result.push({ hash: key.length !== 40 ? arr2hex(text2arr(key)) : key, ...data })
|
||||
}
|
||||
resolve({ id, result })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -191,18 +199,18 @@ export default class TorrentClient extends WebTorrent {
|
|||
for (const exclude of TorrentClient.excludedErrorMessages) {
|
||||
if (e.message?.startsWith(exclude)) return
|
||||
}
|
||||
this.dispatch('error', JSON.stringify(e?.message || e))
|
||||
this.emit('error', JSON.stringify(e?.message || e))
|
||||
}
|
||||
|
||||
async addTorrent (data, skipVerify = false) {
|
||||
const existing = await this.get(data)
|
||||
const existing = await this.client.get(data)
|
||||
if (existing) {
|
||||
if (existing.ready) this.handleTorrent(existing)
|
||||
return
|
||||
}
|
||||
localStorage.setItem('lastFinished', false)
|
||||
if (this.torrents.length) await this.remove(this.torrents[0])
|
||||
const torrent = await this.add(data, {
|
||||
if (this.client.torrents.length) await this.client.remove(this.client.torrents[0])
|
||||
const torrent = await this.client.add(data, {
|
||||
private: this.settings.torrentPeX,
|
||||
path: this.settings.torrentPath,
|
||||
destroyStoreOnDestroy: !this.settings.torrentPersist,
|
||||
|
|
@ -215,44 +223,25 @@ export default class TorrentClient extends WebTorrent {
|
|||
})
|
||||
}
|
||||
|
||||
async handleMessage ({ data }) {
|
||||
switch (data.type) {
|
||||
case 'current': {
|
||||
if (data.data) {
|
||||
const torrent = await this.get(data.data.infoHash)
|
||||
const found = torrent?.files.find(file => file.path === data.data.path)
|
||||
if (!found) return
|
||||
if (this.current) {
|
||||
this.current.removeAllListeners('stream')
|
||||
}
|
||||
this.parser?.destroy()
|
||||
found.select()
|
||||
this.current = found
|
||||
this.parser = new Parser(this, found)
|
||||
this.findSubtitleFiles(found)
|
||||
this.findFontFiles(found)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'scrape': {
|
||||
this._scrape(data.data)
|
||||
break
|
||||
}
|
||||
case 'torrent': {
|
||||
this.addTorrent(data.data)
|
||||
break
|
||||
}
|
||||
async setCurrent (file) {
|
||||
const torrent = await this.client.get(file.infoHash)
|
||||
const found = torrent?.files.find(f => f.path === file.path)
|
||||
if (!found) return
|
||||
if (this.current) {
|
||||
this.current.removeAllListeners('stream')
|
||||
}
|
||||
this.parser?.destroy()
|
||||
found.select()
|
||||
this.current = found
|
||||
this.parser = new Parser(this, found)
|
||||
this.findSubtitleFiles(found)
|
||||
this.findFontFiles(found)
|
||||
}
|
||||
|
||||
async dispatch (type, data, transfer) {
|
||||
await this._ready
|
||||
this.message?.({ type, data }, transfer)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.parser?.destroy()
|
||||
this.server.close()
|
||||
super.destroy()
|
||||
this.client.destroy()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@
|
|||
<div class='font-size-16'>Most features of Miru will not function correctly without being able to connect to an API.</div>
|
||||
<div class='font-size-16'>If you enable a VPN a restart might be required for it to take effect.</div>
|
||||
<!-- eslint-disable-next-line svelte/valid-compile -->
|
||||
<div class='font-size-16'>Visit <a class='text-primary pointer' use:click={() => { IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>this guide</a> for a tutorial on how to bypass ISP blocks.</div>
|
||||
<div class='font-size-16'>Visit <a class='text-primary pointer' use:click={() => { IPC.open('https://thewiki.moe/tutorials/unblock/') }}>this guide</a> for a tutorial on how to bypass ISP blocks.</div>
|
||||
<div class='d-flex w-full mt-20 pt-20'>
|
||||
<button class='btn ml-auto mr-5' type='button' use:click={() => { block = false }}>I Understand</button>
|
||||
<button class='btn btn-primary mr-5' type='button' use:click={() => { IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>Open Guide</button>
|
||||
<button class='btn btn-primary mr-5' type='button' use:click={() => { IPC.open('https://thewiki.moe/tutorials/unblock/') }}>Open Guide</button>
|
||||
<button class='btn btn-primary' type='button' use:click={testConnection}>Reconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
IPC.emit('discord', { activity })
|
||||
IPC.discord.handleDiscordStatus({ activity })
|
||||
}
|
||||
state.subscribe(() => {
|
||||
setDiscordRPC()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import Keybinds, { loadWithDefaults, condition } from 'svelte-keybinds'
|
||||
import { SUPPORTS } from '@/modules/support.js'
|
||||
import 'rvfc-polyfill'
|
||||
import { proxy } from 'comlink'
|
||||
|
||||
const emit = createEventDispatcher()
|
||||
|
||||
|
|
@ -171,7 +172,7 @@
|
|||
current = file
|
||||
emit('current', current)
|
||||
src = file.url
|
||||
client.send('current', file)
|
||||
client.setCurrent(file)
|
||||
subs = new Subtitles(video, files, current, handleHeaders)
|
||||
video.load()
|
||||
await loadAnimeProgress()
|
||||
|
|
@ -718,14 +719,10 @@
|
|||
return 0
|
||||
}
|
||||
let buffer = 0
|
||||
client.on('progress', ({ detail }) => {
|
||||
buffer = detail * 100
|
||||
})
|
||||
|
||||
let chapters = []
|
||||
client.on('chapters', ({ detail }) => {
|
||||
if (detail.length) chapters = detail
|
||||
})
|
||||
client.on('chapters', proxy(data => {
|
||||
if (data.length) chapters = data
|
||||
}))
|
||||
async function findChapters () {
|
||||
if (!chapters.length && current.media.media) {
|
||||
chapters = await getChaptersAniSkip(current, safeduration)
|
||||
|
|
@ -891,11 +888,13 @@
|
|||
}
|
||||
}
|
||||
const torrent = {}
|
||||
client.on('stats', updateStats)
|
||||
function updateStats ({ detail }) {
|
||||
client.on('stats', proxy(updateStats))
|
||||
function updateStats (detail) {
|
||||
torrent.peers = detail.numPeers || 0
|
||||
torrent.up = detail.uploadSpeed || 0
|
||||
torrent.down = detail.downloadSpeed || 0
|
||||
|
||||
buffer = detail.progress * 100
|
||||
}
|
||||
function checkError ({ target }) {
|
||||
// video playback failed - show a message saying why
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
location.reload()
|
||||
}
|
||||
function checkUpdate () {
|
||||
IPC.emit('update')
|
||||
IPC.updater.checkForUpdates()
|
||||
}
|
||||
setInterval(checkUpdate, 1200000)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -75,18 +75,18 @@
|
|||
<button type='button' use:click={() => { homeSections[homeSections.length] = 'Trending Now' }} class='btn btn-primary'>Add Section</button>
|
||||
|
||||
<style>
|
||||
.ghost {
|
||||
margin-bottom: 10px;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
position: absolute !important;
|
||||
}
|
||||
.ghost {
|
||||
margin-bottom: 10px;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
.tp {
|
||||
opacity: 0;
|
||||
}
|
||||
.tp {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.grab{
|
||||
cursor: grab;
|
||||
}
|
||||
.grab{
|
||||
cursor: grab;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import SettingCard from './SettingCard.svelte'
|
||||
import { SUPPORTS } from '@/modules/support.js'
|
||||
function updateAngle () {
|
||||
IPC.emit('angle', settings.value.angle)
|
||||
IPC.angle(settings.value.angle)
|
||||
}
|
||||
export let settings
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
<script context='module'>
|
||||
import { toast } from 'svelte-sonner'
|
||||
import { proxy } from 'comlink'
|
||||
import { click } from '@/modules/click.js'
|
||||
import { settings } from '@/modules/settings.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
|
||||
if (settings.value.enableDoH) IPC.emit('doh', settings.value.doHURL)
|
||||
if (settings.value.enableDoH) IPC.doh(settings.value.doHURL)
|
||||
export const platformMap = {
|
||||
aix: 'Aix',
|
||||
darwin: 'MacOS',
|
||||
|
|
@ -17,35 +18,33 @@
|
|||
win32: 'Windows'
|
||||
}
|
||||
let version = '1.0.0'
|
||||
IPC.on('version', data => (version = data))
|
||||
IPC.emit('version')
|
||||
IPC.version().then(data => (version = data))
|
||||
|
||||
let wasUpdated = false
|
||||
IPC.on('update-available', () => {
|
||||
IPC.updater.on('update-available', proxy(() => {
|
||||
if (!wasUpdated) {
|
||||
wasUpdated = true
|
||||
toast('Auto Updater', {
|
||||
description: 'A new version of Miru is available. Downloading!'
|
||||
})
|
||||
}
|
||||
})
|
||||
IPC.on('update-downloaded', () => {
|
||||
}))
|
||||
IPC.updater.on('update-downloaded', proxy(() => {
|
||||
toast.success('Auto Updater', {
|
||||
description: 'A new version of Miru has downloaded. You can restart to update!'
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
const changeLog = (async () => {
|
||||
const res = await fetch('https://api.github.com/repos/ThaUnknown/miru/releases')
|
||||
const json = await res.json()
|
||||
return json.map(({ body, tag_name: version, published_at: date, assets }) => ({ body, version, date, assets }))
|
||||
})()
|
||||
IPC.emit('show-discord-status', settings.value.showDetailsInRPC)
|
||||
IPC.discord.showDiscordStatus(settings.value.showDetailsInRPC)
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Tabs, TabLabel, Tab } from '@/components/Tabination.js'
|
||||
import { onDestroy } from 'svelte'
|
||||
import PlayerSettings from './PlayerSettings.svelte'
|
||||
import TorrentSettings from './TorrentSettings.svelte'
|
||||
import InterfaceSettings from './InterfaceSettings.svelte'
|
||||
|
|
@ -74,14 +73,7 @@
|
|||
icon: 'description'
|
||||
}
|
||||
}
|
||||
function pathListener (data) {
|
||||
$settings.torrentPath = data
|
||||
}
|
||||
onDestroy(() => {
|
||||
IPC.off('path', pathListener)
|
||||
})
|
||||
$: IPC.emit('show-discord-status', $settings.showDetailsInRPC)
|
||||
IPC.on('path', pathListener)
|
||||
$: IPC.discord.showDiscordStatus($settings.showDetailsInRPC)
|
||||
</script>
|
||||
|
||||
<Tabs>
|
||||
|
|
@ -96,7 +88,7 @@
|
|||
</div>
|
||||
</TabLabel>
|
||||
{/each}
|
||||
<div class='pointer my-5 rounded' tabindex='0' role='button' use:click={() => IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/')}>
|
||||
<div class='pointer my-5 rounded' tabindex='0' role='button' use:click={() => IPC.open('https://github.com/sponsors/ThaUnknown/')}>
|
||||
<div class='px-20 py-10 d-flex'>
|
||||
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>favorite</span>
|
||||
<div class='font-weight-bold font-size-16'>Donate</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
export let settings
|
||||
|
||||
function handleFolder () {
|
||||
IPC.emit('dialog')
|
||||
IPC.dialog().then(data => {
|
||||
settings.torrentPath = data
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -66,9 +66,6 @@
|
|||
duration: '5000'
|
||||
})
|
||||
}
|
||||
function openInBrowser (url) {
|
||||
IPC.emit('open', url)
|
||||
}
|
||||
function getPlayText (media) {
|
||||
if (media.mediaListEntry) {
|
||||
const { status, progress } = media.mediaListEntry
|
||||
|
|
@ -136,7 +133,7 @@
|
|||
</button>
|
||||
{/if}
|
||||
<div class='d-flex mb-5 w-full'>
|
||||
<button class='btn flex-fill font-weight-bold font-size-16 d-flex align-items-center' use:click={() => { openInBrowser(`https://anilist.co/anime/${media.id}`) }}>
|
||||
<button class='btn flex-fill font-weight-bold font-size-16 d-flex align-items-center' use:click={() => { IPC.open(`https://anilist.co/anime/${media.id}`) }}>
|
||||
<span class='material-symbols-outlined mr-15 font-size-18 w-30'> open_in_new </span>
|
||||
Open
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<img src={friend.user.avatar.medium} alt='avatar' class='w-50 h-50 img-fluid rounded cover-img' />
|
||||
<span class='my-0 pl-20 mr-auto text-truncate'>{friend.user.name}</span>
|
||||
<span class='my-0 px-10 text-capitalize'>{friend.status.toLowerCase()}</span>
|
||||
<span class='material-symbols-outlined pointer text-primary font-size-18' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
|
||||
<span class='material-symbols-outlined pointer text-primary font-size-18' use:click={() => IPC.open('https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -63,9 +63,6 @@
|
|||
duration: 5000
|
||||
})
|
||||
}
|
||||
function openInBrowser (url) {
|
||||
IPC.emit('open', url)
|
||||
}
|
||||
let episodeOrder = true
|
||||
</script>
|
||||
|
||||
|
|
@ -134,7 +131,7 @@
|
|||
<button class='btn bg-dark btn-lg btn-square ml-10 material-symbols-outlined font-size-20 shadow-none border-0' use:click={() => copyToClipboard(`https://miru.watch/anime/${media.id}`)}>
|
||||
share
|
||||
</button>
|
||||
<button class='btn bg-dark btn-lg btn-square ml-10 material-symbols-outlined font-size-20 shadow-none border-0' use:click={() => openInBrowser(`https://anilist.co/anime/${media.id}`)}>
|
||||
<button class='btn bg-dark btn-lg btn-square ml-10 material-symbols-outlined font-size-20 shadow-none border-0' use:click={() => IPC.open(`https://anilist.co/anime/${media.id}`)}>
|
||||
open_in_new
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{/if}
|
||||
<h4 class='my-0 pl-20 mr-auto'>{peer.user?.name || 'Anonymous'}</h4>
|
||||
{#if peer.user?.name}
|
||||
<span class='material-symbols-outlined pointer text-primary' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + peer.user?.name)}> open_in_new </span>
|
||||
<span class='material-symbols-outlined pointer text-primary' use:click={() => IPC.open('https://anilist.co/user/' + peer.user?.name)}> open_in_new </span>
|
||||
{/if}
|
||||
<!-- {#if state === 'host'}
|
||||
<span class='material-symbols-outlined ml-15 pointer text-danger' use:click={() => peer.peer.pc.close()}> logout </span>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { toast } from 'svelte-sonner'
|
||||
import { page } from '@/App.svelte'
|
||||
import P2PT from 'p2pt'
|
||||
import { proxy } from 'comlink'
|
||||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import 'browser-event-target-emitter'
|
||||
|
|
@ -108,13 +109,13 @@
|
|||
}
|
||||
})
|
||||
|
||||
client.on('magnet', ({ detail }) => {
|
||||
client.on('magnet', proxy((detail) => {
|
||||
if (detail.hash !== playerState.hash) {
|
||||
playerState.hash = detail.hash
|
||||
playerState.index = 0
|
||||
emit('torrent', detail)
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
function emit (type, data) {
|
||||
if (p2pt) {
|
||||
|
|
@ -131,10 +132,10 @@
|
|||
index: 0
|
||||
}
|
||||
|
||||
IPC.on('w2glink', link => {
|
||||
IPC.protocol.on('w2glink', proxy(link => {
|
||||
joinLobby(link)
|
||||
page.set('watchtogether')
|
||||
})
|
||||
}))
|
||||
|
||||
function cleanup () {
|
||||
state.set(false)
|
||||
|
|
|
|||
|
|
@ -81,7 +81,10 @@
|
|||
},
|
||||
"win": {
|
||||
"artifactName": "${os}-${name}-${version}.${ext}",
|
||||
"target": ["nsis", "portable"]
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"artifactName": "${os}-${name}-${version}.${ext}",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { statfs } from 'node:fs/promises'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { statfs } from 'fs/promises'
|
||||
import { expose } from 'comlink'
|
||||
|
||||
import TorrentClient from 'common/modules/webtorrent.js'
|
||||
|
||||
|
|
@ -8,4 +9,7 @@ async function storageQuota (directory) {
|
|||
return bsize * bavail
|
||||
}
|
||||
|
||||
globalThis.client = new TorrentClient(ipcRenderer, storageQuota, 'node')
|
||||
globalThis.client = new TorrentClient(storageQuota, 'node')
|
||||
ipcRenderer.on('port', ({ ports }) => {
|
||||
expose(globalThis.client, ports[0])
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Client } from 'discord-rpc'
|
||||
import { ipcMain } from 'electron'
|
||||
import { debounce } from '@/modules/util.js'
|
||||
|
||||
export default class {
|
||||
|
|
@ -35,16 +34,6 @@ export default class {
|
|||
transport: 'ipc'
|
||||
})
|
||||
|
||||
ipcMain.on('show-discord-status', (event, data) => {
|
||||
this.allowDiscordDetails = data
|
||||
this.debouncedDiscordRPC(this.allowDiscordDetails ? this.cachedPresence : undefined)
|
||||
})
|
||||
|
||||
ipcMain.on('discord', (event, data) => {
|
||||
this.cachedPresence = data
|
||||
this.debouncedDiscordRPC(this.allowDiscordDetails ? this.cachedPresence : undefined)
|
||||
})
|
||||
|
||||
this.discord.on('ready', async () => {
|
||||
this.setDiscordRPC(this.cachedPresence || this.defaultStatus)
|
||||
this.discord.subscribe('ACTIVITY_JOIN_REQUEST')
|
||||
|
|
@ -61,6 +50,16 @@ export default class {
|
|||
this.debouncedDiscordRPC = debounce(status => this.setDiscordRPC(status), 4500)
|
||||
}
|
||||
|
||||
handleDiscordStatus (data) {
|
||||
this.cachedPresence = data
|
||||
this.debouncedDiscordRPC(this.allowDiscordDetails && this.cachedPresence)
|
||||
}
|
||||
|
||||
showDiscordStatus (data) {
|
||||
this.allowDiscordDetails = data
|
||||
this.debouncedDiscordRPC(this.allowDiscordDetails & this.cachedPresence)
|
||||
}
|
||||
|
||||
loginRPC () {
|
||||
this.discord.login({ clientId: '954855428355915797' }).catch(() => {
|
||||
setTimeout(() => this.loginRPC(), 5000).unref()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,46 @@
|
|||
/* eslint-disable no-new */
|
||||
import { app, BrowserWindow, shell, ipcMain, dialog, MessageChannelMain } from 'electron'
|
||||
import { app, BrowserWindow, shell, ipcMain, dialog } from 'electron'
|
||||
import path from 'path'
|
||||
import Discord from './discord.js'
|
||||
import Updater from './updater.js'
|
||||
import Protocol from './protocol.js'
|
||||
import { development } from './util.js'
|
||||
import { expose } from 'comlink'
|
||||
import { Util, development } from './util.js'
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow
|
||||
let webtorrentWindow
|
||||
function electronEndpoint (nep) {
|
||||
const listeners = new WeakMap()
|
||||
return {
|
||||
postMessage: (...args) => {
|
||||
nep.postMessage(...args)
|
||||
},
|
||||
addEventListener: (_, eh) => {
|
||||
const l = data => {
|
||||
if (data.data?.argumentList) {
|
||||
for (const arg of data.data.argumentList) {
|
||||
if (arg.type === 'HANDLER' && arg.name === 'proxy') arg.value = electronEndpoint(data.ports[0])
|
||||
}
|
||||
}
|
||||
if ('handleEvent' in eh) {
|
||||
eh.handleEvent(data)
|
||||
} else {
|
||||
eh(data)
|
||||
}
|
||||
}
|
||||
nep.on('message', l)
|
||||
listeners.set(eh, l)
|
||||
},
|
||||
removeEventListener: (_, eh) => {
|
||||
const l = listeners.get(eh)
|
||||
if (!l) {
|
||||
return
|
||||
}
|
||||
nep.off('message', l)
|
||||
listeners.delete(eh)
|
||||
},
|
||||
start: nep.start && nep.start.bind(nep)
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
webtorrentWindow = new BrowserWindow({
|
||||
const webtorrentWindow = new BrowserWindow({
|
||||
show: development,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
|
|
@ -21,7 +48,7 @@ function createWindow () {
|
|||
backgroundThrottling: false
|
||||
}
|
||||
})
|
||||
mainWindow = new BrowserWindow({
|
||||
let mainWindow = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 900,
|
||||
frame: process.platform === 'darwin', // Only keep the native frame on Mac
|
||||
|
|
@ -36,14 +63,22 @@ function createWindow () {
|
|||
webPreferences: {
|
||||
enableBlinkFeatures: 'FontAccess, AudioVideoTracks',
|
||||
backgroundThrottling: false,
|
||||
contextIsolation: false,
|
||||
preload: path.join(__dirname, '/preload.js')
|
||||
},
|
||||
icon: path.join(__dirname, '/logo.ico'),
|
||||
show: false
|
||||
})
|
||||
new Discord(mainWindow)
|
||||
new Protocol(mainWindow)
|
||||
new Updater(mainWindow)
|
||||
|
||||
ipcMain.on('mainPort', ({ ports }) => {
|
||||
expose(new Util(mainWindow), electronEndpoint(ports[0]))
|
||||
})
|
||||
|
||||
ipcMain.on('torrentPort', async ({ ports }) => {
|
||||
await torrentLoad
|
||||
webtorrentWindow.webContents.postMessage('port', null, [ports[0]])
|
||||
})
|
||||
|
||||
mainWindow.setMenuBarVisibility(false)
|
||||
|
||||
mainWindow.webContents.session.webRequest.onHeadersReceived(({ responseHeaders }, fn) => {
|
||||
|
|
@ -72,14 +107,6 @@ function createWindow () {
|
|||
app.quit()
|
||||
})
|
||||
|
||||
ipcMain.on('close', () => {
|
||||
mainWindow = null
|
||||
try {
|
||||
webtorrentWindow.webContents.postMessage('destroy', null)
|
||||
} catch (e) {}
|
||||
app.quit()
|
||||
})
|
||||
|
||||
let crashcount = 0
|
||||
mainWindow.webContents.on('render-process-gone', (e, { reason }) => {
|
||||
if (reason === 'crashed') {
|
||||
|
|
@ -100,16 +127,10 @@ function createWindow () {
|
|||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
})
|
||||
ipcMain.on('portRequest', async ({ sender }) => {
|
||||
const { port1, port2 } = new MessageChannelMain()
|
||||
await torrentLoad
|
||||
webtorrentWindow.webContents.postMessage('port', null, [port1])
|
||||
sender.postMessage('port', null, [port2])
|
||||
})
|
||||
}
|
||||
|
||||
app.on('ready', createWindow)
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow === null) createWindow()
|
||||
if (!BrowserWindow.getAllWindows().length) createWindow()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import path from 'node:path'
|
||||
import { app, protocol, shell, ipcMain } from 'electron'
|
||||
import { development } from './util.js'
|
||||
import path from 'path'
|
||||
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
|
|
@ -10,27 +10,42 @@ if (process.defaultApp) {
|
|||
app.setAsDefaultProtocolClient('miru')
|
||||
}
|
||||
|
||||
export default class {
|
||||
export default class Protocol {
|
||||
// schema: miru://key/value
|
||||
protocolMap = {
|
||||
auth: token => this.sendToken(token),
|
||||
anime: id => this.window.webContents.send('open-anime', id),
|
||||
w2g: link => this.window.webContents.send('w2glink', link),
|
||||
schedule: () => this.window.webContents.send('schedule'),
|
||||
anime: id => this.emit('open-anime', id),
|
||||
w2g: link => this.emit('w2glink', link),
|
||||
schedule: () => this.emit('schedule'),
|
||||
donate: () => shell.openExternal('https://github.com/sponsors/ThaUnknown/')
|
||||
}
|
||||
|
||||
protocolRx = /miru:\/\/([a-z0-9]+)\/(.*)/i
|
||||
|
||||
emit (event, ...args) {
|
||||
if (!this.events[event]?.length) return
|
||||
for (const cb of this.events[event]) {
|
||||
cb(...args)
|
||||
}
|
||||
}
|
||||
|
||||
off (event) {
|
||||
delete this.events[event]
|
||||
}
|
||||
|
||||
events = {}
|
||||
|
||||
on (event, cb) {
|
||||
(this.events[event] ||= []).push(cb)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('electron').BrowserWindow} window
|
||||
*/
|
||||
constructor (window) {
|
||||
this.window = window
|
||||
|
||||
protocol.registerHttpProtocol('miru', (req, cb) => {
|
||||
const token = req.url.slice(7)
|
||||
this.window.loadURL(development ? 'http://localhost:5000/app.html' + token : `file://${path.join(__dirname, '/app.html')}${token}`)
|
||||
window.loadURL(development ? 'http://localhost:5000/app.html' + token : `file://${path.join(__dirname, '/app.html')}${token}`)
|
||||
})
|
||||
|
||||
app.on('open-url', (event, url) => {
|
||||
|
|
@ -48,9 +63,9 @@ export default class {
|
|||
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (this.window) {
|
||||
if (this.window.isMinimized()) this.window.restore()
|
||||
this.window.focus()
|
||||
if (window) {
|
||||
if (window.isMinimized()) window.restore()
|
||||
window.focus()
|
||||
}
|
||||
// There's probably a better way to do this instead of a for loop and split[1][0]
|
||||
// but for now it works as a way to fix multiple OS's commandLine differences
|
||||
|
|
@ -64,7 +79,7 @@ export default class {
|
|||
let token = line.split('access_token=')[1].split('&token_type')[0]
|
||||
if (token) {
|
||||
if (token.endsWith('/')) token = token.slice(0, -1)
|
||||
this.window.webContents.send('altoken', token)
|
||||
this.emit('altoken', token)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,38 @@
|
|||
import log from 'electron-log'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
log.transports.file.level = 'info'
|
||||
autoUpdater.logger = log
|
||||
ipcMain.on('update', () => {
|
||||
autoUpdater.checkForUpdatesAndNotify()
|
||||
})
|
||||
|
||||
autoUpdater.checkForUpdatesAndNotify()
|
||||
export default class {
|
||||
/**
|
||||
* @param {import('electron').BrowserWindow} window
|
||||
*/
|
||||
constructor (window) {
|
||||
export default class Updater {
|
||||
emit (event, ...args) {
|
||||
if (!this.events[event]?.length) return
|
||||
for (const cb of this.events[event]) {
|
||||
cb(...args)
|
||||
}
|
||||
}
|
||||
|
||||
off (event) {
|
||||
delete this.events[event]
|
||||
}
|
||||
|
||||
events = {}
|
||||
|
||||
on (event, cb) {
|
||||
(this.events[event] ||= []).push(cb)
|
||||
}
|
||||
|
||||
constructor () {
|
||||
autoUpdater.on('update-available', () => {
|
||||
window.webContents.send('update-available', true)
|
||||
this.emit('update-available', true)
|
||||
})
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
window.webContents.send('update-downloaded', true)
|
||||
this.emit('update-downloaded', true)
|
||||
})
|
||||
}
|
||||
|
||||
checkForUpdates () {
|
||||
autoUpdater.checkForUpdatesAndNotify()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,75 @@
|
|||
import { app, ipcMain, shell, dialog } from 'electron'
|
||||
import { app, dialog, shell } from 'electron'
|
||||
import store from './store.js'
|
||||
import Discord from './discord.js'
|
||||
import Protocol from './protocol.js'
|
||||
import Updater from './updater.js'
|
||||
|
||||
export const development = process.env.NODE_ENV?.trim() === 'development'
|
||||
|
||||
export class Util {
|
||||
mainWindow
|
||||
discord
|
||||
protocol
|
||||
updater
|
||||
/**
|
||||
* @param {import('electron').BrowserWindow} mainWindow
|
||||
*/
|
||||
constructor (mainWindow) {
|
||||
this.mainWindow = mainWindow
|
||||
this.discord = new Discord(mainWindow)
|
||||
this.protocol = new Protocol(mainWindow)
|
||||
this.updater = new Updater()
|
||||
}
|
||||
|
||||
close () {
|
||||
this.mainWindow = null
|
||||
// try {
|
||||
// this.webtorrentWindow.webContents.postMessage('destroy', null)
|
||||
// } catch (e) {}
|
||||
app.quit()
|
||||
}
|
||||
|
||||
open (url) {
|
||||
shell.openExternal(url)
|
||||
}
|
||||
|
||||
doh (dns) {
|
||||
try {
|
||||
const url = new URL(dns)
|
||||
|
||||
app.configureHostResolver({
|
||||
secureDnsMode: 'secure',
|
||||
secureDnsServers: [url.toString()]
|
||||
})
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
angle (angle) {
|
||||
return store.set('angle', angle)
|
||||
}
|
||||
|
||||
async dialog () {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (filePaths.length) {
|
||||
let path = filePaths[0]
|
||||
if (!(path.endsWith('\\') || path.endsWith('/'))) {
|
||||
if (path.indexOf('\\') !== -1) {
|
||||
path += '\\'
|
||||
} else if (path.indexOf('/') !== -1) {
|
||||
path += '/'
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
version () {
|
||||
return app.getVersion()
|
||||
}
|
||||
}
|
||||
|
||||
const flags = [
|
||||
['disable-gpu-sandbox'],
|
||||
['disable-direct-composition-video-overlays'],
|
||||
|
|
@ -27,50 +94,6 @@ app.commandLine.appendSwitch('use-angle', store.get('angle') || 'default')
|
|||
|
||||
if (!app.requestSingleInstanceLock()) app.quit()
|
||||
|
||||
ipcMain.on('open', (event, url) => {
|
||||
shell.openExternal(url)
|
||||
})
|
||||
|
||||
ipcMain.on('doh', (event, dns) => {
|
||||
try {
|
||||
const url = new URL(dns)
|
||||
|
||||
app.configureHostResolver({
|
||||
secureDnsMode: 'secure',
|
||||
secureDnsServers: [url.toString()]
|
||||
})
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
ipcMain.on('angle', (e, data) => {
|
||||
store.set('angle', data)
|
||||
})
|
||||
|
||||
ipcMain.on('close', () => {
|
||||
app.quit()
|
||||
})
|
||||
|
||||
ipcMain.on('dialog', async (event, data) => {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (filePaths.length) {
|
||||
let path = filePaths[0]
|
||||
if (!(path.endsWith('\\') || path.endsWith('/'))) {
|
||||
if (path.indexOf('\\') !== -1) {
|
||||
path += '\\'
|
||||
} else if (path.indexOf('/') !== -1) {
|
||||
path += '/'
|
||||
}
|
||||
}
|
||||
event.sender.send('path', path)
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('version', (event) => {
|
||||
event.sender.send('version', app.getVersion()) // fucking stupid
|
||||
})
|
||||
|
||||
app.setJumpList?.([
|
||||
{
|
||||
name: 'Frequent',
|
||||
|
|
|
|||
|
|
@ -1,32 +1,22 @@
|
|||
/* eslint n/no-callback-literal: 0 */
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
contextBridge.exposeInMainWorld('IPC', {
|
||||
emit: (event, data) => {
|
||||
ipcRenderer.send(event, data)
|
||||
},
|
||||
on: (event, callback) => {
|
||||
ipcRenderer.on(event, (event, ...args) => callback(...args))
|
||||
},
|
||||
once: (event, callback) => {
|
||||
ipcRenderer.once(event, (event, ...args) => callback(...args))
|
||||
},
|
||||
off: (event) => {
|
||||
ipcRenderer.removeAllListeners(event)
|
||||
const mainChannel = new MessageChannel()
|
||||
globalThis.Native = mainChannel.port1
|
||||
ipcRenderer.postMessage('mainPort', null, [mainChannel.port2])
|
||||
const d = mainChannel.port1.postMessage.bind(mainChannel.port1)
|
||||
mainChannel.port1.postMessage = (...args) => {
|
||||
if (args[0]?.argumentList) {
|
||||
for (const arg of args[0].argumentList) {
|
||||
if (arg.type === 'HANDLER' && arg.name === 'proxy') delete arg.value
|
||||
}
|
||||
}
|
||||
})
|
||||
contextBridge.exposeInMainWorld('version', {
|
||||
d(...args)
|
||||
}
|
||||
const torrentChannel = new MessageChannel()
|
||||
globalThis.Torrent = torrentChannel.port1
|
||||
ipcRenderer.postMessage('torrentPort', null, [torrentChannel.port2])
|
||||
|
||||
globalThis.version = {
|
||||
arch: process.arch,
|
||||
platform: process.platform
|
||||
})
|
||||
|
||||
ipcRenderer.once('port', ({ ports }) => {
|
||||
contextBridge.exposeInMainWorld('port', {
|
||||
onmessage: (cb) => {
|
||||
ports[0].onmessage = ({ type, data }) => cb({ type, data })
|
||||
},
|
||||
postMessage: (a, b) => {
|
||||
ports[0].postMessage(a, b)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
5
electron/src/renderer/ipc.js
Normal file
5
electron/src/renderer/ipc.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { wrap } from 'comlink'
|
||||
|
||||
export default wrap(globalThis.Native)
|
||||
|
||||
export const TorrentClient = wrap(globalThis.Torrent)
|
||||
|
|
@ -5,6 +5,10 @@ const mode = process.env.NODE_ENV?.trim() || 'development'
|
|||
|
||||
const commonConfig = require('common/webpack.config.cjs')
|
||||
|
||||
const alias = {
|
||||
'@/modules/ipc.js': join(__dirname, 'src', 'renderer', 'ipc.js')
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
devtool: 'source-map',
|
||||
|
|
@ -40,7 +44,7 @@ module.exports = [
|
|||
port: 5000
|
||||
}
|
||||
},
|
||||
commonConfig(__dirname),
|
||||
commonConfig(__dirname, alias),
|
||||
{
|
||||
devtool: 'source-map',
|
||||
entry: join(__dirname, 'src', 'preload', 'preload.js'),
|
||||
|
|
|
|||
|
|
@ -13,8 +13,14 @@
|
|||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@/*": ["./common/*"],
|
||||
"three": ["./types.d.ts"],
|
||||
"rxjs": ["./types.d.ts"],
|
||||
},
|
||||
"types": ["@cloudflare/workers-types", "./types.d.ts"],
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "build", "git_modules", ".svelte-kit", "**/node_modules", "**/dist", "**/build", "**/git_modules", "**/.svelte-kit", "**/node_modules/*", "**/dist/*", "**/build/*", "**/git_modules/*", "**/.svelte-kit/*"]
|
||||
"exclude": [
|
||||
"node_modules", "dist", "build", "git_modules", ".svelte-kit", "public", "android", "@types/three",
|
||||
"**/node_modules", "**/dist", "**/build", "**/git_modules", "**/.svelte-kit", "**/public", "**/android", "**/@types/three",
|
||||
"**/node_modules/*", "**/dist/*", "**/build/*", "**/git_modules/*", "**/.svelte-kit/*", "**/public/*", "**/android/*", "**@types/three/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"comlink": "^4.4.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ importers:
|
|||
'@typescript-eslint/parser':
|
||||
specifier: ^6.12.0
|
||||
version: 6.12.0(eslint@8.54.0)(typescript@5.3.2)
|
||||
comlink:
|
||||
specifier: ^4.4.1
|
||||
version: 4.4.1
|
||||
concurrently:
|
||||
specifier: ^8.2.2
|
||||
version: 8.2.2
|
||||
|
|
@ -1288,7 +1291,7 @@ packages:
|
|||
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 20.9.5
|
||||
'@types/node': 20.10.1
|
||||
|
||||
/@types/bonjour@3.5.13:
|
||||
resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==}
|
||||
|
|
@ -1313,7 +1316,7 @@ packages:
|
|||
/@types/connect@3.4.38:
|
||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.5
|
||||
'@types/node': 20.10.1
|
||||
|
||||
/@types/cookie@0.5.4:
|
||||
resolution: {integrity: sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==}
|
||||
|
|
@ -1343,7 +1346,7 @@ packages:
|
|||
/@types/express-serve-static-core@4.17.41:
|
||||
resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.5
|
||||
'@types/node': 20.10.1
|
||||
'@types/qs': 6.9.10
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.4
|
||||
|
|
@ -1381,7 +1384,7 @@ packages:
|
|||
/@types/http-proxy@1.17.14:
|
||||
resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.5
|
||||
'@types/node': 20.10.1
|
||||
|
||||
/@types/json-schema@7.0.15:
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
|
@ -1413,7 +1416,7 @@ packages:
|
|||
/@types/node-forge@1.3.10:
|
||||
resolution: {integrity: sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.5
|
||||
'@types/node': 20.10.1
|
||||
|
||||
/@types/node@18.19.2:
|
||||
resolution: {integrity: sha512-6wzfBdbWpe8QykUkXBjtmO3zITA0A3FIjoy+in0Y2K4KrCiRhNYJIdwAPDffZ3G6GnaKaSLSEa9ZuORLfEoiwg==}
|
||||
|
|
@ -1467,7 +1470,7 @@ packages:
|
|||
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 20.9.5
|
||||
'@types/node': 20.10.1
|
||||
|
||||
/@types/serve-index@1.9.4:
|
||||
resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==}
|
||||
|
|
@ -2796,6 +2799,10 @@ packages:
|
|||
delayed-stream: 1.0.0
|
||||
dev: true
|
||||
|
||||
/comlink@4.4.1:
|
||||
resolution: {integrity: sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==}
|
||||
dev: false
|
||||
|
||||
/commander@10.0.1:
|
||||
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
|
||||
engines: {node: '>=14'}
|
||||
|
|
@ -5148,6 +5155,7 @@ packages:
|
|||
|
||||
/ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
/ignore-by-default@1.0.1:
|
||||
|
|
|
|||
|
|
@ -18,10 +18,14 @@
|
|||
"target": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"types": ["./types.d.ts"],
|
||||
"types": [],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "build", "git_modules", "**/node_modules", "**/dist", "**/build", "**/git_modules",]
|
||||
"exclude": [
|
||||
"node_modules", "dist", "build", "git_modules", ".svelte-kit", "public", "android", "@types/three",
|
||||
"**/node_modules", "**/dist", "**/build", "**/git_modules", "**/.svelte-kit", "**/public", "**/android", "**/@types/three",
|
||||
"**/node_modules/*", "**/dist/*", "**/build/*", "**/git_modules/*", "**/.svelte-kit/*", "**/public/*", "**/android/*", "**@types/three/*.d.ts"
|
||||
]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["../common/*"],
|
||||
"$lib":["./src/lib"],
|
||||
"$lib/*":["./src/lib/*"]
|
||||
},
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"types": ["./types.d.ts"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "build", "git_modules", "**/node_modules", "**/dist", "**/build", "**/git_modules",]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
Loading…
Reference in a new issue