Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
import {
type BootWiFiRecoveryChoice,
bootWiFiFailureMessage,
networkReadyResultForRecoveryChoice,
shouldRetryBootWiFiAttempt,
} from 'boot-network-recovery'
import type { NetworkReadyResult } from 'capabilities'
import { createLocalPeerCapability } from 'local-peer-capability'
import type { LocalPeerCapability } from 'local-peer-types'
import { localize } from 'localization'
import config from 'mc/config'
import { wait } from 'stackchan-util'
import { connectStoredWiFi, stopStoredWiFiConnection } from 'stored-wifi'
export type { NetworkReadyResult } from 'capabilities'
export type HostBootServices = {
connectivity: {
network: {
ready: Promise<NetworkReadyResult>
}
localPeer?: LocalPeerCapability
}
}
export type BootWiFiStatus = {
attempt: number
maxAttempts: number
message: string
}
export type HostBootServicesOptions = {
wifi?: {
maxAttempts?: number
retryDelayMs?: number
onStatusChanged?: (status: BootWiFiStatus) => void
promptRecoveryChoice?: (status: BootWiFiStatus & { reason: string }) => Promise<BootWiFiRecoveryChoice>
}
}
type BootWiFiConfig = {
ssid?: unknown
password?: unknown
}
type BootConfig = {
wifi?: BootWiFiConfig
}
const NOT_STARTED: NetworkReadyResult = {
status: 'skipped',
reason: 'host boot services not started',
}
const DEFAULT_BOOT_WIFI_MAX_ATTEMPTS = 3
const DEFAULT_BOOT_WIFI_RETRY_DELAY_MS = 500
let bootServices: HostBootServices = {
connectivity: {
network: {
ready: Promise.resolve(NOT_STARTED),
},
},
}
export function startHostBootServices(options: HostBootServicesOptions = {}): HostBootServices {
const networkReady = startStoredWiFi(options.wifi)
const localPeer = createLocalPeerCapability()
bootServices = {
connectivity: {
network: {
ready: networkReady,
},
localPeer,
},
}
return bootServices
}
export function getHostBootServices(): HostBootServices {
return bootServices
}
async function startStoredWiFi(
options: NonNullable<HostBootServicesOptions['wifi']> = {},
): Promise<NetworkReadyResult> {
const maxAttempts = options.maxAttempts ?? DEFAULT_BOOT_WIFI_MAX_ATTEMPTS
const retryDelayMs = options.retryDelayMs ?? DEFAULT_BOOT_WIFI_RETRY_DELAY_MS
for (;;) {
let lastReason = 'connection failed'
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
options.onStatusChanged?.({
attempt,
maxAttempts,
message: localize('splash.connecting', { attempt, maxAttempts }),
})
const result = await connectStoredWiFiOnce()
if (result.status !== 'failed') {
return result
}
lastReason = result.reason
trace(`[network] boot Wi-Fi attempt ${attempt}/${maxAttempts} failed: ${lastReason}\n`)
if (shouldRetryBootWiFiAttempt(attempt, maxAttempts)) {
await wait(retryDelayMs)
}
}
const message = bootWiFiFailureMessage(lastReason)
if (!options.promptRecoveryChoice) {
return { status: 'failed', reason: lastReason }
}
const choice = await options.promptRecoveryChoice({
attempt: maxAttempts,
maxAttempts,
message,
reason: lastReason,
})
const result = networkReadyResultForRecoveryChoice(choice, lastReason)
if (result) {
trace(`[network] ${result.reason}\n`)
return result
}
trace('[network] retrying Wi-Fi by user request\n')
}
}
function connectStoredWiFiOnce(): Promise<NetworkReadyResult> {
return new Promise((resolve) => {
let settled = false
const finish = (result: NetworkReadyResult) => {
if (settled) return
settled = true
if (result.status !== 'connected') {
stopStoredWiFiConnection()
}
resolve(result)
}
try {
stopStoredWiFiConnection()
const started = connectStoredWiFi({
...getBootWiFiCredentials(),
scanBeforeConnect: true,
onConnected: () => finish({ status: 'connected' }),
onError: (reason) => {
const message = reason ?? 'connection failed'
trace(`[network] connection failed: ${message}\n`)
finish({ status: 'failed', reason: message })
},
})
if (!started) {
finish({ status: 'skipped', reason: 'missing Wi-Fi credentials' })
}
} catch (error) {
const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : String(error)
trace(`[network] connection failed: ${message}\n`)
finish({ status: 'failed', reason: message })
}
})
}
function getBootWiFiCredentials(): { ssid?: string; password?: string } {
const bootConfig = config as BootConfig
const wifi = bootConfig.wifi ?? {}
const ssid = wifi.ssid
const password = wifi.password
return {
ssid: typeof ssid === 'string' ? ssid : undefined,
password: typeof password === 'string' ? password : undefined,
}
}
c44ec7454This file is pinned to the selected release. Download the original to open it in your design tool.