mirror of
https://github.com/modrinth/code.git
synced 2026-09-04 05:48:57 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54d756f391 | ||
|
|
b2d73d099d |
@@ -21,7 +21,6 @@
|
|||||||
:server-name="`${auth?.user?.username}'s server`"
|
:server-name="`${auth?.user?.username}'s server`"
|
||||||
:out-of-stock-url="outOfStockUrl"
|
:out-of-stock-url="outOfStockUrl"
|
||||||
:fetch-capacity-statuses="fetchCapacityStatuses"
|
:fetch-capacity-statuses="fetchCapacityStatuses"
|
||||||
:pings="regionPings"
|
|
||||||
:regions="regions"
|
:regions="regions"
|
||||||
:refresh-payment-methods="fetchPaymentData"
|
:refresh-payment-methods="fetchPaymentData"
|
||||||
:fetch-stock="fetchStock"
|
:fetch-stock="fetchStock"
|
||||||
@@ -1233,81 +1232,16 @@ const planQuery = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const regions = ref([])
|
const regions = ref([])
|
||||||
const regionPings = ref([])
|
function fetchRegions() {
|
||||||
|
|
||||||
function pingRegions() {
|
|
||||||
client.archon.servers_v1.getRegions().then((res) => {
|
client.archon.servers_v1.getRegions().then((res) => {
|
||||||
regions.value = res
|
regions.value = res
|
||||||
regions.value.forEach((region) => {
|
|
||||||
runPingTest(region)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const PING_COUNT = 20
|
|
||||||
const PING_INTERVAL = 200
|
|
||||||
const MAX_PING_TIME = 1000
|
|
||||||
|
|
||||||
const initialIndex = {
|
|
||||||
'eu-lim': 31,
|
|
||||||
}
|
|
||||||
|
|
||||||
function runPingTest(region, index = initialIndex[region.shortcode] ?? 1) {
|
|
||||||
if (index > (initialIndex[region.shortcode] ?? 1) + 10) {
|
|
||||||
regionPings.value.push({
|
|
||||||
region: region.shortcode,
|
|
||||||
ping: -1,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsUrl = `wss://${region.shortcode}${index}.${region.zone}/pingtest`
|
|
||||||
try {
|
|
||||||
const socket = new WebSocket(wsUrl)
|
|
||||||
const pings = []
|
|
||||||
|
|
||||||
socket.onopen = () => {
|
|
||||||
for (let i = 0; i < PING_COUNT; i++) {
|
|
||||||
setTimeout(() => {
|
|
||||||
socket.send(performance.now())
|
|
||||||
}, i * PING_INTERVAL)
|
|
||||||
}
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
socket.close()
|
|
||||||
|
|
||||||
const median = Math.round([...pings].sort((a, b) => a - b)[Math.floor(pings.length / 2)])
|
|
||||||
if (median) {
|
|
||||||
regionPings.value.push({
|
|
||||||
region: region.shortcode,
|
|
||||||
ping: median,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
PING_COUNT * PING_INTERVAL + MAX_PING_TIME,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
|
||||||
pings.push(performance.now() - event.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onerror = (event) => {
|
|
||||||
console.error(
|
|
||||||
`Failed to connect pingtest WebSocket with ${wsUrl}, trying index ${index + 1}:`,
|
|
||||||
event,
|
|
||||||
)
|
|
||||||
runPingTest(region, index + 1)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to connect pingtest WebSocket with ${wsUrl}:`, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
startTyping()
|
startTyping()
|
||||||
planQuery()
|
planQuery()
|
||||||
pingRegions()
|
fetchRegions()
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(customer, (newCustomer) => {
|
watch(customer, (newCustomer) => {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export { clearNodeAuthState, nodeAuthState, setNodeAuthState } from './state/nod
|
|||||||
export * from './types'
|
export * from './types'
|
||||||
export { withJWTRetry } from './utils/jwt-retry'
|
export { withJWTRetry } from './utils/jwt-retry'
|
||||||
export { getNodeWebSocketUrl } from './utils/node-url'
|
export { getNodeWebSocketUrl } from './utils/node-url'
|
||||||
|
export { pingWebSocketUrl, type WebSocketPingOptions } from './utils/pingtest'
|
||||||
export {
|
export {
|
||||||
type ParsedSseEvent,
|
type ParsedSseEvent,
|
||||||
type ParsedSseItem,
|
type ParsedSseItem,
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
export interface WebSocketPingOptions {
|
||||||
|
count?: number
|
||||||
|
intervalMs?: number
|
||||||
|
settleDelayMs?: number
|
||||||
|
timeoutMs?: number
|
||||||
|
signal?: AbortSignal
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pingWebSocketUrl(
|
||||||
|
url: string,
|
||||||
|
options: WebSocketPingOptions = {},
|
||||||
|
): Promise<number> {
|
||||||
|
const count = options.count ?? 5
|
||||||
|
const intervalMs = options.intervalMs ?? 200
|
||||||
|
const settleDelayMs = options.settleDelayMs ?? 1000
|
||||||
|
const timeoutMs = options.timeoutMs ?? count * intervalMs + settleDelayMs + 1000
|
||||||
|
|
||||||
|
if (options.signal?.aborted) return -1
|
||||||
|
|
||||||
|
return await new Promise<number>((resolve) => {
|
||||||
|
const samples: number[] = []
|
||||||
|
const timers = new Set<ReturnType<typeof setTimeout>>()
|
||||||
|
let socket: WebSocket | undefined
|
||||||
|
let settled = false
|
||||||
|
|
||||||
|
const setTrackedTimeout = (callback: () => void, ms: number) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
timers.delete(timer)
|
||||||
|
callback()
|
||||||
|
}, ms)
|
||||||
|
timers.add(timer)
|
||||||
|
return timer
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
for (const timer of timers) clearTimeout(timer)
|
||||||
|
timers.clear()
|
||||||
|
options.signal?.removeEventListener('abort', abort)
|
||||||
|
|
||||||
|
if (
|
||||||
|
socket &&
|
||||||
|
(socket.readyState === WebSocket.CONNECTING || socket.readyState === WebSocket.OPEN)
|
||||||
|
) {
|
||||||
|
socket.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finish = (ping: number) => {
|
||||||
|
if (settled) return
|
||||||
|
settled = true
|
||||||
|
cleanup()
|
||||||
|
resolve(ping)
|
||||||
|
}
|
||||||
|
|
||||||
|
const abort = () => finish(-1)
|
||||||
|
options.signal?.addEventListener('abort', abort, { once: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket = new WebSocket(url)
|
||||||
|
} catch {
|
||||||
|
finish(-1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setTrackedTimeout(() => finish(-1), timeoutMs)
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTrackedTimeout(() => {
|
||||||
|
if (socket?.readyState === WebSocket.OPEN) {
|
||||||
|
socket.send(String(performance.now()))
|
||||||
|
}
|
||||||
|
}, i * intervalMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTrackedTimeout(
|
||||||
|
() => {
|
||||||
|
const ping =
|
||||||
|
samples.length > 0
|
||||||
|
? Math.round([...samples].sort((a, b) => a - b)[Math.floor(samples.length / 2)])
|
||||||
|
: -1
|
||||||
|
finish(ping)
|
||||||
|
},
|
||||||
|
count * intervalMs + settleDelayMs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onmessage = (event) => {
|
||||||
|
samples.push(performance.now() - Number(event.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = () => finish(-1)
|
||||||
|
socket.onclose = () => {
|
||||||
|
if (!settled && samples.length === 0) finish(-1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Archon, Labrinth } from '@modrinth/api-client'
|
import { type Archon, type Labrinth, pingWebSocketUrl } from '@modrinth/api-client'
|
||||||
import {
|
import {
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { useQueryClient } from '@tanstack/vue-query'
|
import { useQueryClient } from '@tanstack/vue-query'
|
||||||
import type Stripe from 'stripe'
|
import type Stripe from 'stripe'
|
||||||
import { computed, nextTick, ref, toRef, useTemplateRef, watch } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, ref, toRef, useTemplateRef, watch } from 'vue'
|
||||||
|
|
||||||
import { injectNotificationManager } from '#ui/providers/web-notifications.ts'
|
import { injectNotificationManager } from '#ui/providers/web-notifications.ts'
|
||||||
|
|
||||||
@@ -43,7 +43,6 @@ const props = defineProps<{
|
|||||||
paymentMethods: Stripe.PaymentMethod[]
|
paymentMethods: Stripe.PaymentMethod[]
|
||||||
customer: Stripe.Customer
|
customer: Stripe.Customer
|
||||||
currency: string
|
currency: string
|
||||||
pings: RegionPing[]
|
|
||||||
regions: Archon.Servers.v1.Region[]
|
regions: Archon.Servers.v1.Region[]
|
||||||
availableProducts: Labrinth.Billing.Internal.Product[]
|
availableProducts: Labrinth.Billing.Internal.Product[]
|
||||||
planStage?: boolean
|
planStage?: boolean
|
||||||
@@ -117,7 +116,13 @@ const steps: Step[] = props.planStage
|
|||||||
: (['region', 'payment', 'review'] as Step[])
|
: (['region', 'payment', 'review'] as Step[])
|
||||||
|
|
||||||
const isUpgrade = computed(() => !!props.existingSubscription)
|
const isUpgrade = computed(() => !!props.existingSubscription)
|
||||||
const skipRegionStep = computed(() => isUpgrade.value && !customServer.value)
|
const existingSubscriptionRegion = computed(() =>
|
||||||
|
props.existingSubscription?.metadata?.type === 'pyro'
|
||||||
|
? props.existingSubscription.metadata.region
|
||||||
|
: undefined,
|
||||||
|
)
|
||||||
|
const hideRegionSelection = computed(() => isUpgrade.value && !!existingSubscriptionRegion.value)
|
||||||
|
const skipRegionStep = computed(() => !customServer.value && hideRegionSelection.value)
|
||||||
const visibleSteps = computed(() => steps.filter((s) => !(s === 'region' && skipRegionStep.value)))
|
const visibleSteps = computed(() => steps.filter((s) => !(s === 'region' && skipRegionStep.value)))
|
||||||
|
|
||||||
const titles: Record<Step, MessageDescriptor> = {
|
const titles: Record<Step, MessageDescriptor> = {
|
||||||
@@ -144,12 +149,81 @@ const currentRegion = computed(() => {
|
|||||||
return props.regions.find((region) => region.shortcode === selectedRegion.value)
|
return props.regions.find((region) => region.shortcode === selectedRegion.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const regionPings = ref<RegionPing[]>([])
|
||||||
|
|
||||||
const currentPing = computed(() => {
|
const currentPing = computed(() => {
|
||||||
return props.pings.find((ping) => ping.region === currentRegion.value?.shortcode)?.ping
|
return regionPings.value.find((ping) => ping.region === currentRegion.value?.shortcode)?.ping
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentStep = ref<Step>()
|
const currentStep = ref<Step>()
|
||||||
|
|
||||||
|
const PING_COUNT = 5
|
||||||
|
const PING_INTERVAL = 200
|
||||||
|
const MAX_PING_TIME = 1000
|
||||||
|
|
||||||
|
const initialIndex: Record<string, number> = {
|
||||||
|
'eu-lim': 31,
|
||||||
|
}
|
||||||
|
|
||||||
|
let regionPingAbortController: AbortController | null = null
|
||||||
|
|
||||||
|
function setRegionPing(region: Archon.Servers.v1.Region, ping: number) {
|
||||||
|
regionPings.value = regionPings.value.filter((entry) => entry.region !== region.shortcode)
|
||||||
|
regionPings.value.push({
|
||||||
|
region: region.shortcode,
|
||||||
|
ping,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRegionPings() {
|
||||||
|
regionPingAbortController?.abort()
|
||||||
|
regionPingAbortController = null
|
||||||
|
regionPings.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRegionPings() {
|
||||||
|
regionPingAbortController?.abort()
|
||||||
|
regionPings.value = []
|
||||||
|
|
||||||
|
const controller = new AbortController()
|
||||||
|
regionPingAbortController = controller
|
||||||
|
|
||||||
|
for (const region of props.regions) {
|
||||||
|
void runRegionPing(region, initialIndex[region.shortcode] ?? 1, controller.signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureRegionPings() {
|
||||||
|
if (regionPingAbortController && !regionPingAbortController.signal.aborted) return
|
||||||
|
startRegionPings()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runRegionPing(region: Archon.Servers.v1.Region, index: number, signal: AbortSignal) {
|
||||||
|
if (signal.aborted) return
|
||||||
|
|
||||||
|
const ping = await pingWebSocketUrl(`wss://${region.shortcode}${index}.${region.zone}/pingtest`, {
|
||||||
|
count: PING_COUNT,
|
||||||
|
intervalMs: PING_INTERVAL,
|
||||||
|
settleDelayMs: MAX_PING_TIME,
|
||||||
|
timeoutMs: PING_COUNT * PING_INTERVAL + MAX_PING_TIME + 1000,
|
||||||
|
signal,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (signal.aborted) return
|
||||||
|
|
||||||
|
if (ping > 0) {
|
||||||
|
setRegionPing(region, ping)
|
||||||
|
} else {
|
||||||
|
setRegionPing(region, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRegionPingsIfNeeded() {
|
||||||
|
if (currentStep.value === 'region') {
|
||||||
|
ensureRegionPings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const currentStepIndex = computed(() =>
|
const currentStepIndex = computed(() =>
|
||||||
currentStep.value ? visibleSteps.value.indexOf(currentStep.value) : -1,
|
currentStep.value ? visibleSteps.value.indexOf(currentStep.value) : -1,
|
||||||
)
|
)
|
||||||
@@ -268,6 +342,7 @@ async function setStep(step: Step | undefined, skipValidation = false) {
|
|||||||
|
|
||||||
if (await beforeProceed(step)) {
|
if (await beforeProceed(step)) {
|
||||||
currentStep.value = step
|
currentStep.value = step
|
||||||
|
startRegionPingsIfNeeded()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
await afterProceed(step)
|
await afterProceed(step)
|
||||||
@@ -285,10 +360,10 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.existingSubscription,
|
existingSubscriptionRegion,
|
||||||
(sub) => {
|
(region) => {
|
||||||
if (sub?.metadata?.type === 'pyro' && sub.metadata.region) {
|
if (region) {
|
||||||
selectedRegion.value = sub.metadata.region
|
selectedRegion.value = region
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
@@ -325,6 +400,7 @@ function begin(
|
|||||||
currentStep.value = skipPlanStep
|
currentStep.value = skipPlanStep
|
||||||
? (visibleSteps.value[1] ?? visibleSteps.value[0])
|
? (visibleSteps.value[1] ?? visibleSteps.value[0])
|
||||||
: visibleSteps.value[0]
|
: visibleSteps.value[0]
|
||||||
|
startRegionPingsIfNeeded()
|
||||||
skipPaymentMethods.value = true
|
skipPaymentMethods.value = true
|
||||||
projectId.value = project
|
projectId.value = project
|
||||||
modal.value?.show()
|
modal.value?.show()
|
||||||
@@ -338,6 +414,13 @@ const emit = defineEmits<{
|
|||||||
(e: 'hide' | 'purchase-success'): void
|
(e: 'hide' | 'purchase-success'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
function handleHide() {
|
||||||
|
stopRegionPings()
|
||||||
|
emit('hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(stopRegionPings)
|
||||||
|
|
||||||
function handleChooseCustom() {
|
function handleChooseCustom() {
|
||||||
customServer.value = true
|
customServer.value = true
|
||||||
selectedPlan.value = undefined
|
selectedPlan.value = undefined
|
||||||
@@ -365,7 +448,7 @@ function goToBreadcrumbStep(id: string) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<NewModal ref="modal" @hide="$emit('hide')">
|
<NewModal ref="modal" @hide="handleHide">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex items-center gap-1 font-bold text-secondary">
|
<div class="flex items-center gap-1 font-bold text-secondary">
|
||||||
<template v-for="(step, index) in visibleSteps" :key="step">
|
<template v-for="(step, index) in visibleSteps" :key="step">
|
||||||
@@ -408,9 +491,9 @@ function goToBreadcrumbStep(id: string) {
|
|||||||
v-model:region="selectedRegion"
|
v-model:region="selectedRegion"
|
||||||
v-model:plan="selectedPlan"
|
v-model:plan="selectedPlan"
|
||||||
:regions="regions"
|
:regions="regions"
|
||||||
:pings="pings"
|
:pings="regionPings"
|
||||||
:custom="customServer"
|
:custom="customServer"
|
||||||
:hide-region-selection="isUpgrade"
|
:hide-region-selection="hideRegionSelection"
|
||||||
:available-products="availableProducts"
|
:available-products="availableProducts"
|
||||||
:currency="currency"
|
:currency="currency"
|
||||||
:interval="selectedInterval"
|
:interval="selectedInterval"
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
:payment-methods="paymentMethods"
|
:payment-methods="paymentMethods"
|
||||||
:currency="selectedCurrency"
|
:currency="selectedCurrency"
|
||||||
:return-url="checkoutReturnUrl"
|
:return-url="checkoutReturnUrl"
|
||||||
:pings="regionPings"
|
|
||||||
:regions="regionsData"
|
:regions="regionsData"
|
||||||
:refresh-payment-methods="fetchPaymentData"
|
:refresh-payment-methods="fetchPaymentData"
|
||||||
:fetch-stock="fetchStock"
|
:fetch-stock="fetchStock"
|
||||||
@@ -62,13 +61,6 @@ const customer = ref<any>(null)
|
|||||||
const paymentMethods = ref<any[]>([])
|
const paymentMethods = ref<any[]>([])
|
||||||
const selectedCurrency = ref<string>('USD')
|
const selectedCurrency = ref<string>('USD')
|
||||||
|
|
||||||
const regionPings = ref<
|
|
||||||
{
|
|
||||||
region: string
|
|
||||||
ping: number
|
|
||||||
}[]
|
|
||||||
>([])
|
|
||||||
|
|
||||||
const pyroProducts = (props.products as Labrinth.Billing.Internal.Product[])
|
const pyroProducts = (props.products as Labrinth.Billing.Internal.Product[])
|
||||||
.filter((p) => p?.metadata?.type === 'pyro' || p?.metadata?.type === 'medal')
|
.filter((p) => p?.metadata?.type === 'pyro' || p?.metadata?.type === 'medal')
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
@@ -96,14 +88,6 @@ const { data: regionsData } = useQuery({
|
|||||||
queryFn: () => archon.servers_v1.getRegions(),
|
queryFn: () => archon.servers_v1.getRegions(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const PING_COUNT = 20
|
|
||||||
const PING_INTERVAL = 200
|
|
||||||
const MAX_PING_TIME = 1000
|
|
||||||
|
|
||||||
const initialIndex = {
|
|
||||||
'eu-lim': 31,
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
customerData,
|
customerData,
|
||||||
(newCustomer) => {
|
(newCustomer) => {
|
||||||
@@ -120,18 +104,6 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
|
||||||
regionsData,
|
|
||||||
(newRegions) => {
|
|
||||||
if (newRegions) {
|
|
||||||
newRegions.forEach((region) => {
|
|
||||||
runPingTest(region)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
async function fetchPaymentData() {
|
async function fetchPaymentData() {
|
||||||
await refetchPaymentMethods()
|
await refetchPaymentMethods()
|
||||||
}
|
}
|
||||||
@@ -144,57 +116,6 @@ async function fetchStock(
|
|||||||
return result.available
|
return result.available
|
||||||
}
|
}
|
||||||
|
|
||||||
function runPingTest(
|
|
||||||
region: Archon.Servers.v1.Region,
|
|
||||||
index = initialIndex[region.shortcode] ?? 1,
|
|
||||||
) {
|
|
||||||
if (index > (initialIndex[region.shortcode] ?? 1) + 10) {
|
|
||||||
regionPings.value.push({
|
|
||||||
region: region.shortcode,
|
|
||||||
ping: -1,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsUrl = `wss://${region.shortcode}${index}.${region.zone}/pingtest`
|
|
||||||
try {
|
|
||||||
const socket = new WebSocket(wsUrl)
|
|
||||||
const pings: number[] = []
|
|
||||||
|
|
||||||
socket.onopen = () => {
|
|
||||||
for (let i = 0; i < PING_COUNT; i++) {
|
|
||||||
setTimeout(() => {
|
|
||||||
socket.send(String(performance.now()))
|
|
||||||
}, i * PING_INTERVAL)
|
|
||||||
}
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
socket.close()
|
|
||||||
const median = Math.round([...pings].sort((a, b) => a - b)[Math.floor(pings.length / 2)])
|
|
||||||
if (median) {
|
|
||||||
regionPings.value.push({
|
|
||||||
region: region.shortcode,
|
|
||||||
ping: median,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
PING_COUNT * PING_INTERVAL + MAX_PING_TIME,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
|
||||||
const start = Number(event.data)
|
|
||||||
pings.push(performance.now() - start)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onerror = () => {
|
|
||||||
runPingTest(region, index + 1)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscription = ref<Labrinth.Billing.Internal.UserSubscription | null>(null)
|
const subscription = ref<Labrinth.Billing.Internal.UserSubscription | null>(null)
|
||||||
const pendingDowngradeBody = ref<Labrinth.Billing.Internal.EditSubscriptionRequest | null>(null)
|
const pendingDowngradeBody = ref<Labrinth.Billing.Internal.EditSubscriptionRequest | null>(null)
|
||||||
const currentPlanFromSubscription = computed<Labrinth.Billing.Internal.Product | undefined>(() => {
|
const currentPlanFromSubscription = computed<Labrinth.Billing.Internal.Product | undefined>(() => {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
:customer="customer"
|
:customer="customer"
|
||||||
:payment-methods="paymentMethods"
|
:payment-methods="paymentMethods"
|
||||||
:currency="selectedCurrency"
|
:currency="selectedCurrency"
|
||||||
:pings="regionPings"
|
|
||||||
:regions="regions"
|
:regions="regions"
|
||||||
:refresh-payment-methods="fetchPaymentData"
|
:refresh-payment-methods="fetchPaymentData"
|
||||||
:fetch-stock="fetchStock"
|
:fetch-stock="fetchStock"
|
||||||
@@ -407,12 +406,6 @@ const medalUpgradeModal = ref<UpgradeModalRef | null>(null)
|
|||||||
const resubscribeModal = ref<InstanceType<typeof ResubscribeModal> | null>(null)
|
const resubscribeModal = ref<InstanceType<typeof ResubscribeModal> | null>(null)
|
||||||
const affiliateCode = ref<string | null>(null)
|
const affiliateCode = ref<string | null>(null)
|
||||||
const selectedCurrency = ref<string>('USD')
|
const selectedCurrency = ref<string>('USD')
|
||||||
const regionPings = ref<
|
|
||||||
{
|
|
||||||
region: string
|
|
||||||
ping: number
|
|
||||||
}[]
|
|
||||||
>([])
|
|
||||||
|
|
||||||
const pyroProducts = computed(() => {
|
const pyroProducts = computed(() => {
|
||||||
return [...props.products]
|
return [...props.products]
|
||||||
@@ -453,27 +446,6 @@ const { data: regions, isLoading: regionsLoading } = useQuery({
|
|||||||
enabled: loggedIn,
|
enabled: loggedIn,
|
||||||
})
|
})
|
||||||
|
|
||||||
const PING_COUNT = 20
|
|
||||||
const PING_INTERVAL = 200
|
|
||||||
const MAX_PING_TIME = 1000
|
|
||||||
|
|
||||||
const initialIndex = {
|
|
||||||
'eu-lim': 31,
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
regions,
|
|
||||||
(newRegions) => {
|
|
||||||
regionPings.value = []
|
|
||||||
if (newRegions) {
|
|
||||||
newRegions.forEach((region) => {
|
|
||||||
runPingTest(region)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
async function fetchPaymentData() {
|
async function fetchPaymentData() {
|
||||||
await Promise.all([refetchCustomer(), refetchPaymentMethods()])
|
await Promise.all([refetchCustomer(), refetchPaymentMethods()])
|
||||||
}
|
}
|
||||||
@@ -486,82 +458,6 @@ async function fetchStock(
|
|||||||
return result.available
|
return result.available
|
||||||
}
|
}
|
||||||
|
|
||||||
function runPingTest(
|
|
||||||
region: Archon.Servers.v1.Region,
|
|
||||||
index = initialIndex[region.shortcode] ?? 1,
|
|
||||||
) {
|
|
||||||
if (index > (initialIndex[region.shortcode] ?? 1) + 10) {
|
|
||||||
regionPings.value.push({
|
|
||||||
region: region.shortcode,
|
|
||||||
ping: -1,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsUrl = `wss://${region.shortcode}${index}.${region.zone}/pingtest`
|
|
||||||
try {
|
|
||||||
const socket = new WebSocket(wsUrl)
|
|
||||||
const pings: number[] = []
|
|
||||||
let finalized = false
|
|
||||||
|
|
||||||
const finalize = (ping: number) => {
|
|
||||||
if (finalized) return
|
|
||||||
finalized = true
|
|
||||||
clearTimeout(connectTimeout)
|
|
||||||
regionPings.value = regionPings.value.filter((entry) => entry.region !== region.shortcode)
|
|
||||||
regionPings.value.push({
|
|
||||||
region: region.shortcode,
|
|
||||||
ping,
|
|
||||||
})
|
|
||||||
socket.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
const retryNext = () => {
|
|
||||||
if (finalized) return
|
|
||||||
finalized = true
|
|
||||||
clearTimeout(connectTimeout)
|
|
||||||
socket.close()
|
|
||||||
runPingTest(region, index + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent hangs where the socket never opens or errors.
|
|
||||||
const connectTimeout = setTimeout(() => {
|
|
||||||
retryNext()
|
|
||||||
}, 3000)
|
|
||||||
|
|
||||||
socket.onopen = () => {
|
|
||||||
clearTimeout(connectTimeout)
|
|
||||||
|
|
||||||
for (let i = 0; i < PING_COUNT; i++) {
|
|
||||||
setTimeout(() => {
|
|
||||||
socket.send(String(performance.now()))
|
|
||||||
}, i * PING_INTERVAL)
|
|
||||||
}
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
const median =
|
|
||||||
pings.length > 0
|
|
||||||
? Math.round([...pings].sort((a, b) => a - b)[Math.floor(pings.length / 2)])
|
|
||||||
: -1
|
|
||||||
finalize(median)
|
|
||||||
},
|
|
||||||
PING_COUNT * PING_INTERVAL + MAX_PING_TIME,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
|
||||||
const start = Number(event.data)
|
|
||||||
pings.push(performance.now() - start)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onerror = () => {
|
|
||||||
retryNext()
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
runPingTest(region, index + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: serverResponse,
|
data: serverResponse,
|
||||||
error: fetchError,
|
error: fetchError,
|
||||||
|
|||||||
Reference in New Issue
Block a user