diff --git a/.github/workflows/next-deploy.yml b/.github/workflows/next-deploy.yml
index 0856285c..665abb30 100644
--- a/.github/workflows/next-deploy.yml
+++ b/.github/workflows/next-deploy.yml
@@ -29,7 +29,7 @@ jobs:
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Write Release Info
run: |
- echo "{\"latestTag\": \"$(git rev-parse --short $GITHUB_SHA)\"}" > assets/release.json
+ echo "{\"latestTag\": \"$(git rev-parse --short $GITHUB_SHA)\", \"isCommit\": true}" > assets/release.json
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- run: pnpm build-storybook
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
index 7ad16c2e..18c80e8c 100644
--- a/.github/workflows/preview.yml
+++ b/.github/workflows/preview.yml
@@ -58,7 +58,7 @@ jobs:
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Write Release Info
run: |
- echo "{\"latestTag\": \"$(git rev-parse --short ${{ github.event.pull_request.head.sha }})\"}" > assets/release.json
+ echo "{\"latestTag\": \"$(git rev-parse --short ${{ github.event.pull_request.head.sha }})\", \"isCommit\": true}" > assets/release.json
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- run: pnpm build-storybook
diff --git a/index.html b/index.html
index d662cbb6..8e85c039 100644
--- a/index.html
+++ b/index.html
@@ -25,6 +25,8 @@
- {versionText}
+ {versionText}
{
+ return activeModalStack.length === 0 && !miscUiState.gameLoaded
+}
+
const refreshApp = async (failedUpdate = false) => {
- const registration = await navigator.serviceWorker.getRegistration()
- await registration?.unregister()
- if (failedUpdate) {
- await new Promise(resolve => {
- setTimeout(resolve, 2000)
- })
- }
- if (activeModalStack.length !== 0) return
- if (failedUpdate) {
- sessionStorage.justReloaded = false
- // try to force bypass cache
- location.search = '?update=true'
- } else {
- window.justReloaded = true
- sessionStorage.justReloaded = true
- window.location.reload()
+ try {
+ const registration = await navigator.serviceWorker.getRegistration()
+ if (registration) {
+ // First, disconnect all clients
+ const clients = await window.clients?.matchAll() || []
+ await Promise.all(clients.map(client => client.postMessage('SKIP_WAITING')))
+
+ // Force the waiting service worker to become active
+ if (registration.waiting) {
+ registration.waiting.postMessage('SKIP_WAITING')
+ }
+
+ // Add timeout to prevent infinite waiting
+ const unregisterPromise = registration.unregister()
+ const timeoutPromise = new Promise((_, reject) => {
+ setTimeout(() => reject(new Error('SW unregister timeout')), 3000)
+ })
+
+ await Promise.race([unregisterPromise, timeoutPromise])
+ .catch(err => {
+ console.warn('SW unregister error:', err)
+ if (isMainMenu()) {
+ alert('Failed to unregister SW: ' + err)
+ }
+ })
+ }
+
+ if (failedUpdate) {
+ await new Promise(resolve => { setTimeout(resolve, 2000) })
+ }
+
+ if (!isMainMenu()) return
+
+ if (failedUpdate) {
+ sessionStorage.justReloaded = false
+ // try to force bypass cache
+ location.search = '?update=true'
+ } else {
+ window.justReloaded = true
+ sessionStorage.justReloaded = true
+ window.location.reload()
+ }
+ } catch (err) {
+ console.error('Failed to refresh app:', err)
+ if (!isMainMenu()) {
+ alert('Critical error on refreshApp: ' + err)
+ // Fallback to force reload if something goes wrong
+ window.location.reload()
+ }
}
}
@@ -122,7 +159,7 @@ export default () => {
await refreshApp()
}}
onVersionTextClick={async () => {
- openGithub('/releases')
+ openGithub(process.env.RELEASE_LINK)
}}
versionText={process.env.RELEASE_TAG}
/>
diff --git a/src/react/MessageFormattedString.tsx b/src/react/MessageFormattedString.tsx
index 97cef89c..09797076 100644
--- a/src/react/MessageFormattedString.tsx
+++ b/src/react/MessageFormattedString.tsx
@@ -1,6 +1,5 @@
import { useMemo } from 'react'
import { fromFormattedString } from '@xmcl/text-component'
-import nbt from 'prismarine-nbt'
import { ErrorBoundary } from '@zardoy/react-util'
import { formatMessage } from '../chatUtils'
import MessageFormatted from './MessageFormatted'
@@ -13,16 +12,16 @@ export default ({ message, fallbackColor, className }: {
}) => {
const messageJson = useMemo(() => {
if (!message) return null
- const transformIfNbt = (x) => {
- if (typeof x === 'object' && x?.type) return nbt.simplify(x) as Record
- // if (Array.isArray(x)) return x.map(transformIfNbt)
- // if (typeof x === 'object') return Object.fromEntries(Object.entries(x).map(([k, v]) => [k, transformIfNbt(v)]))
- return x
- }
- if (typeof message === 'object' && message.text?.text?.type) {
- message.text.text = transformIfNbt(message.text.text)
- message.text.extra = transformIfNbt(message.text.extra)
- }
+ // const transformIfNbt = (x) => {
+ // if (typeof x === 'object' && x?.type) return nbt.simplify(x) as Record
+ // // if (Array.isArray(x)) return x.map(transformIfNbt)
+ // // if (typeof x === 'object') return Object.fromEntries(Object.entries(x).map(([k, v]) => [k, transformIfNbt(v)]))
+ // return x
+ // }
+ // if (typeof message === 'object' && message.text?.text?.type) {
+ // message.text.text = transformIfNbt(message.text.text)
+ // message.text.extra = transformIfNbt(message.text.extra)
+ // }
try {
const texts = formatMessage(typeof message === 'string' ? fromFormattedString(message) : message)
return texts.map(text => {
diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx
index 704c77e6..e0147a33 100644
--- a/src/react/ServersListProvider.tsx
+++ b/src/react/ServersListProvider.tsx
@@ -245,7 +245,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
}
const parsed = parseServerAddress(parts.join(':'))
overrides = {
- ip: parsed.host,
+ ip: parsed.serverIpFull,
versionOverride: parsed.version,
authenticatedAccountOverride: msAuth ? true : undefined, // todo popup selector
}
diff --git a/src/testCrasher.ts b/src/testCrasher.ts
new file mode 100644
index 00000000..0383c2a6
--- /dev/null
+++ b/src/testCrasher.ts
@@ -0,0 +1,5 @@
+import { appQueryParams } from './appParams'
+
+if (appQueryParams.testCrashApp === '1') {
+ throw new Error('test error')
+}
diff --git a/src/utils.test.ts b/src/utils.test.ts
index 14505ae2..00569bf4 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -1,5 +1,10 @@
import { describe, expect, it } from 'vitest'
-import { parseServerAddress } from './parseServerAddress'
+import { parseServerAddress as parseServerAddressOriginal } from './parseServerAddress'
+
+const parseServerAddress = (address: string | undefined, removeHttp = true) => {
+ const { serverIpFull, ...result } = parseServerAddressOriginal(address, removeHttp)
+ return result
+}
describe('parseServerAddress', () => {
it('should handle undefined input', () => {
diff --git a/src/viewerConnector.ts b/src/viewerConnector.ts
index 692056e3..5096ba4d 100644
--- a/src/viewerConnector.ts
+++ b/src/viewerConnector.ts
@@ -126,9 +126,9 @@ export const getWsProtocolStream = async (url: string) => {
const CHANNEL_NAME = 'minecraft-web-client:data'
export const handleCustomChannel = async () => {
- // await new Promise(resolve => {
- // bot._client.once('login', resolve)
- // })
+ await new Promise(resolve => {
+ bot._client.once('login', resolve)
+ })
bot._client.registerChannel(CHANNEL_NAME, ['string', []], true)
const toCleanup = [] as Array<() => void>