make service interfaces and hosts one to one

This commit is contained in:
Matt Hill
2024-02-19 12:40:52 -07:00
parent eae75c13bb
commit d7bc7a2d38
15 changed files with 222 additions and 311 deletions

View File

@@ -106,11 +106,11 @@ export class HostSystemStartOs implements Effects {
T.Effects["clearBindings"] T.Effects["clearBindings"]
> >
} }
clearNetworkInterfaces( clearServiceInterfaces(
...[]: Parameters<T.Effects["clearNetworkInterfaces"]> ...[]: Parameters<T.Effects["clearServiceInterfaces"]>
) { ) {
return this.rpcRound("clearNetworkInterfaces", null) as ReturnType< return this.rpcRound("clearServiceInterfaces", null) as ReturnType<
T.Effects["clearNetworkInterfaces"] T.Effects["clearServiceInterfaces"]
> >
} }
createOverlayedImage(options: { imageId: string }): Promise<string> { createOverlayedImage(options: { imageId: string }): Promise<string> {
@@ -131,11 +131,11 @@ export class HostSystemStartOs implements Effects {
T.Effects["exportAction"] T.Effects["exportAction"]
> >
} }
exportNetworkInterface( exportServiceInterface(
...[options]: Parameters<T.Effects["exportNetworkInterface"]> ...[options]: Parameters<T.Effects["exportServiceInterface"]>
) { ) {
return this.rpcRound("exportNetworkInterface", options) as ReturnType< return this.rpcRound("exportServiceInterface", options) as ReturnType<
T.Effects["exportNetworkInterface"] T.Effects["exportServiceInterface"]
> >
} }
exposeForDependents(...[options]: any) { exposeForDependents(...[options]: any) {

View File

@@ -128,8 +128,8 @@ pub fn service_effect_handler() -> ParentHandler {
// .subcommand("removeAddress",from_fn(remove_address)) // .subcommand("removeAddress",from_fn(remove_address))
// .subcommand("exportAction",from_fn(export_action)) // .subcommand("exportAction",from_fn(export_action))
// .subcommand("bind",from_fn(bind)) // .subcommand("bind",from_fn(bind))
// .subcommand("clearNetworkInterfaces",from_fn(clear_network_interfaces)) // .subcommand("clearServiceInterfaces",from_fn(clear_network_interfaces))
// .subcommand("exportNetworkInterface",from_fn(export_network_interface)) // .subcommand("exportServiceInterface",from_fn(export_network_interface))
// .subcommand("clearBindings",from_fn(clear_bindings)) // .subcommand("clearBindings",from_fn(clear_bindings))
// .subcommand("getHostnames",from_fn(get_hostnames)) // .subcommand("getHostnames",from_fn(get_hostnames))
// .subcommand("getInterface",from_fn(get_interface)) // .subcommand("getInterface",from_fn(get_interface))

View File

@@ -68,7 +68,7 @@ export function setupConfig<
return { error: "Set config type error for config" } return { error: "Set config type error for config" }
} }
await effects.clearBindings() await effects.clearBindings()
await effects.clearNetworkInterfaces() await effects.clearServiceInterfaces()
const { restart } = await write({ const { restart } = await write({
input: JSON.parse(JSON.stringify(input)), input: JSON.parse(JSON.stringify(input)),
effects, effects,

View File

@@ -55,7 +55,7 @@ type AddSslOptions = {
addXForwardedHeaders?: boolean /** default: false */ addXForwardedHeaders?: boolean /** default: false */
} }
type Security = { secure: false; ssl: false } | { secure: true; ssl: boolean } type Security = { secure: false; ssl: false } | { secure: true; ssl: boolean }
export type PortOptions = { export type BindOptions = {
scheme: Scheme scheme: Scheme
preferredExternalPort: number preferredExternalPort: number
addSsl: AddSslOptions | null addSsl: AddSslOptions | null
@@ -85,7 +85,7 @@ type PortOptionsByKnownProtocol =
scheme?: Scheme scheme?: Scheme
addSsl?: AddSslOptions | null addSsl?: AddSslOptions | null
} }
type PortOptionsByProtocol = PortOptionsByKnownProtocol | PortOptions type PortOptionsByProtocol = PortOptionsByKnownProtocol | BindOptions
export type HostKind = "static" | "single" | "multi" export type HostKind = "static" | "single" | "multi"

View File

@@ -1,13 +1,13 @@
import { Address } from "../types" import { AddressInfo } from "../types"
import { Host, PortOptions } from "./Host" import { Host, BindOptions } from "./Host"
export class Origin<T extends Host> { export class Origin<T extends Host> {
constructor( constructor(
readonly host: T, readonly host: T,
readonly options: PortOptions, readonly options: BindOptions,
) {} ) {}
build({ username, path, search }: BuildOptions): Address { build({ username, path, search }: BuildOptions): AddressInfo {
const qpEntries = Object.entries(search) const qpEntries = Object.entries(search)
.map( .map(
([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`, ([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,

View File

@@ -1,5 +1,5 @@
import { Address, Effects } from "../types" import { AddressInfo, Effects } from "../types"
import { NetworkInterfaceType } from "../util/utils" import { ServiceInterfaceType } from "../util/utils"
import { AddressReceipt } from "./AddressReceipt" import { AddressReceipt } from "./AddressReceipt"
import { Host } from "./Host" import { Host } from "./Host"
import { Origin } from "./Origin" import { Origin } from "./Origin"
@@ -15,7 +15,7 @@ import { Origin } from "./Origin"
* @param options * @param options
* @returns * @returns
*/ */
export class NetworkInterfaceBuilder { export class ServiceInterfaceBuilder {
constructor( constructor(
readonly options: { readonly options: {
effects: Effects effects: Effects
@@ -24,7 +24,7 @@ export class NetworkInterfaceBuilder {
description: string description: string
hasPrimary: boolean hasPrimary: boolean
disabled: boolean disabled: boolean
type: NetworkInterfaceType type: ServiceInterfaceType
username: null | string username: null | string
path: string path: string
search: Record<string, string> search: Record<string, string>
@@ -36,12 +36,12 @@ export class NetworkInterfaceBuilder {
* *
* The returned addressReceipt serves as proof that the addresses were registered * The returned addressReceipt serves as proof that the addresses were registered
* *
* @param addresses * @param addressInfo
* @returns * @returns
*/ */
async export<Origins extends Origin<Host>[]>( async export<OriginForHost extends Origin<Host>>(
origins: Origins, origin: OriginForHost,
): Promise<Address[] & AddressReceipt> { ): Promise<AddressInfo & AddressReceipt> {
const { const {
name, name,
description, description,
@@ -54,20 +54,18 @@ export class NetworkInterfaceBuilder {
search, search,
} = this.options } = this.options
const addresses = Array.from(origins).map((o) => const addressInfo = origin.build({ username, path, search, scheme: null })
o.build({ username, path, search, scheme: null }),
)
await this.options.effects.exportNetworkInterface({ await this.options.effects.exportServiceInterface({
interfaceId: id, id,
name, name,
description, description,
hasPrimary, hasPrimary,
disabled, disabled,
addresses, addressInfo,
type, type,
}) })
return addresses as Address[] & AddressReceipt return addressInfo as AddressInfo & AddressReceipt
} }
} }

View File

@@ -1,10 +1,10 @@
import { Config } from "../config/builder/config" import { Config } from "../config/builder/config"
import { SDKManifest } from "../manifest/ManifestTypes" import { SDKManifest } from "../manifest/ManifestTypes"
import { Address, Effects } from "../types" import { AddressInfo, Effects } from "../types"
import { Utils } from "../util/utils" import { Utils } from "../util/utils"
import { AddressReceipt } from "./AddressReceipt" import { AddressReceipt } from "./AddressReceipt"
export type InterfacesReceipt = Array<Address[] & AddressReceipt> export type InterfacesReceipt = Array<AddressInfo[] & AddressReceipt>
export type SetInterfaces< export type SetInterfaces<
Manifest extends SDKManifest, Manifest extends SDKManifest,
Store, Store,

View File

@@ -2,7 +2,7 @@ import { Effects, ExpectedExports } from "../types"
import { createMainUtils } from "../util" import { createMainUtils } from "../util"
import { Utils, createUtils } from "../util/utils" import { Utils, createUtils } from "../util/utils"
import { Daemons } from "./Daemons" import { Daemons } from "./Daemons"
import "../interfaces/NetworkInterfaceBuilder" import "../interfaces/ServiceInterfaceBuilder"
import "../interfaces/Origin" import "../interfaces/Origin"
import "./Daemons" import "./Daemons"

View File

@@ -1,4 +1,4 @@
import { NetworkInterfaceBuilder } from "../interfaces/NetworkInterfaceBuilder" import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder"
import { Effects } from "../types" import { Effects } from "../types"
import { createUtils } from "../util" import { createUtils } from "../util"
@@ -8,7 +8,7 @@ describe("host", () => {
const utils = createUtils<never, never>(effects) const utils = createUtils<never, never>(effects)
const foo = utils.host.multi("foo") const foo = utils.host.multi("foo")
const fooOrigin = await foo.bindPort(80, { protocol: "http" as const }) const fooOrigin = await foo.bindPort(80, { protocol: "http" as const })
const fooInterface = new NetworkInterfaceBuilder({ const fooInterface = new ServiceInterfaceBuilder({
effects, effects,
name: "Foo", name: "Foo",
id: "foo", id: "foo",

View File

@@ -1,4 +1,4 @@
import { getHostname } from "../util/getNetworkInterface" import { getHostname } from "../util/getServiceInterface"
describe("getHostname ", () => { describe("getHostname ", () => {
const inputToExpected = [ const inputToExpected = [

View File

@@ -1,4 +1,4 @@
import { getHostname } from "../util/getNetworkInterface" import { getHostname } from "../util/getServiceInterface"
import { splitCommand } from "../util/splitCommand" import { splitCommand } from "../util/splitCommand"
describe("splitCommand ", () => { describe("splitCommand ", () => {

View File

@@ -1,10 +1,10 @@
export * as configTypes from "./config/configTypes" export * as configTypes from "./config/configTypes"
import { InputSpec } from "./config/configTypes" import { InputSpec } from "./config/configTypes"
import { DependenciesReceipt } from "./config/setupConfig" import { DependenciesReceipt } from "./config/setupConfig"
import { HostKind, PortOptions } from "./interfaces/Host" import { HostKind, BindOptions } from "./interfaces/Host"
import { Daemons } from "./mainFn/Daemons" import { Daemons } from "./mainFn/Daemons"
import { UrlString } from "./util/getNetworkInterface" import { UrlString } from "./util/getServiceInterface"
import { NetworkInterfaceType, Signals } from "./util/utils" import { ServiceInterfaceType, Signals } from "./util/utils"
export type ExportedAction = (options: { export type ExportedAction = (options: {
effects: Effects effects: Effects
@@ -165,80 +165,57 @@ export type ActionMetadata = {
} }
export declare const hostName: unique symbol export declare const hostName: unique symbol
export type Hostname = string & { [hostName]: never } export type Hostname = string & { [hostName]: never }
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */ /** ${scheme}://${username}@${host}:${externalPort}${suffix} */
export type Address = { export type AddressInfo = {
username: string | null username: string | null
hostId: string hostId: string
options: PortOptions options: BindOptions
suffix: string suffix: string
} }
export type ListenKind = "onion" | "ip" export type HostnameInfoIp = {
export type ListenInfoBase = {
kind: ListenKind
}
export type ListenInfoOnion = ListenInfoBase & {
kind: "onion"
}
export type ListenInfoIp = ListenInfoBase & {
kind: "ip" kind: "ip"
interfaceId: string networkInterfaceId: string
hostname:
| {
kind: "ipv4" | "ipv6" | "local"
value: string
port: number | null
sslPort: number | null
}
| {
kind: "domain"
domain: string
subdomain: string | null
port: number | null
sslPort: number | null
}
} }
export type ListenInfo = ListenInfoOnion | ListenInfoIp export type HostnameInfoOnion = {
kind: "onion"
export type HostBase = { hostname: { value: string; port: number | null; sslPort: number | null }
id: string
kind: HostKind
} }
export type SingleHost = HostBase & { export type HostnameInfo = HostnameInfoIp | HostnameInfoOnion
export type SingleHost = {
kind: "single" | "static" kind: "single" | "static"
} & ( hostname: HostnameInfo | null
| {
listen: null
hostname: null
}
| {
listen: ListenInfoOnion
hostname: string
}
| {
listen: ListenInfoIp
hostname:
| string
| { domain: string; subdomain: string | null; port: number }
}
)
export type MultiHost = HostBase & {
kind: "multi"
} & {
hostnames:
| {
listen: null
hostname: null
}
| {
listen: ListenInfoOnion
hostname: string
}
| {
listen: ListenInfoIp
hostname: (
| string
| { domain: string; subdomain: string | null; port: number }
)[]
}
} }
export type InterfaceId = string export type MultiHost = {
kind: "multi"
hostnames: HostnameInfo[]
}
export type NetworkInterface = { export type HostInfo = SingleHost | MultiHost
interfaceId: InterfaceId
export type ServiceInterfaceId = string
export type ServiceInterface = {
id: ServiceInterfaceId
/** The title of this field to be displayed */ /** The title of this field to be displayed */
name: string name: string
/** Human readable description, used as tooltip usually */ /** Human readable description, used as tooltip usually */
@@ -247,11 +224,10 @@ export type NetworkInterface = {
hasPrimary: boolean hasPrimary: boolean
/** Disabled interfaces do not serve, but they retain their metadata and addresses */ /** Disabled interfaces do not serve, but they retain their metadata and addresses */
disabled: boolean disabled: boolean
/** All URIs */ /** URI Information */
addresses: Address[] addressInfo: AddressInfo
/** The network interface could be several types, something like ui, p2p, or network */ /** The network interface could be several types, something like ui, p2p, or network */
type: NetworkInterfaceType type: ServiceInterfaceType
} }
// prettier-ignore // prettier-ignore
export type ExposeAllServicePaths<Store, PreviousPath extends string = ""> = export type ExposeAllServicePaths<Store, PreviousPath extends string = ""> =
@@ -299,7 +275,7 @@ export type Effects = {
kind: "static" | "single" | "multi" kind: "static" | "single" | "multi"
id: string id: string
internalPort: number internalPort: number
} & PortOptions, } & BindOptions,
): Promise<void> ): Promise<void>
/** Retrieves the current hostname(s) associated with a host id */ /** Retrieves the current hostname(s) associated with a host id */
getHostnames(options: { getHostnames(options: {
@@ -307,13 +283,13 @@ export type Effects = {
hostId: string hostId: string
packageId?: string packageId?: string
callback: () => void callback: () => void
}): Promise<[Hostname]> }): Promise<[] | [Hostname]>
getHostnames(options: { getHostnames(options: {
kind?: "multi" kind?: "multi"
packageId?: string packageId?: string
hostId: string hostId: string
callback: () => void callback: () => void
}): Promise<[Hostname, ...Hostname[]]> }): Promise<Hostname[]>
// /** // /**
// * Run rsync between two volumes. This is used to backup data between volumes. // * Run rsync between two volumes. This is used to backup data between volumes.
@@ -357,7 +333,7 @@ export type Effects = {
getIPHostname(): Promise<string[]> getIPHostname(): Promise<string[]>
/** Get the address for another service for tor interfaces */ /** Get the address for another service for tor interfaces */
getServiceTorHostname( getServiceTorHostname(
interfaceId: InterfaceId, serviceInterfaceId: ServiceInterfaceId,
packageId?: string, packageId?: string,
): Promise<string> ): Promise<string>
/** Get the IP address of the container */ /** Get the IP address of the container */
@@ -371,11 +347,11 @@ export type Effects = {
): Promise<number> ): Promise<number>
/** Removes all network interfaces */ /** Removes all network interfaces */
clearNetworkInterfaces(): Promise<void> clearServiceInterfaces(): Promise<void>
/** When we want to create a link in the front end interfaces, and example is /** When we want to create a link in the front end interfaces, and example is
* exposing a url to view a web service * exposing a url to view a web service
*/ */
exportNetworkInterface(options: NetworkInterface): Promise<string> exportServiceInterface(options: ServiceInterface): Promise<string>
exposeForDependents<Store = never>( exposeForDependents<Store = never>(
options: ExposeServicePaths<Store>, options: ExposeServicePaths<Store>,
@@ -388,11 +364,11 @@ export type Effects = {
* *
* Note: any auth should be filtered out already * Note: any auth should be filtered out already
*/ */
getInterface(options: { getServiceInterface(options: {
packageId?: PackageId packageId?: PackageId
interfaceId: InterfaceId serviceInterfaceId: ServiceInterfaceId
callback: () => void callback: () => void
}): Promise<NetworkInterface> }): Promise<ServiceInterface>
/** /**
* The user sets the primary url for a interface * The user sets the primary url for a interface
@@ -400,7 +376,7 @@ export type Effects = {
*/ */
getPrimaryUrl(options: { getPrimaryUrl(options: {
packageId?: PackageId packageId?: PackageId
interfaceId: InterfaceId serviceInterfaceId: ServiceInterfaceId
callback: () => void callback: () => void
}): Promise<UrlString | null> }): Promise<UrlString | null>
@@ -410,10 +386,10 @@ export type Effects = {
* *
* Note: any auth should be filtered out already * Note: any auth should be filtered out already
*/ */
listInterface(options: { listServiceInterfaces(options: {
packageId?: PackageId packageId?: PackageId
callback: () => void callback: () => void
}): Promise<NetworkInterface[]> }): Promise<ServiceInterface[]>
/** /**
*Remove an address that was exported. Used problably during main or during setConfig. *Remove an address that was exported. Used problably during main or during setConfig.

View File

@@ -1,28 +1,28 @@
import { Address, Effects, HostName, NetworkInterface } from "../types" import { AddressInfo, Effects, Hostname, ServiceInterface } from "../types"
import * as regexes from "./regexes" import * as regexes from "./regexes"
import { NetworkInterfaceType } from "./utils" import { ServiceInterfaceType } from "./utils"
export type UrlString = string export type UrlString = string
export type HostId = string export type HostId = string
const getHostnameRegex = /^(\w+:\/\/)?([^\/\:]+)(:\d{1,3})?(\/)?/ const getHostnameRegex = /^(\w+:\/\/)?([^\/\:]+)(:\d{1,3})?(\/)?/
export const getHostname = (url: string): HostName | null => { export const getHostname = (url: string): Hostname | null => {
const founds = url.match(getHostnameRegex)?.[2] const founds = url.match(getHostnameRegex)?.[2]
if (!founds) return null if (!founds) return null
const parts = founds.split("@") const parts = founds.split("@")
const last = parts[parts.length - 1] as HostName | null const last = parts[parts.length - 1] as Hostname | null
return last return last
} }
export type Filled = { export type Filled = {
hostnames: HostName[] hostnames: Hostname[]
onionHostnames: HostName[] onionHostnames: Hostname[]
localHostnames: HostName[] localHostnames: Hostname[]
ipHostnames: HostName[] ipHostnames: Hostname[]
ipv4Hostnames: HostName[] ipv4Hostnames: Hostname[]
ipv6Hostnames: HostName[] ipv6Hostnames: Hostname[]
nonIpHostnames: HostName[] nonIpHostnames: Hostname[]
allHostnames: HostName[] allHostnames: Hostname[]
urls: UrlString[] urls: UrlString[]
onionUrls: UrlString[] onionUrls: UrlString[]
@@ -33,9 +33,9 @@ export type Filled = {
nonIpUrls: UrlString[] nonIpUrls: UrlString[]
allUrls: UrlString[] allUrls: UrlString[]
} }
export type FilledAddress = Address & Filled export type FilledAddressInfo = AddressInfo & Filled
export type NetworkInterfaceFilled = { export type ServiceInterfaceFilled = {
interfaceId: string id: string
/** The title of this field to be displayed */ /** The title of this field to be displayed */
name: string name: string
/** Human readable description, used as tooltip usually */ /** Human readable description, used as tooltip usually */
@@ -44,15 +44,15 @@ export type NetworkInterfaceFilled = {
hasPrimary: boolean hasPrimary: boolean
/** Whether or not the interface disabled */ /** Whether or not the interface disabled */
disabled: boolean disabled: boolean
/** All URIs */ /** URI information */
addresses: FilledAddress[] addressInfo: FilledAddressInfo
/** Indicates if we are a ui/p2p/api for the kind of interface that this is representing */
/** Indicates if we are a ui/ p2p/ api/ other for the kind of interface that this is representing */ type: ServiceInterfaceType
type: NetworkInterfaceType /** The primary hostname for the service, as chosen by the user */
primaryHostname: Hostname | null
primaryHostname: HostName | null /** The primary URL for the service, as chosen by the user */
primaryUrl: UrlString | null primaryUrl: UrlString | null
} & Filled }
const either = const either =
<A>(...args: ((a: A) => boolean)[]) => <A>(...args: ((a: A) => boolean)[]) =>
(a: A) => (a: A) =>
@@ -63,8 +63,8 @@ const negate =
!fn(a) !fn(a)
const unique = <A>(values: A[]) => Array.from(new Set(values)) const unique = <A>(values: A[]) => Array.from(new Set(values))
const addressHostToUrl = ( const addressHostToUrl = (
{ options, username, suffix }: Address, { options, username, suffix }: AddressInfo,
host: HostName, host: Hostname,
): UrlString => { ): UrlString => {
const scheme = host.endsWith(".onion") const scheme = host.endsWith(".onion")
? options.scheme ? options.scheme
@@ -76,15 +76,12 @@ const addressHostToUrl = (
}${host}${suffix}` }${host}${suffix}`
} }
export const filledAddress = ( export const filledAddress = (
mapHostnames: { hostnames: Hostname[],
[hostId: string]: HostName[] addressInfo: AddressInfo,
}, ): FilledAddressInfo => {
address: Address, const toUrl = addressHostToUrl.bind(null, addressInfo)
): FilledAddress => {
const toUrl = addressHostToUrl.bind(null, address)
const hostnames = mapHostnames[address.hostId] ?? []
return { return {
...address, ...addressInfo,
hostnames, hostnames,
get onionHostnames() { get onionHostnames() {
return hostnames.filter(regexes.torHostname.test) return hostnames.filter(regexes.torHostname.test)
@@ -138,131 +135,60 @@ export const filledAddress = (
} }
} }
export const networkInterfaceFilled = (
interfaceValue: NetworkInterface,
primaryUrl: UrlString | null,
addresses: FilledAddress[],
): NetworkInterfaceFilled => {
return {
...interfaceValue,
addresses,
get hostnames() {
return unique(addresses.flatMap((x) => x.hostnames))
},
get onionHostnames() {
return unique(addresses.flatMap((x) => x.onionHostnames))
},
get localHostnames() {
return unique(addresses.flatMap((x) => x.localHostnames))
},
get ipHostnames() {
return unique(addresses.flatMap((x) => x.ipHostnames))
},
get ipv4Hostnames() {
return unique(addresses.flatMap((x) => x.ipv4Hostnames))
},
get ipv6Hostnames() {
return unique(addresses.flatMap((x) => x.ipv6Hostnames))
},
get nonIpHostnames() {
return unique(addresses.flatMap((x) => x.nonIpHostnames))
},
get allHostnames() {
return unique(addresses.flatMap((x) => x.allHostnames))
},
get primaryHostname() {
if (primaryUrl == null) return null
return getHostname(primaryUrl)
},
get urls() {
return unique(addresses.flatMap((x) => x.urls))
},
get onionUrls() {
return unique(addresses.flatMap((x) => x.onionUrls))
},
get localUrls() {
return unique(addresses.flatMap((x) => x.localUrls))
},
get ipUrls() {
return unique(addresses.flatMap((x) => x.ipUrls))
},
get ipv4Urls() {
return unique(addresses.flatMap((x) => x.ipv4Urls))
},
get ipv6Urls() {
return unique(addresses.flatMap((x) => x.ipv6Urls))
},
get nonIpUrls() {
return unique(addresses.flatMap((x) => x.nonIpUrls))
},
get allUrls() {
return unique(addresses.flatMap((x) => x.allUrls))
},
primaryUrl,
}
}
const makeInterfaceFilled = async ({ const makeInterfaceFilled = async ({
effects, effects,
interfaceId, id,
packageId, packageId,
callback, callback,
}: { }: {
effects: Effects effects: Effects
interfaceId: string id: string
packageId: string | undefined packageId: string | undefined
callback: () => void callback: () => void
}) => { }) => {
const interfaceValue = await effects.getInterface({ const serviceInterfaceValue = await effects.getServiceInterface({
interfaceId, serviceInterfaceId: id,
packageId, packageId,
callback, callback,
}) })
const hostIdsRecord = Promise.all( const hostIdRecord = await effects.getHostnames({
unique(interfaceValue.addresses.map((x) => x.hostId)).map( packageId,
async (hostId) => hostId: serviceInterfaceValue.addressInfo.hostId,
[ callback,
hostId, })
await effects.getHostnames({ const primaryUrl = await effects.getPrimaryUrl({
packageId, serviceInterfaceId: id,
hostId,
callback,
}),
] as const,
),
)
const primaryUrl = effects.getPrimaryUrl({
interfaceId,
packageId, packageId,
callback, callback,
}) })
const fillAddress = filledAddress.bind( const interfaceFilled: ServiceInterfaceFilled = {
null, ...serviceInterfaceValue,
Object.fromEntries(await hostIdsRecord), primaryUrl: primaryUrl,
) addressInfo: filledAddress(hostIdRecord, serviceInterfaceValue.addressInfo),
const interfaceFilled: NetworkInterfaceFilled = networkInterfaceFilled( get primaryHostname() {
interfaceValue, if (primaryUrl == null) return null
await primaryUrl, return getHostname(primaryUrl)
interfaceValue.addresses.map(fillAddress), },
) }
return interfaceFilled return interfaceFilled
} }
export class GetNetworkInterface { export class GetServiceInterface {
constructor( constructor(
readonly effects: Effects, readonly effects: Effects,
readonly opts: { interfaceId: string; packageId?: string }, readonly opts: { id: string; packageId?: string },
) {} ) {}
/** /**
* Returns the value of Store at the provided path. Restart the service if the value changes * Returns the value of Store at the provided path. Restart the service if the value changes
*/ */
async const() { async const() {
const { interfaceId, packageId } = this.opts const { id, packageId } = this.opts
const callback = this.effects.restart const callback = this.effects.restart
const interfaceFilled: NetworkInterfaceFilled = await makeInterfaceFilled({ const interfaceFilled: ServiceInterfaceFilled = await makeInterfaceFilled({
effects: this.effects, effects: this.effects,
interfaceId, id,
packageId, packageId,
callback, callback,
}) })
@@ -270,14 +196,14 @@ export class GetNetworkInterface {
return interfaceFilled return interfaceFilled
} }
/** /**
* Returns the value of NetworkInterfacesFilled at the provided path. Does nothing if the value changes * Returns the value of ServiceInterfacesFilled at the provided path. Does nothing if the value changes
*/ */
async once() { async once() {
const { interfaceId, packageId } = this.opts const { id, packageId } = this.opts
const callback = () => {} const callback = () => {}
const interfaceFilled: NetworkInterfaceFilled = await makeInterfaceFilled({ const interfaceFilled: ServiceInterfaceFilled = await makeInterfaceFilled({
effects: this.effects, effects: this.effects,
interfaceId, id,
packageId, packageId,
callback, callback,
}) })
@@ -286,10 +212,10 @@ export class GetNetworkInterface {
} }
/** /**
* Watches the value of NetworkInterfacesFilled at the provided path. Takes a custom callback function to run whenever the value changes * Watches the value of ServiceInterfacesFilled at the provided path. Takes a custom callback function to run whenever the value changes
*/ */
async *watch() { async *watch() {
const { interfaceId, packageId } = this.opts const { id, packageId } = this.opts
while (true) { while (true) {
let callback: () => void = () => {} let callback: () => void = () => {}
const waitForNext = new Promise<void>((resolve) => { const waitForNext = new Promise<void>((resolve) => {
@@ -297,7 +223,7 @@ export class GetNetworkInterface {
}) })
yield await makeInterfaceFilled({ yield await makeInterfaceFilled({
effects: this.effects, effects: this.effects,
interfaceId, id,
packageId, packageId,
callback, callback,
}) })
@@ -305,9 +231,9 @@ export class GetNetworkInterface {
} }
} }
} }
export function getNetworkInterface( export function getServiceInterface(
effects: Effects, effects: Effects,
opts: { interfaceId: string; packageId?: string }, opts: { id: string; packageId?: string },
) { ) {
return new GetNetworkInterface(effects, opts) return new GetServiceInterface(effects, opts)
} }

View File

@@ -1,10 +1,9 @@
import { Effects, HostName } from "../types" import { Effects } from "../types"
import { import {
HostId, ServiceInterfaceFilled,
NetworkInterfaceFilled,
filledAddress, filledAddress,
networkInterfaceFilled, getHostname,
} from "./getNetworkInterface" } from "./getServiceInterface"
const makeManyInterfaceFilled = async ({ const makeManyInterfaceFilled = async ({
effects, effects,
@@ -15,7 +14,7 @@ const makeManyInterfaceFilled = async ({
packageId: string | undefined packageId: string | undefined
callback: () => void callback: () => void
}) => { }) => {
const interfaceValues = await effects.listInterface({ const serviceInterfaceValues = await effects.listServiceInterfaces({
packageId, packageId,
callback, callback,
}) })
@@ -23,7 +22,9 @@ const makeManyInterfaceFilled = async ({
await Promise.all( await Promise.all(
Array.from( Array.from(
new Set( new Set(
interfaceValues.flatMap((x) => x.addresses).map((x) => x.hostId), serviceInterfaceValues
.flatMap((x) => x.addressInfo)
.map((x) => x.hostId),
), ),
).map( ).map(
async (hostId) => async (hostId) =>
@@ -38,25 +39,37 @@ const makeManyInterfaceFilled = async ({
), ),
), ),
) )
const fillAddress = filledAddress.bind(null, hostIdsRecord)
const interfacesFilled: NetworkInterfaceFilled[] = await Promise.all( const serviceInterfacesFilled: ServiceInterfaceFilled[] = await Promise.all(
interfaceValues.map(async (interfaceValue) => serviceInterfaceValues.map(async (serviceInterfaceValue) => {
networkInterfaceFilled( const hostIdRecord = await effects.getHostnames({
interfaceValue, packageId,
await effects.getPrimaryUrl({ hostId: serviceInterfaceValue.addressInfo.hostId,
interfaceId: interfaceValue.interfaceId, callback,
packageId, })
callback, const primaryUrl = await effects.getPrimaryUrl({
}), serviceInterfaceId: serviceInterfaceValue.id,
interfaceValue.addresses.map(fillAddress), packageId,
), callback,
), })
return {
...serviceInterfaceValue,
primaryUrl: primaryUrl,
addressInfo: filledAddress(
hostIdRecord,
serviceInterfaceValue.addressInfo,
),
get primaryHostname() {
if (primaryUrl == null) return null
return getHostname(primaryUrl)
},
}
}),
) )
return interfacesFilled return serviceInterfacesFilled
} }
export class GetNetworkInterfaces { export class GetServiceInterfaces {
constructor( constructor(
readonly effects: Effects, readonly effects: Effects,
readonly opts: { packageId?: string }, readonly opts: { packageId?: string },
@@ -68,7 +81,7 @@ export class GetNetworkInterfaces {
async const() { async const() {
const { packageId } = this.opts const { packageId } = this.opts
const callback = this.effects.restart const callback = this.effects.restart
const interfaceFilled: NetworkInterfaceFilled[] = const interfaceFilled: ServiceInterfaceFilled[] =
await makeManyInterfaceFilled({ await makeManyInterfaceFilled({
effects: this.effects, effects: this.effects,
packageId, packageId,
@@ -78,12 +91,12 @@ export class GetNetworkInterfaces {
return interfaceFilled return interfaceFilled
} }
/** /**
* Returns the value of NetworkInterfacesFilled at the provided path. Does nothing if the value changes * Returns the value of ServiceInterfacesFilled at the provided path. Does nothing if the value changes
*/ */
async once() { async once() {
const { packageId } = this.opts const { packageId } = this.opts
const callback = () => {} const callback = () => {}
const interfaceFilled: NetworkInterfaceFilled[] = const interfaceFilled: ServiceInterfaceFilled[] =
await makeManyInterfaceFilled({ await makeManyInterfaceFilled({
effects: this.effects, effects: this.effects,
packageId, packageId,
@@ -94,7 +107,7 @@ export class GetNetworkInterfaces {
} }
/** /**
* Watches the value of NetworkInterfacesFilled at the provided path. Takes a custom callback function to run whenever the value changes * Watches the value of ServiceInterfacesFilled at the provided path. Takes a custom callback function to run whenever the value changes
*/ */
async *watch() { async *watch() {
const { packageId } = this.opts const { packageId } = this.opts
@@ -112,9 +125,9 @@ export class GetNetworkInterfaces {
} }
} }
} }
export function getNetworkInterfaces( export function getServiceInterfaces(
effects: Effects, effects: Effects,
opts: { packageId?: string }, opts: { packageId?: string },
) { ) {
return new GetNetworkInterfaces(effects, opts) return new GetServiceInterfaces(effects, opts)
} }

View File

@@ -9,13 +9,11 @@ import {
Effects, Effects,
EnsureStorePath, EnsureStorePath,
ExtractStore, ExtractStore,
InterfaceId, ServiceInterfaceId,
PackageId, PackageId,
ValidIfNoStupidEscape, ValidIfNoStupidEscape,
} from "../types" } from "../types"
import { GetSystemSmtp } from "./GetSystemSmtp" import { GetSystemSmtp } from "./GetSystemSmtp"
import { DefaultString } from "../config/configTypes"
import { getDefaultString } from "./getDefaultString"
import { GetStore, getStore } from "../store/getStore" import { GetStore, getStore } from "../store/getStore"
import { import {
MountDependenciesOut, MountDependenciesOut,
@@ -27,13 +25,13 @@ import {
NamedPath, NamedPath,
Path, Path,
} from "../dependency/setupDependencyMounts" } from "../dependency/setupDependencyMounts"
import { Host, MultiHost, SingleHost, StaticHost } from "../interfaces/Host" import { MultiHost, SingleHost, StaticHost } from "../interfaces/Host"
import { NetworkInterfaceBuilder } from "../interfaces/NetworkInterfaceBuilder" import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder"
import { GetNetworkInterface, getNetworkInterface } from "./getNetworkInterface" import { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
import { import {
GetNetworkInterfaces, GetServiceInterfaces,
getNetworkInterfaces, getServiceInterfaces,
} from "./getNetworkInterfaces" } from "./getServiceInterfaces"
import * as CP from "node:child_process" import * as CP from "node:child_process"
import { promisify } from "node:util" import { promisify } from "node:util"
import { splitCommand } from "./splitCommand" import { splitCommand } from "./splitCommand"
@@ -50,7 +48,7 @@ const childProcess = {
execFile: promisify(CP.execFile), execFile: promisify(CP.execFile),
} }
export type NetworkInterfaceType = "ui" | "p2p" | "api" | "other" export type ServiceInterfaceType = "ui" | "p2p" | "api"
export type Utils< export type Utils<
Manifest extends SDKManifest, Manifest extends SDKManifest,
@@ -81,11 +79,11 @@ export type Utils<
description: string description: string
hasPrimary: boolean hasPrimary: boolean
disabled: boolean disabled: boolean
type: NetworkInterfaceType type: ServiceInterfaceType
username: null | string username: null | string
path: string path: string
search: Record<string, string> search: Record<string, string>
}) => NetworkInterfaceBuilder }) => ServiceInterfaceBuilder
getSystemSmtp: () => GetSystemSmtp & WrapperOverWrite getSystemSmtp: () => GetSystemSmtp & WrapperOverWrite
host: { host: {
static: (id: string) => StaticHost static: (id: string) => StaticHost
@@ -101,16 +99,16 @@ export type Utils<
>( >(
value: In, value: In,
) => Promise<MountDependenciesOut<In>> ) => Promise<MountDependenciesOut<In>>
networkInterface: { serviceInterface: {
getOwn: (interfaceId: InterfaceId) => GetNetworkInterface & WrapperOverWrite getOwn: (id: ServiceInterfaceId) => GetServiceInterface & WrapperOverWrite
get: (opts: { get: (opts: {
interfaceId: InterfaceId id: ServiceInterfaceId
packageId: PackageId packageId: PackageId
}) => GetNetworkInterface & WrapperOverWrite }) => GetServiceInterface & WrapperOverWrite
getAllOwn: () => GetNetworkInterfaces & WrapperOverWrite getAllOwn: () => GetServiceInterfaces & WrapperOverWrite
getAll: (opts: { getAll: (opts: {
packageId: PackageId packageId: PackageId
}) => GetNetworkInterfaces & WrapperOverWrite }) => GetServiceInterfaces & WrapperOverWrite
} }
nullIfEmpty: typeof nullIfEmpty nullIfEmpty: typeof nullIfEmpty
runCommand: <A extends string>( runCommand: <A extends string>(
@@ -156,11 +154,11 @@ export const createUtils = <
description: string description: string
hasPrimary: boolean hasPrimary: boolean
disabled: boolean disabled: boolean
type: NetworkInterfaceType type: ServiceInterfaceType
username: null | string username: null | string
path: string path: string
search: Record<string, string> search: Record<string, string>
}) => new NetworkInterfaceBuilder({ ...options, effects }), }) => new ServiceInterfaceBuilder({ ...options, effects }),
childProcess, childProcess,
getSystemSmtp: () => getSystemSmtp: () =>
new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite, new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite,
@@ -172,18 +170,18 @@ export const createUtils = <
}, },
nullIfEmpty, nullIfEmpty,
networkInterface: { serviceInterface: {
getOwn: (interfaceId: InterfaceId) => getOwn: (id: ServiceInterfaceId) =>
getNetworkInterface(effects, { interfaceId }) as GetNetworkInterface & getServiceInterface(effects, { id }) as GetServiceInterface &
WrapperOverWrite, WrapperOverWrite,
get: (opts: { interfaceId: InterfaceId; packageId: PackageId }) => get: (opts: { id: ServiceInterfaceId; packageId: PackageId }) =>
getNetworkInterface(effects, opts) as GetNetworkInterface & getServiceInterface(effects, opts) as GetServiceInterface &
WrapperOverWrite, WrapperOverWrite,
getAllOwn: () => getAllOwn: () =>
getNetworkInterfaces(effects, {}) as GetNetworkInterfaces & getServiceInterfaces(effects, {}) as GetServiceInterfaces &
WrapperOverWrite, WrapperOverWrite,
getAll: (opts: { packageId: PackageId }) => getAll: (opts: { packageId: PackageId }) =>
getNetworkInterfaces(effects, opts) as GetNetworkInterfaces & getServiceInterfaces(effects, opts) as GetServiceInterfaces &
WrapperOverWrite, WrapperOverWrite,
}, },
store: { store: {