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

@@ -1,28 +1,28 @@
import { Address, Effects, HostName, NetworkInterface } from "../types"
import { AddressInfo, Effects, Hostname, ServiceInterface } from "../types"
import * as regexes from "./regexes"
import { NetworkInterfaceType } from "./utils"
import { ServiceInterfaceType } from "./utils"
export type UrlString = string
export type HostId = string
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]
if (!founds) return null
const parts = founds.split("@")
const last = parts[parts.length - 1] as HostName | null
const last = parts[parts.length - 1] as Hostname | null
return last
}
export type Filled = {
hostnames: HostName[]
onionHostnames: HostName[]
localHostnames: HostName[]
ipHostnames: HostName[]
ipv4Hostnames: HostName[]
ipv6Hostnames: HostName[]
nonIpHostnames: HostName[]
allHostnames: HostName[]
hostnames: Hostname[]
onionHostnames: Hostname[]
localHostnames: Hostname[]
ipHostnames: Hostname[]
ipv4Hostnames: Hostname[]
ipv6Hostnames: Hostname[]
nonIpHostnames: Hostname[]
allHostnames: Hostname[]
urls: UrlString[]
onionUrls: UrlString[]
@@ -33,9 +33,9 @@ export type Filled = {
nonIpUrls: UrlString[]
allUrls: UrlString[]
}
export type FilledAddress = Address & Filled
export type NetworkInterfaceFilled = {
interfaceId: string
export type FilledAddressInfo = AddressInfo & Filled
export type ServiceInterfaceFilled = {
id: string
/** The title of this field to be displayed */
name: string
/** Human readable description, used as tooltip usually */
@@ -44,15 +44,15 @@ export type NetworkInterfaceFilled = {
hasPrimary: boolean
/** Whether or not the interface disabled */
disabled: boolean
/** All URIs */
addresses: FilledAddress[]
/** Indicates if we are a ui/ p2p/ api/ other for the kind of interface that this is representing */
type: NetworkInterfaceType
primaryHostname: HostName | null
/** URI information */
addressInfo: FilledAddressInfo
/** Indicates if we are a ui/p2p/api for the kind of interface that this is representing */
type: ServiceInterfaceType
/** The primary hostname for the service, as chosen by the user */
primaryHostname: Hostname | null
/** The primary URL for the service, as chosen by the user */
primaryUrl: UrlString | null
} & Filled
}
const either =
<A>(...args: ((a: A) => boolean)[]) =>
(a: A) =>
@@ -63,8 +63,8 @@ const negate =
!fn(a)
const unique = <A>(values: A[]) => Array.from(new Set(values))
const addressHostToUrl = (
{ options, username, suffix }: Address,
host: HostName,
{ options, username, suffix }: AddressInfo,
host: Hostname,
): UrlString => {
const scheme = host.endsWith(".onion")
? options.scheme
@@ -76,15 +76,12 @@ const addressHostToUrl = (
}${host}${suffix}`
}
export const filledAddress = (
mapHostnames: {
[hostId: string]: HostName[]
},
address: Address,
): FilledAddress => {
const toUrl = addressHostToUrl.bind(null, address)
const hostnames = mapHostnames[address.hostId] ?? []
hostnames: Hostname[],
addressInfo: AddressInfo,
): FilledAddressInfo => {
const toUrl = addressHostToUrl.bind(null, addressInfo)
return {
...address,
...addressInfo,
hostnames,
get onionHostnames() {
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 ({
effects,
interfaceId,
id,
packageId,
callback,
}: {
effects: Effects
interfaceId: string
id: string
packageId: string | undefined
callback: () => void
}) => {
const interfaceValue = await effects.getInterface({
interfaceId,
const serviceInterfaceValue = await effects.getServiceInterface({
serviceInterfaceId: id,
packageId,
callback,
})
const hostIdsRecord = Promise.all(
unique(interfaceValue.addresses.map((x) => x.hostId)).map(
async (hostId) =>
[
hostId,
await effects.getHostnames({
packageId,
hostId,
callback,
}),
] as const,
),
)
const primaryUrl = effects.getPrimaryUrl({
interfaceId,
const hostIdRecord = await effects.getHostnames({
packageId,
hostId: serviceInterfaceValue.addressInfo.hostId,
callback,
})
const primaryUrl = await effects.getPrimaryUrl({
serviceInterfaceId: id,
packageId,
callback,
})
const fillAddress = filledAddress.bind(
null,
Object.fromEntries(await hostIdsRecord),
)
const interfaceFilled: NetworkInterfaceFilled = networkInterfaceFilled(
interfaceValue,
await primaryUrl,
interfaceValue.addresses.map(fillAddress),
)
const interfaceFilled: ServiceInterfaceFilled = {
...serviceInterfaceValue,
primaryUrl: primaryUrl,
addressInfo: filledAddress(hostIdRecord, serviceInterfaceValue.addressInfo),
get primaryHostname() {
if (primaryUrl == null) return null
return getHostname(primaryUrl)
},
}
return interfaceFilled
}
export class GetNetworkInterface {
export class GetServiceInterface {
constructor(
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
*/
async const() {
const { interfaceId, packageId } = this.opts
const { id, packageId } = this.opts
const callback = this.effects.restart
const interfaceFilled: NetworkInterfaceFilled = await makeInterfaceFilled({
const interfaceFilled: ServiceInterfaceFilled = await makeInterfaceFilled({
effects: this.effects,
interfaceId,
id,
packageId,
callback,
})
@@ -270,14 +196,14 @@ export class GetNetworkInterface {
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() {
const { interfaceId, packageId } = this.opts
const { id, packageId } = this.opts
const callback = () => {}
const interfaceFilled: NetworkInterfaceFilled = await makeInterfaceFilled({
const interfaceFilled: ServiceInterfaceFilled = await makeInterfaceFilled({
effects: this.effects,
interfaceId,
id,
packageId,
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() {
const { interfaceId, packageId } = this.opts
const { id, packageId } = this.opts
while (true) {
let callback: () => void = () => {}
const waitForNext = new Promise<void>((resolve) => {
@@ -297,7 +223,7 @@ export class GetNetworkInterface {
})
yield await makeInterfaceFilled({
effects: this.effects,
interfaceId,
id,
packageId,
callback,
})
@@ -305,9 +231,9 @@ export class GetNetworkInterface {
}
}
}
export function getNetworkInterface(
export function getServiceInterface(
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 {
HostId,
NetworkInterfaceFilled,
ServiceInterfaceFilled,
filledAddress,
networkInterfaceFilled,
} from "./getNetworkInterface"
getHostname,
} from "./getServiceInterface"
const makeManyInterfaceFilled = async ({
effects,
@@ -15,7 +14,7 @@ const makeManyInterfaceFilled = async ({
packageId: string | undefined
callback: () => void
}) => {
const interfaceValues = await effects.listInterface({
const serviceInterfaceValues = await effects.listServiceInterfaces({
packageId,
callback,
})
@@ -23,7 +22,9 @@ const makeManyInterfaceFilled = async ({
await Promise.all(
Array.from(
new Set(
interfaceValues.flatMap((x) => x.addresses).map((x) => x.hostId),
serviceInterfaceValues
.flatMap((x) => x.addressInfo)
.map((x) => x.hostId),
),
).map(
async (hostId) =>
@@ -38,25 +39,37 @@ const makeManyInterfaceFilled = async ({
),
),
)
const fillAddress = filledAddress.bind(null, hostIdsRecord)
const interfacesFilled: NetworkInterfaceFilled[] = await Promise.all(
interfaceValues.map(async (interfaceValue) =>
networkInterfaceFilled(
interfaceValue,
await effects.getPrimaryUrl({
interfaceId: interfaceValue.interfaceId,
packageId,
callback,
}),
interfaceValue.addresses.map(fillAddress),
),
),
const serviceInterfacesFilled: ServiceInterfaceFilled[] = await Promise.all(
serviceInterfaceValues.map(async (serviceInterfaceValue) => {
const hostIdRecord = await effects.getHostnames({
packageId,
hostId: serviceInterfaceValue.addressInfo.hostId,
callback,
})
const primaryUrl = await effects.getPrimaryUrl({
serviceInterfaceId: serviceInterfaceValue.id,
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(
readonly effects: Effects,
readonly opts: { packageId?: string },
@@ -68,7 +81,7 @@ export class GetNetworkInterfaces {
async const() {
const { packageId } = this.opts
const callback = this.effects.restart
const interfaceFilled: NetworkInterfaceFilled[] =
const interfaceFilled: ServiceInterfaceFilled[] =
await makeManyInterfaceFilled({
effects: this.effects,
packageId,
@@ -78,12 +91,12 @@ export class GetNetworkInterfaces {
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() {
const { packageId } = this.opts
const callback = () => {}
const interfaceFilled: NetworkInterfaceFilled[] =
const interfaceFilled: ServiceInterfaceFilled[] =
await makeManyInterfaceFilled({
effects: this.effects,
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() {
const { packageId } = this.opts
@@ -112,9 +125,9 @@ export class GetNetworkInterfaces {
}
}
}
export function getNetworkInterfaces(
export function getServiceInterfaces(
effects: Effects,
opts: { packageId?: string },
) {
return new GetNetworkInterfaces(effects, opts)
return new GetServiceInterfaces(effects, opts)
}

View File

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