From ab5f6ab448377cf9cf01ee0b000dc53e83e68578 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 1 Oct 2024 01:34:42 +0300 Subject: [PATCH 1/2] fix: add fallback peerjs discovery server to bypass geo restrictions and because sometimes official server is down feat: allow to use custom peerjs server via config --- config.json | 2 + prismarine-viewer/viewer/lib/entities.ts | 1 + src/connect.ts | 1 + src/entities.ts | 7 +- src/globalState.ts | 3 + src/index.ts | 11 ++- src/localServerMultiplayer.ts | 86 +++++++++++++++++++++--- src/react/PauseScreen.tsx | 9 ++- 8 files changed, 101 insertions(+), 19 deletions(-) diff --git a/config.json b/config.json index 330957078..7813b5911 100644 --- a/config.json +++ b/config.json @@ -3,6 +3,8 @@ "defaultHost": "", "defaultProxy": "proxy.mcraft.fun", "mapsProvider": "https://maps.mcraft.fun/", + "peerJsServer": "", + "peerJsServerFallback": "https://p2p.mcraft.fun", "promoteServers": [ { "ip": "kaboom.pw", diff --git a/prismarine-viewer/viewer/lib/entities.ts b/prismarine-viewer/viewer/lib/entities.ts index ee659263a..c7476f6c3 100644 --- a/prismarine-viewer/viewer/lib/entities.ts +++ b/prismarine-viewer/viewer/lib/entities.ts @@ -340,6 +340,7 @@ export class Entities extends EventEmitter { } update (entity: import('prismarine-entity').Entity & { delete?; pos }, overrides) { + console.log('entity', entity) const isPlayerModel = entity.name === 'player' if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') { overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}` diff --git a/src/connect.ts b/src/connect.ts index 12a1fc5bf..40a476693 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -15,4 +15,5 @@ export type ConnectOptions = { serverIndex?: string /** If true, will show a UI to authenticate with a new account */ authenticatedAccount?: AuthenticatedAccount | true + peerOptions?: any } diff --git a/src/entities.ts b/src/entities.ts index de97fb578..26641f5ac 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -75,10 +75,9 @@ customEvents.on('gameLoaded', () => { const isWalking = Math.abs(speed.x) > WALKING_SPEED || Math.abs(speed.z) > WALKING_SPEED const isSprinting = Math.abs(speed.x) > SPRINTING_SPEED || Math.abs(speed.z) > SPRINTING_SPEED const newAnimation = isWalking ? (isSprinting ? 'running' : 'walking') : 'idle' - const username = e.username! - if (newAnimation !== playerPerAnimation[username]) { + if (newAnimation !== playerPerAnimation[id]) { viewer.entities.playAnimation(e.id, newAnimation) - playerPerAnimation[username] = newAnimation + playerPerAnimation[id] = newAnimation } } }) @@ -122,7 +121,7 @@ customEvents.on('gameLoaded', () => { } viewer.entities.addListener('remove', (e) => { loadedSkinEntityIds.delete(e.id) - playerPerAnimation[e.username] = '' + playerPerAnimation[e.id] = '' bot.tracker.stopTrackingEntity(e, true) }) diff --git a/src/globalState.ts b/src/globalState.ts index b0a447f21..cefa78103 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -109,6 +109,8 @@ export type AppConfig = { defaultProxy?: string // defaultProxySave?: string // defaultVersion?: string + peerJsServer?: string + peerJsServerFallback?: string promoteServers?: Array<{ ip, description, version? }> mapsProvider?: string } @@ -120,6 +122,7 @@ export const miscUiState = proxy({ singleplayer: false, flyingSquid: false, wanOpened: false, + wanOpening: false, /** wether game hud is shown (in playing state) */ gameLoaded: false, showUI: true, diff --git a/src/index.ts b/src/index.ts index db855960f..b26619c33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,7 +72,7 @@ import defaultServerOptions from './defaultLocalServerOptions' import dayCycle from './dayCycle' import { onAppLoad, resourcepackReload } from './resourcePack' -import { connectToPeer } from './localServerMultiplayer' +import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer' import CustomChannelClient from './customClient' import { loadScript } from 'prismarine-viewer/viewer/lib/utils' import { registerServiceWorker } from './serviceWorker' @@ -486,7 +486,7 @@ async function connect (connectOptions: ConnectOptions) { port: server.port ? +server.port : undefined, version: connectOptions.botVersion || false, ...p2pMultiplayer ? { - stream: await connectToPeer(connectOptions.peerId!), + stream: await connectToPeer(connectOptions.peerId!, connectOptions.peerOptions), } : {}, ...singleplayer || p2pMultiplayer ? { keepAlive: false, @@ -1022,6 +1022,10 @@ downloadAndOpenFile().then((downloadAction) => { void Promise.resolve().then(() => { // try to connect to peer const peerId = qs.get('connectPeer') + const peerOptions = {} as ConnectPeerOptions + if (qs.get('server')) { + peerOptions.server = qs.get('server')! + } const version = qs.get('peerVersion') if (peerId) { let username: string | null = options.guestUsername @@ -1031,7 +1035,8 @@ downloadAndOpenFile().then((downloadAction) => { void connect({ username, botVersion: version || undefined, - peerId + peerId, + peerOptions }) } }) diff --git a/src/localServerMultiplayer.ts b/src/localServerMultiplayer.ts index c8e4bc5e5..7d147d0d7 100644 --- a/src/localServerMultiplayer.ts +++ b/src/localServerMultiplayer.ts @@ -19,6 +19,8 @@ class CustomDuplex extends Duplex { let peerInstance: Peer | undefined +let overridePeerJsServer = null as string | null + export const getJoinLink = () => { if (!peerInstance) return const url = new URL(window.location.href) @@ -27,6 +29,11 @@ export const getJoinLink = () => { } url.searchParams.set('connectPeer', peerInstance.id) url.searchParams.set('peerVersion', localServer!.options.version) + const host = (overridePeerJsServer ?? miscUiState.appConfig?.peerJsServer) ?? undefined + if (host) { + // TODO! use miscUiState.appConfig.peerJsServer + url.searchParams.set('server', host) + } return url.toString() } @@ -46,8 +53,12 @@ export const openToWanAndCopyJoinLink = async (writeText: (text) => void, doCopy if (doCopy) await copyJoinLink() return 'Already opened to wan. Join link copied' } + miscUiState.wanOpening = true + const host = (overridePeerJsServer ?? miscUiState.appConfig?.peerJsServer) || undefined + const params = host ? parseUrl(host) : undefined const peer = new Peer({ debug: 3, + ...params }) peerInstance = peer peer.on('connection', (connection) => { @@ -83,34 +94,91 @@ export const openToWanAndCopyJoinLink = async (writeText: (text) => void, doCopy connection.on('close', disconnected) connection.on('error', disconnected) }) + const fallbackServer = miscUiState.appConfig?.peerJsServerFallback + const hasFallback = fallbackServer && peer.options.host !== fallbackServer + let hadErrorReported = false peer.on('error', (error) => { - console.error(error) - writeText(error.message) + console.error('peerJS error', error) + if (error.type === 'server-error' && hasFallback) { + return + } + hadErrorReported = true + writeText(error.message || JSON.stringify(error)) }) - return new Promise(resolve => { + let timeout + const destroy = () => { + clearTimeout(timeout) + timeout = undefined + peer.destroy() + peerInstance = undefined + } + + const result = await new Promise(resolve => { peer.on('open', async () => { await copyJoinLink() resolve('Copied join link to clipboard') }) - setTimeout(() => { + timeout = setTimeout(() => { + if (!hadErrorReported && timeout !== undefined) { + writeText('timeout') + } resolve('Failed to open to wan (timeout)') - }, 5000) + }, 6000) + + // fallback + peer.on('error', async (error) => { + if (!peer.open) { + if (hasFallback) { + destroy() + + overridePeerJsServer = fallbackServer + console.log('Trying fallback server', fallbackServer) + resolve((await openToWanAndCopyJoinLink(writeText, doCopy))!) + } + } + }) }) + if (!peerInstance.open) { + destroy() + } + miscUiState.wanOpening = false + return result +} + +const parseUrl = (url: string) => { + // peerJS does this internally for some reason: const url = new URL(`${protocol}://${host}:${port}${path}${key}/${method}`) + if (!url.startsWith('http')) url = `${location.protocol}//${url}` + const urlObj = new URL(url) + const key = urlObj.searchParams.get('key') + return { + host: urlObj.hostname, + path: urlObj.pathname, + protocol: urlObj.protocol.slice(0, -1), + ...urlObj.port ? { port: +urlObj.port } : {}, + ...key ? { key } : {}, + } } export const closeWan = () => { - if (!peerInstance) return - peerInstance.destroy() + peerInstance?.destroy() peerInstance = undefined miscUiState.wanOpened = false - return 'Closed to wan' + return 'Closed WAN' +} + +export type ConnectPeerOptions = { + server?: string } -export const connectToPeer = async (peerId: string) => { +export const connectToPeer = async (peerId: string, options: ConnectPeerOptions = {}) => { setLoadingScreenStatus('Connecting to peer server') // todo destroy connection on error + // TODO! use miscUiState.appConfig.peerJsServer + const host = options.server + const params = host ? parseUrl(host) : undefined const peer = new Peer({ debug: 3, + ...params }) await resolveTimeout(new Promise(resolve => { peer.once('open', resolve) diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 1c4fdcd4e..19f1385cb 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -151,7 +151,7 @@ export default () => { const isModalActive = useIsModalActive('pause-screen') const fsStateSnap = useSnapshot(fsState) const activeModalStackSnap = useSnapshot(activeModalStack) - const { singleplayer, wanOpened } = useSnapshot(miscUiState) + const { singleplayer, wanOpened, wanOpening } = useSnapshot(miscUiState) const handlePointerLockChange = () => { if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { @@ -188,7 +188,10 @@ export default () => { return } if (!wanOpened || !qr) { - await openToWanAndCopyJoinLink(() => { }, !qr) + await openToWanAndCopyJoinLink((err) => { + if (!miscUiState.wanOpening) return + alert(`Something went wrong: ${err}`) + }, !qr) } if (qr) { const joinLink = getJoinLink() @@ -230,7 +233,7 @@ export default () => { {singleplayer ? (
{(navigator.share as typeof navigator.share | undefined) ? (