misc fixes for alpha.16 (#3091)

* port misc fixes from feature/nvidia

* switch back to official tor proxy on 9050

* refactor OpenUI

* fix typo

* fixes, plus getServiceManifest

* fix EffectCreator, bump to beta.47

* fixes
This commit is contained in:
Aiden McClelland
2026-01-10 12:58:17 -07:00
committed by GitHub
parent 466b9217b5
commit e8ef39adad
37 changed files with 491 additions and 231 deletions

View File

@@ -1,3 +1,12 @@
export type AccessType =
| 'tor'
| 'mdns'
| 'localhost'
| 'ipv4'
| 'ipv6'
| 'domain'
| 'wan-ipv4'
export type WorkspaceConfig = {
gitHash: string
useMocks: boolean
@@ -8,7 +17,7 @@ export type WorkspaceConfig = {
version: string
}
mocks: {
maskAs: 'tor' | 'local' | 'localhost' | 'ipv4' | 'ipv6' | 'clearnet'
maskAs: AccessType
maskAsHttps: boolean
skipStartupAlerts: boolean
}

View File

@@ -206,119 +206,67 @@ export class InterfaceService {
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */
launchableAddress(ui: T.ServiceInterface, host: T.Host): string {
const hostnameInfos = this.hostnameInfo(ui, host)
const addresses = utils.filledAddress(host, ui.addressInfo)
if (!hostnameInfos.length) return ''
if (!addresses.hostnames.length) return ''
const addressInfo = ui.addressInfo
const username = addressInfo.username ? addressInfo.username + '@' : ''
const suffix = addressInfo.suffix || ''
const url = new URL(`https://${username}placeholder${suffix}`)
const use = (hostname: {
value: string
port: number | null
sslPort: number | null
}) => {
url.hostname = hostname.value
const useSsl =
hostname.port && hostname.sslPort
? this.config.isHttps()
: !!hostname.sslPort
url.protocol = useSsl
? `${addressInfo.sslScheme || 'https'}:`
: `${addressInfo.scheme || 'http'}:`
const port = useSsl ? hostname.sslPort : hostname.port
const omitPort = useSsl
? ui.addressInfo.sslScheme === 'https' && port === 443
: ui.addressInfo.scheme === 'http' && port === 80
if (!omitPort && port) url.port = String(port)
}
const useFirst = (
hostnames: (
| {
value: string
port: number | null
sslPort: number | null
}
| undefined
)[],
) => {
const first = hostnames.find(h => h)
if (first) {
use(first)
}
return !!first
const publicDomains = addresses.filter({
kind: 'domain',
visibility: 'public',
})
const tor = addresses.filter({ kind: 'onion' })
const wanIp = addresses.filter({ kind: 'ipv4', visibility: 'public' })
const bestPublic = [publicDomains, tor, wanIp].flatMap(h =>
h.format('urlstring'),
)[0]
const privateDomains = addresses.filter({
kind: 'domain',
visibility: 'private',
})
const mdns = addresses.filter({ kind: 'mdns' })
const bestPrivate = [privateDomains, mdns].flatMap(h =>
h.format('urlstring'),
)[0]
let matching
let onLan = false
switch (this.config.accessType) {
case 'ipv4':
matching = addresses.nonLocal
.filter({
kind: 'ipv4',
predicate: h => h.hostname.value === this.config.hostname,
})
.format('urlstring')[0]
onLan = true
break
case 'ipv6':
matching = addresses.nonLocal
.filter({
kind: 'ipv6',
predicate: h => h.hostname.value === this.config.hostname,
})
.format('urlstring')[0]
break
case 'localhost':
matching = addresses
.filter({ kind: 'localhost' })
.format('urlstring')[0]
onLan = true
break
case 'tor':
matching = tor.format('urlstring')[0]
break
case 'mdns':
matching = mdns.format('urlstring')[0]
onLan = true
break
}
const ipHostnames = hostnameInfos
.filter(h => h.kind === 'ip')
.map(h => h.hostname) as T.IpHostname[]
const domainHostname = ipHostnames
.filter(h => h.kind === 'domain')
.map(h => h as T.IpHostname & { kind: 'domain' })
.map(h => ({
value: h.value,
sslPort: h.sslPort,
port: h.port,
}))[0]
const wanIpHostname = hostnameInfos
.filter(h => h.kind === 'ip' && h.public && h.hostname.kind !== 'domain')
.map(h => h.hostname as Exclude<T.IpHostname, { kind: 'domain' }>)
.map(h => ({
value: h.value,
sslPort: h.sslPort,
port: h.port,
}))[0]
const onionHostname = hostnameInfos
.filter(h => h.kind === 'onion')
.map(h => h as T.HostnameInfo & { kind: 'onion' })
.map(h => ({
value: h.hostname.value,
sslPort: h.hostname.sslPort,
port: h.hostname.port,
}))[0]
const localHostname = ipHostnames
.filter(h => h.kind === 'local')
.map(h => h as T.IpHostname & { kind: 'local' })
.map(h => ({ value: h.value, sslPort: h.sslPort, port: h.port }))[0]
if (this.config.isClearnet()) {
if (
!useFirst([domainHostname, wanIpHostname, onionHostname, localHostname])
) {
return ''
}
} else if (this.config.isTor()) {
if (
!useFirst([onionHostname, domainHostname, wanIpHostname, localHostname])
) {
return ''
}
} else if (this.config.isIpv6()) {
const ipv6Hostname = ipHostnames.find(h => h.kind === 'ipv6') as {
kind: 'ipv6'
value: string
scopeId: number
port: number | null
sslPort: number | null
}
if (!useFirst([ipv6Hostname, localHostname])) {
return ''
}
} else {
// ipv4 or .local or localhost
if (!localHostname) return ''
use({
value: this.config.hostname,
port: localHostname.port,
sslPort: localHostname.sslPort,
})
}
return url.href
if (matching) return matching
if (onLan && bestPrivate) return bestPrivate
if (bestPublic) return bestPublic
return ''
}
private hostnameInfo(
@@ -330,7 +278,7 @@ export class InterfaceService {
return (
hostnameInfo?.filter(
h =>
this.config.isLocalhost() ||
this.config.accessType === 'localhost' ||
!(
h.kind === 'ip' &&
((h.hostname.kind === 'ipv6' &&

View File

@@ -92,7 +92,7 @@ import { MarketplacePkgSideload, validateS9pk } from './sideload.utils'
],
})
export default class SideloadComponent {
readonly isTor = inject(ConfigService).isTor()
readonly isTor = inject(ConfigService).accessType === 'tor'
file: File | null = null
readonly package = signal<MarketplacePkgSideload | null>(null)

View File

@@ -245,7 +245,7 @@ export default class SystemGeneralComponent {
private readonly errorService = inject(ErrorService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService)
private readonly isTor = inject(ConfigService).isTor()
private readonly isTor = inject(ConfigService).accessType === 'tor'
private readonly dialog = inject(DialogService)
private readonly i18n = inject(i18nPipe)
private readonly injector = inject(INJECTOR)

View File

@@ -31,6 +31,6 @@ import { i18nPipe } from '@start9labs/shared'
imports: [TuiLabel, FormsModule, TuiCheckbox, i18nPipe],
})
export class SystemWipeComponent {
readonly isTor = inject(ConfigService).isTor()
readonly isTor = inject(ConfigService).accessType === 'tor'
readonly component = inject(SystemGeneralComponent)
}

View File

@@ -385,7 +385,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -420,7 +420,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -465,7 +465,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -500,7 +500,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -547,7 +547,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.5',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
@@ -595,7 +595,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.4',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
@@ -647,7 +647,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -682,7 +682,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -727,7 +727,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
@@ -775,7 +775,7 @@ export namespace Mock {
marketingSite: '',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.46',
sdkVersion: '0.4.0-beta.47',
gitHash: 'fakehash',
icon: PROXY_ICON,
sourceVersion: null,

View File

@@ -1,5 +1,5 @@
import { Inject, Injectable, DOCUMENT } from '@angular/core'
import { WorkspaceConfig } from '@start9labs/shared'
import { AccessType, WorkspaceConfig } from '@start9labs/shared'
import { T, utils } from '@start9labs/start-sdk'
const {
@@ -29,53 +29,29 @@ export class ConfigService {
supportsWebSockets = !!window.WebSocket
defaultRegistry = defaultRegistry
isTor(): boolean {
return useMocks ? mocks.maskAs === 'tor' : this.hostname.endsWith('.onion')
}
isLocalhost(): boolean {
return useMocks
? mocks.maskAs === 'localhost'
: this.hostname === 'localhost' || this.hostname === '127.0.0.1'
private getAccessType = utils.once(() => {
if (useMocks) return mocks.maskAs
if (this.hostname === 'localhost') return 'localhost'
if (this.hostname.endsWith('.onion')) return 'tor'
if (this.hostname.endsWith('.local')) return 'mdns'
let ip = null
try {
ip = utils.IpAddress.parse(this.hostname.replace(/[\[\]]/g, ''))
} catch {}
if (ip) {
if (utils.IPV4_LOOPBACK.contains(ip) || utils.IPV6_LOOPBACK.contains(ip))
return 'localhost'
if (ip.isIpv4()) return ip.isPublic() ? 'wan-ipv4' : 'ipv4'
return 'ipv6'
}
return 'domain'
})
get accessType(): AccessType {
return this.getAccessType()
}
isLanHttp(): boolean {
return !this.isTor() && !this.isLocalhost() && !this.isHttps()
}
private isLocal(): boolean {
return useMocks
? mocks.maskAs === 'local'
: this.hostname.endsWith('.local')
}
private isLanIpv4(): boolean {
return useMocks
? mocks.maskAs === 'ipv4'
: new RegExp(utils.Patterns.ipv4.regex).test(this.hostname) &&
(this.hostname.startsWith('192.168.') ||
this.hostname.startsWith('10.') ||
(this.hostname.startsWith('172.') &&
!![this.hostname.split('.').map(Number)[1] || NaN].filter(
n => n >= 16 && n < 32,
).length))
}
isIpv6(): boolean {
return useMocks
? mocks.maskAs === 'ipv6'
: new RegExp(utils.Patterns.ipv6.regex).test(this.hostname)
}
isClearnet(): boolean {
return useMocks
? mocks.maskAs === 'clearnet'
: this.isHttps() &&
!this.isTor() &&
!this.isLocal() &&
!this.isLocalhost() &&
!this.isLanIpv4() &&
!this.isIpv6()
return !this.isHttps() && !['localhost', 'tor'].includes(this.accessType)
}
isHttps(): boolean {