mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
task fix and keyboard fix (#3130)
* task fix and keyboard fix
* fixes for build scripts
* passthrough feature
* feat: inline domain health checks and improve address UX
- addPublicDomain returns DNS query + port check results (AddPublicDomainRes)
so frontend skips separate API calls after adding a domain
- addPrivateDomain returns check_dns result for the gateway
- Support multiple ports per domain in validation modal (deduplicated)
- Run port checks concurrently via futures::future::join_all
- Add note to add-domain dialog showing other interfaces on same host
- Add addXForwardedHeaders to knownProtocols in SDK Host.ts
- Add plugin filter kind, pluginId filter, matchesAny, and docs to
getServiceInterface.ts
- Add PassthroughInfo type and passthroughs field to NetworkInfo
- Pluralize "port forwarding rules" in i18n dictionaries
* feat: add shared host note to private domain dialog with i18n
* fix: scope public domain to single binding and return single port check
Accept internalPort in AddPublicDomainParams to target a specific
binding. Disable the domain on all other bindings. Return a single
CheckPortRes instead of Vec. Revert multi-port UI to singular port
display from 0f8a66b35.
* better shared hostname approach, and improve look-feel of addresses tables
* fix starttls
* preserve usb as top efi boot option
* fix race condition in wan ip check
* sdk beta.56
* various bug, improve smtp
* multiple bugs, better outbound gateway UX
* remove non option from smtp for better package compat
* bump sdk
---------
Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -1,41 +1,57 @@
|
||||
import { SmtpValue } from '../../types'
|
||||
import { GetSystemSmtp, Patterns } from '../../util'
|
||||
import { InputSpec, InputSpecOf } from './builder/inputSpec'
|
||||
import { InputSpec } from './builder/inputSpec'
|
||||
import { Value } from './builder/value'
|
||||
import { Variants } from './builder/variants'
|
||||
|
||||
const securityVariants = Variants.of({
|
||||
tls: {
|
||||
name: 'TLS',
|
||||
spec: InputSpec.of({
|
||||
port: Value.dynamicText(async () => ({
|
||||
name: 'Port',
|
||||
required: true,
|
||||
default: '465',
|
||||
disabled: 'Fixed for TLS',
|
||||
})),
|
||||
}),
|
||||
},
|
||||
starttls: {
|
||||
name: 'STARTTLS',
|
||||
spec: InputSpec.of({
|
||||
port: Value.select({
|
||||
name: 'Port',
|
||||
default: '587',
|
||||
values: { '25': '25', '587': '587', '2525': '2525' },
|
||||
}),
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates an SMTP field spec with provider-specific defaults pre-filled.
|
||||
*/
|
||||
function smtpFields(
|
||||
defaults: {
|
||||
host?: string
|
||||
port?: number
|
||||
security?: 'starttls' | 'tls'
|
||||
hostDisabled?: boolean
|
||||
} = {},
|
||||
): InputSpec<SmtpValue> {
|
||||
return InputSpec.of<InputSpecOf<SmtpValue>>({
|
||||
host: Value.text({
|
||||
name: 'Host',
|
||||
required: true,
|
||||
default: defaults.host ?? null,
|
||||
placeholder: 'smtp.example.com',
|
||||
}),
|
||||
port: Value.number({
|
||||
name: 'Port',
|
||||
required: true,
|
||||
default: defaults.port ?? 587,
|
||||
min: 1,
|
||||
max: 65535,
|
||||
integer: true,
|
||||
}),
|
||||
security: Value.select({
|
||||
) {
|
||||
const hostSpec = Value.text({
|
||||
name: 'Host',
|
||||
required: true,
|
||||
default: defaults.host ?? null,
|
||||
placeholder: 'smtp.example.com',
|
||||
})
|
||||
|
||||
return InputSpec.of({
|
||||
host: defaults.hostDisabled
|
||||
? hostSpec.withDisabled('Fixed for this provider')
|
||||
: hostSpec,
|
||||
security: Value.union({
|
||||
name: 'Connection Security',
|
||||
default: defaults.security ?? 'starttls',
|
||||
values: {
|
||||
starttls: 'STARTTLS',
|
||||
tls: 'TLS',
|
||||
},
|
||||
default: defaults.security ?? 'tls',
|
||||
variants: securityVariants,
|
||||
}),
|
||||
from: Value.text({
|
||||
name: 'From Address',
|
||||
@@ -72,40 +88,39 @@ export const smtpProviderVariants = Variants.of({
|
||||
name: 'Gmail',
|
||||
spec: smtpFields({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 587,
|
||||
security: 'starttls',
|
||||
security: 'tls',
|
||||
hostDisabled: true,
|
||||
}),
|
||||
},
|
||||
ses: {
|
||||
name: 'Amazon SES',
|
||||
spec: smtpFields({
|
||||
host: 'email-smtp.us-east-1.amazonaws.com',
|
||||
port: 587,
|
||||
security: 'starttls',
|
||||
security: 'tls',
|
||||
}),
|
||||
},
|
||||
sendgrid: {
|
||||
name: 'SendGrid',
|
||||
spec: smtpFields({
|
||||
host: 'smtp.sendgrid.net',
|
||||
port: 587,
|
||||
security: 'starttls',
|
||||
security: 'tls',
|
||||
hostDisabled: true,
|
||||
}),
|
||||
},
|
||||
mailgun: {
|
||||
name: 'Mailgun',
|
||||
spec: smtpFields({
|
||||
host: 'smtp.mailgun.org',
|
||||
port: 587,
|
||||
security: 'starttls',
|
||||
security: 'tls',
|
||||
hostDisabled: true,
|
||||
}),
|
||||
},
|
||||
protonmail: {
|
||||
name: 'Proton Mail',
|
||||
spec: smtpFields({
|
||||
host: 'smtp.protonmail.ch',
|
||||
port: 587,
|
||||
security: 'starttls',
|
||||
security: 'tls',
|
||||
hostDisabled: true,
|
||||
}),
|
||||
},
|
||||
other: {
|
||||
@@ -121,7 +136,7 @@ export const smtpProviderVariants = Variants.of({
|
||||
export const systemSmtpSpec = InputSpec.of({
|
||||
provider: Value.union({
|
||||
name: 'Provider',
|
||||
default: null as any,
|
||||
default: 'gmail',
|
||||
variants: smtpProviderVariants,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -14,28 +14,34 @@ export const knownProtocols = {
|
||||
defaultPort: 80,
|
||||
withSsl: 'https',
|
||||
alpn: { specified: ['http/1.1'] } as AlpnInfo,
|
||||
addXForwardedHeaders: true,
|
||||
},
|
||||
https: {
|
||||
secure: { ssl: true },
|
||||
defaultPort: 443,
|
||||
addXForwardedHeaders: true,
|
||||
},
|
||||
ws: {
|
||||
secure: null,
|
||||
defaultPort: 80,
|
||||
withSsl: 'wss',
|
||||
alpn: { specified: ['http/1.1'] } as AlpnInfo,
|
||||
addXForwardedHeaders: true,
|
||||
},
|
||||
wss: {
|
||||
secure: { ssl: true },
|
||||
defaultPort: 443,
|
||||
addXForwardedHeaders: true,
|
||||
},
|
||||
ssh: {
|
||||
secure: { ssl: false },
|
||||
defaultPort: 22,
|
||||
addXForwardedHeaders: false,
|
||||
},
|
||||
dns: {
|
||||
secure: { ssl: false },
|
||||
defaultPort: 53,
|
||||
addXForwardedHeaders: false,
|
||||
},
|
||||
} as const
|
||||
|
||||
@@ -136,7 +142,7 @@ export class MultiHost {
|
||||
const sslProto = this.getSslProto(options)
|
||||
const addSsl = sslProto
|
||||
? {
|
||||
addXForwardedHeaders: false,
|
||||
addXForwardedHeaders: knownProtocols[sslProto].addXForwardedHeaders,
|
||||
preferredExternalPort: knownProtocols[sslProto].defaultPort,
|
||||
scheme: sslProto,
|
||||
alpn: 'alpn' in protoInfo ? protoInfo.alpn : null,
|
||||
@@ -148,7 +154,7 @@ export class MultiHost {
|
||||
preferredExternalPort: 443,
|
||||
scheme: sslProto,
|
||||
alpn: null,
|
||||
...('addSsl' in options ? options.addSsl : null),
|
||||
...options.addSsl,
|
||||
}
|
||||
: null
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@ export type AddPublicDomainParams = {
|
||||
fqdn: string
|
||||
acme: AcmeProvider | null
|
||||
gateway: GatewayId
|
||||
internalPort: number
|
||||
}
|
||||
|
||||
4
sdk/base/lib/osBindings/AddPublicDomainRes.ts
Normal file
4
sdk/base/lib/osBindings/AddPublicDomainRes.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CheckPortRes } from './CheckPortRes'
|
||||
|
||||
export type AddPublicDomainRes = { dns: string | null; port: CheckPortRes }
|
||||
@@ -5,6 +5,7 @@ import type { DnsSettings } from './DnsSettings'
|
||||
import type { GatewayId } from './GatewayId'
|
||||
import type { Host } from './Host'
|
||||
import type { NetworkInterfaceInfo } from './NetworkInterfaceInfo'
|
||||
import type { PassthroughInfo } from './PassthroughInfo'
|
||||
import type { WifiInfo } from './WifiInfo'
|
||||
|
||||
export type NetworkInfo = {
|
||||
@@ -14,4 +15,5 @@ export type NetworkInfo = {
|
||||
acme: { [key: AcmeProvider]: AcmeSettings }
|
||||
dns: DnsSettings
|
||||
defaultOutbound: string | null
|
||||
passthroughs: Array<PassthroughInfo>
|
||||
}
|
||||
|
||||
9
sdk/base/lib/osBindings/PassthroughInfo.ts
Normal file
9
sdk/base/lib/osBindings/PassthroughInfo.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PassthroughInfo = {
|
||||
hostname: string
|
||||
listenPort: number
|
||||
backend: string
|
||||
publicGateways: string[]
|
||||
privateIps: string[]
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export { AddPackageSignerParams } from './AddPackageSignerParams'
|
||||
export { AddPackageToCategoryParams } from './AddPackageToCategoryParams'
|
||||
export { AddPrivateDomainParams } from './AddPrivateDomainParams'
|
||||
export { AddPublicDomainParams } from './AddPublicDomainParams'
|
||||
export { AddPublicDomainRes } from './AddPublicDomainRes'
|
||||
export { AddressInfo } from './AddressInfo'
|
||||
export { AddSslOptions } from './AddSslOptions'
|
||||
export { AddTunnelParams } from './AddTunnelParams'
|
||||
@@ -201,6 +202,7 @@ export { PackagePlugin } from './PackagePlugin'
|
||||
export { PackageState } from './PackageState'
|
||||
export { PackageVersionInfo } from './PackageVersionInfo'
|
||||
export { PartitionInfo } from './PartitionInfo'
|
||||
export { PassthroughInfo } from './PassthroughInfo'
|
||||
export { PasswordType } from './PasswordType'
|
||||
export { PathOrUrl } from './PathOrUrl'
|
||||
export { Pem } from './Pem'
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
export * as inputSpecTypes from './actions/input/inputSpecTypes'
|
||||
export {
|
||||
CurrentDependenciesResult,
|
||||
OptionalDependenciesOf as OptionalDependencies,
|
||||
RequiredDependenciesOf as RequiredDependencies,
|
||||
} from './dependencies/setupDependencies'
|
||||
export * from './osBindings'
|
||||
export { SDKManifest } from './types/ManifestTypes'
|
||||
export { Effects }
|
||||
import { InputSpec as InputSpecClass } from './actions/input/builder/inputSpec'
|
||||
|
||||
import {
|
||||
DependencyRequirement,
|
||||
NamedHealthCheckResult,
|
||||
Manifest,
|
||||
ServiceInterface,
|
||||
ActionId,
|
||||
} from './osBindings'
|
||||
import { Affine, StringObject, ToKebab } from './util'
|
||||
import { Action, Actions } from './actions/setupActions'
|
||||
import { Effects } from './Effects'
|
||||
import { ExtendedVersion, VersionRange } from './exver'
|
||||
export { Effects }
|
||||
export * from './osBindings'
|
||||
export { SDKManifest } from './types/ManifestTypes'
|
||||
export {
|
||||
RequiredDependenciesOf as RequiredDependencies,
|
||||
OptionalDependenciesOf as OptionalDependencies,
|
||||
CurrentDependenciesResult,
|
||||
} from './dependencies/setupDependencies'
|
||||
import {
|
||||
ActionId,
|
||||
DependencyRequirement,
|
||||
Manifest,
|
||||
NamedHealthCheckResult,
|
||||
ServiceInterface,
|
||||
} from './osBindings'
|
||||
import { StringObject, ToKebab } from './util'
|
||||
|
||||
/** An object that can be built into a terminable daemon process. */
|
||||
export type DaemonBuildable = {
|
||||
|
||||
@@ -26,6 +26,18 @@ export const getHostname = (url: string): Hostname | null => {
|
||||
return last
|
||||
}
|
||||
|
||||
/**
|
||||
* The kinds of hostnames that can be filtered on.
|
||||
*
|
||||
* - `'mdns'` — mDNS / Bonjour `.local` hostnames
|
||||
* - `'domain'` — any os-managed domain name (matches both `'private-domain'` and `'public-domain'` metadata kinds)
|
||||
* - `'ip'` — shorthand for both `'ipv4'` and `'ipv6'`
|
||||
* - `'ipv4'` — IPv4 addresses only
|
||||
* - `'ipv6'` — IPv6 addresses only
|
||||
* - `'localhost'` — loopback addresses (`localhost`, `127.0.0.1`, `::1`)
|
||||
* - `'link-local'` — IPv6 link-local addresses (fe80::/10)
|
||||
* - `'plugin'` — hostnames provided by a plugin package
|
||||
*/
|
||||
type FilterKinds =
|
||||
| 'mdns'
|
||||
| 'domain'
|
||||
@@ -34,10 +46,25 @@ type FilterKinds =
|
||||
| 'ipv6'
|
||||
| 'localhost'
|
||||
| 'link-local'
|
||||
| 'plugin'
|
||||
|
||||
/**
|
||||
* Describes which hostnames to include (or exclude) when filtering a `Filled` address.
|
||||
*
|
||||
* Every field is optional — omitted fields impose no constraint.
|
||||
* Filters are composable: the `.filter()` method intersects successive filters,
|
||||
* and the `exclude` field inverts a nested filter.
|
||||
*/
|
||||
export type Filter = {
|
||||
/** Keep only hostnames with the given visibility. `'public'` = externally reachable, `'private'` = LAN-only. */
|
||||
visibility?: 'public' | 'private'
|
||||
/** Keep only hostnames whose metadata kind matches. A single kind or array of kinds. `'ip'` expands to `['ipv4','ipv6']`, `'domain'` matches both `'private-domain'` and `'public-domain'`. */
|
||||
kind?: FilterKinds | FilterKinds[]
|
||||
/** Arbitrary predicate — hostnames for which this returns `false` are excluded. */
|
||||
predicate?: (h: HostnameInfo) => boolean
|
||||
/** Keep only plugin hostnames provided by this package. Implies `kind: 'plugin'`. */
|
||||
pluginId?: PackageId
|
||||
/** A nested filter whose matches are *removed* from the result (logical NOT). */
|
||||
exclude?: Filter
|
||||
}
|
||||
|
||||
@@ -65,9 +92,13 @@ type KindFilter<K extends FilterKinds> = K extends 'mdns'
|
||||
?
|
||||
| (HostnameInfo & { metadata: { kind: 'ipv6' } })
|
||||
| KindFilter<Exclude<K, 'ipv6'>>
|
||||
: K extends 'ip'
|
||||
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
|
||||
: never
|
||||
: K extends 'plugin'
|
||||
?
|
||||
| (HostnameInfo & { metadata: { kind: 'plugin' } })
|
||||
| KindFilter<Exclude<K, 'plugin'>>
|
||||
: K extends 'ip'
|
||||
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
|
||||
: never
|
||||
|
||||
type FilterReturnTy<F extends Filter> = F extends {
|
||||
visibility: infer V extends 'public' | 'private'
|
||||
@@ -107,20 +138,62 @@ type FormatReturnTy<
|
||||
? UrlString | FormatReturnTy<F, Exclude<Format, 'urlstring'>>
|
||||
: never
|
||||
|
||||
/**
|
||||
* A resolved address with its hostnames already populated, plus helpers
|
||||
* for filtering, formatting, and converting hostnames to URLs.
|
||||
*
|
||||
* Filters are chainable and each call returns a new `Filled` narrowed to the
|
||||
* matching subset of hostnames:
|
||||
*
|
||||
* ```ts
|
||||
* addresses.nonLocal // exclude localhost & link-local
|
||||
* addresses.public // only publicly-reachable hostnames
|
||||
* addresses.filter({ kind: 'domain' }) // only domain-name hostnames
|
||||
* addresses.filter({ visibility: 'private' }) // only LAN-reachable hostnames
|
||||
* addresses.nonLocal.filter({ kind: 'ip' }) // chainable — non-local IPs only
|
||||
* ```
|
||||
*/
|
||||
export type Filled<F extends Filter = {}> = {
|
||||
/** The hostnames that survived all applied filters. */
|
||||
hostnames: HostnameInfo[]
|
||||
|
||||
/** Convert a single hostname into a fully-formed URL string, applying the address's scheme, username, and suffix. */
|
||||
toUrl: (h: HostnameInfo) => UrlString
|
||||
|
||||
/**
|
||||
* Return every hostname in the requested format.
|
||||
*
|
||||
* - `'urlstring'` (default) — formatted URL strings
|
||||
* - `'url'` — `URL` objects
|
||||
* - `'hostname-info'` — raw `HostnameInfo` objects
|
||||
*/
|
||||
format: <Format extends Formats = 'urlstring'>(
|
||||
format?: Format,
|
||||
) => FormatReturnTy<{}, Format>[]
|
||||
|
||||
/**
|
||||
* Apply an arbitrary {@link Filter} and return a new `Filled` containing only
|
||||
* the hostnames that match. Filters compose: calling `.filter()` on an
|
||||
* already-filtered `Filled` intersects the constraints.
|
||||
*/
|
||||
filter: <NewFilter extends Filter>(
|
||||
filter: NewFilter,
|
||||
) => Filled<NewFilter & Filter>
|
||||
|
||||
/**
|
||||
* Apply multiple filters and return hostnames that match **any** of them (union / OR).
|
||||
*
|
||||
* ```ts
|
||||
* addresses.matchesAny([{ kind: 'domain' }, { kind: 'mdns' }])
|
||||
* ```
|
||||
*/
|
||||
matchesAny: <NewFilters extends Filter[]>(
|
||||
filters: [...NewFilters],
|
||||
) => Filled<NewFilters[number] & F>
|
||||
|
||||
/** Shorthand filter that excludes `localhost` and IPv6 link-local addresses — keeps only network-reachable hostnames. */
|
||||
nonLocal: Filled<typeof nonLocalFilter & Filter>
|
||||
/** Shorthand filter that keeps only publicly-reachable hostnames (those with `public: true`). */
|
||||
public: Filled<typeof publicFilter & Filter>
|
||||
}
|
||||
export type FilledAddressInfo = AddressInfo & Filled
|
||||
@@ -210,7 +283,16 @@ function filterRec(
|
||||
['localhost', '127.0.0.1', '::1'].includes(h.hostname)) ||
|
||||
(kind.has('link-local') &&
|
||||
h.metadata.kind === 'ipv6' &&
|
||||
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname)))),
|
||||
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname))) ||
|
||||
(kind.has('plugin') && h.metadata.kind === 'plugin')),
|
||||
)
|
||||
}
|
||||
if (filter.pluginId) {
|
||||
const id = filter.pluginId
|
||||
hostnames = hostnames.filter(
|
||||
(h) =>
|
||||
invert !==
|
||||
(h.metadata.kind === 'plugin' && h.metadata.packageId === id),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -242,6 +324,14 @@ function enabledAddresses(addr: DerivedAddressInfo): HostnameInfo[] {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out localhost and IPv6 link-local hostnames from a list.
|
||||
* Equivalent to the `nonLocal` filter on `Filled` addresses.
|
||||
*/
|
||||
export function filterNonLocal(hostnames: HostnameInfo[]): HostnameInfo[] {
|
||||
return filterRec(hostnames, nonLocalFilter, false)
|
||||
}
|
||||
|
||||
export const filledAddress = (
|
||||
host: Host,
|
||||
addressInfo: AddressInfo,
|
||||
@@ -280,6 +370,19 @@ export const filledAddress = (
|
||||
filterRec(hostnames, filter, false),
|
||||
)
|
||||
},
|
||||
matchesAny: <NewFilters extends Filter[]>(filters: [...NewFilters]) => {
|
||||
const seen = new Set<HostnameInfo>()
|
||||
const union: HostnameInfo[] = []
|
||||
for (const f of filters) {
|
||||
for (const h of filterRec(hostnames, f, false)) {
|
||||
if (!seen.has(h)) {
|
||||
seen.add(h)
|
||||
union.push(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
return filledAddressFromHostnames<NewFilters[number] & F>(union)
|
||||
},
|
||||
get nonLocal(): Filled<typeof nonLocalFilter & F> {
|
||||
return getNonLocal()
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ export {
|
||||
GetServiceInterface,
|
||||
getServiceInterface,
|
||||
filledAddress,
|
||||
filterNonLocal,
|
||||
} from './getServiceInterface'
|
||||
export { getServiceInterfaces } from './getServiceInterfaces'
|
||||
export { once } from './once'
|
||||
|
||||
@@ -141,6 +141,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
| 'getSystemSmtp'
|
||||
| 'getOutboundGateway'
|
||||
| 'getContainerIp'
|
||||
| 'getStatus'
|
||||
| 'getDataVersion'
|
||||
| 'setDataVersion'
|
||||
| 'getServiceManifest'
|
||||
@@ -164,7 +165,6 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
getSslKey: (effects, ...args) => effects.getSslKey(...args),
|
||||
shutdown: (effects, ...args) => effects.shutdown(...args),
|
||||
getDependencies: (effects, ...args) => effects.getDependencies(...args),
|
||||
getStatus: (effects, ...args) => effects.getStatus(...args),
|
||||
setHealth: (effects, ...args) => effects.setHealth(...args),
|
||||
}
|
||||
|
||||
@@ -342,6 +342,104 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the service's current status with reactive subscription support.
|
||||
*
|
||||
* Returns an object with multiple read strategies: `const()` for a value
|
||||
* that retries on change, `once()` for a single read, `watch()` for an async
|
||||
* generator, `onChange()` for a callback, and `waitFor()` to block until a predicate is met.
|
||||
*
|
||||
* @param effects - The effects context
|
||||
* @param options - Optional filtering options (e.g. `packageId`)
|
||||
*/
|
||||
getStatus: (
|
||||
effects: T.Effects,
|
||||
options: Omit<Parameters<T.Effects['getStatus']>[0], 'callback'> = {},
|
||||
) => {
|
||||
async function* watch(abort?: AbortSignal) {
|
||||
const resolveCell = { resolve: () => {} }
|
||||
effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
abort?.addEventListener('abort', () => resolveCell.resolve())
|
||||
while (effects.isInContext && !abort?.aborted) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve
|
||||
resolveCell.resolve = resolve
|
||||
})
|
||||
yield await effects.getStatus({ ...options, callback })
|
||||
await waitForNext
|
||||
}
|
||||
}
|
||||
return {
|
||||
const: () =>
|
||||
effects.getStatus({
|
||||
...options,
|
||||
callback:
|
||||
effects.constRetry &&
|
||||
(() => effects.constRetry && effects.constRetry()),
|
||||
}),
|
||||
once: () => effects.getStatus(options),
|
||||
watch: (abort?: AbortSignal) => {
|
||||
const ctrl = new AbortController()
|
||||
abort?.addEventListener('abort', () => ctrl.abort())
|
||||
return DropGenerator.of(watch(ctrl.signal), () => ctrl.abort())
|
||||
},
|
||||
onChange: (
|
||||
callback: (
|
||||
value: T.StatusInfo | null,
|
||||
error?: Error,
|
||||
) => { cancel: boolean } | Promise<{ cancel: boolean }>,
|
||||
) => {
|
||||
;(async () => {
|
||||
const ctrl = new AbortController()
|
||||
for await (const value of watch(ctrl.signal)) {
|
||||
try {
|
||||
const res = await callback(value)
|
||||
if (res.cancel) {
|
||||
ctrl.abort()
|
||||
break
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'callback function threw an error @ getStatus.onChange',
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
})()
|
||||
.catch((e) => callback(null, e))
|
||||
.catch((e) =>
|
||||
console.error(
|
||||
'callback function threw an error @ getStatus.onChange',
|
||||
e,
|
||||
),
|
||||
)
|
||||
},
|
||||
waitFor: async (pred: (value: T.StatusInfo | null) => boolean) => {
|
||||
const resolveCell = { resolve: () => {} }
|
||||
effects.onLeaveContext(() => {
|
||||
resolveCell.resolve()
|
||||
})
|
||||
while (effects.isInContext) {
|
||||
let callback: () => void = () => {}
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve
|
||||
resolveCell.resolve = resolve
|
||||
})
|
||||
const res = await effects.getStatus({ ...options, callback })
|
||||
if (pred(res)) {
|
||||
resolveCell.resolve()
|
||||
return res
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return null
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
MultiHost: {
|
||||
/**
|
||||
* Create a new MultiHost instance for binding ports and exporting interfaces.
|
||||
|
||||
4
sdk/package/package-lock.json
generated
4
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.55",
|
||||
"version": "0.4.0-beta.58",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.55",
|
||||
"version": "0.4.0-beta.58",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.55",
|
||||
"version": "0.4.0-beta.58",
|
||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||
"main": "./package/lib/index.js",
|
||||
"types": "./package/lib/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user