Feature/fe new registry (#2647)

* bugfixes

* update fe types

* implement new registry types in marketplace and ui

* fix marketplace types to have default params

* add alt implementation toggle

* merge cleanup

* more cleanup and notes

* fix build

* cleanup sync with next/minor

* add exver JS parser

* parse ValidExVer to string

* update types to interface

* add VersionRange and comparative functions

* Parse ExtendedVersion from string

* add conjunction, disjunction, and inversion logic

* consider flavor in satisfiedBy fn

* consider prerelease for ordering

* add compare fn for sorting

* rename fns for consistency

* refactoring

* update compare fn to return null if flavors don't match

* begin simplifying dependencies

* under construction

* wip

* add dependency metadata to CurrentDependencyInfo

* ditch inheritance for recursive VersionRange constructor. Recursive 'satisfiedBy' fn wip

* preprocess manifest

* misc fixes

* use sdk version as osVersion in manifest

* chore: Change the type to just validate and not generate all solutions.

* add publishedAt

* fix pegjs exports

* integrate exver into sdk

* misc fixes

* complete satisfiedBy fn

* refactor - use greaterThanOrEqual and lessThanOrEqual fns

* fix tests

* update dependency details

* update types

* remove interim types

* rename alt implementation to flavor

* cleanup os update

* format exver.ts

* add s9pk parsing endpoints

* fix build

* update to exver

* exver and bug fixes

* update static endpoints + cleanup

* cleanup

* update static proxy verification

* make mocks more robust; fix dep icon fallback; cleanup

* refactor alert versions and update fixtures

* registry bugfixes

* misc fixes

* cleanup unused

* convert patchdb ui seed to camelCase

* update otherVersions type

* change otherVersions: null to 'none'

* refactor and complete feature

* improve static endpoints

* fix install params

* mask systemd-networkd-wait-online

* fix static file fetching

* include non-matching versions in otherVersions

* convert release notes to modal and clean up displayExver

* alert for no other versions

* Fix ack-instructions casing

* fix indeterminate loader on service install

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Shadowy Super Coder <musashidisciple@proton.me>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
Co-authored-by: J H <dragondef@gmail.com>
Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
Lucy
2024-07-22 20:48:12 -04:00
committed by GitHub
parent 0fbb18b315
commit a535fc17c3
196 changed files with 7002 additions and 2162 deletions

View File

@@ -18,6 +18,15 @@ import { AuthService } from '../auth.service'
import { DOCUMENT } from '@angular/common'
import { DataModel } from '../patch-db/data-model'
import { Dump, pathFromArray } from 'patch-db-client'
import { T } from '@start9labs/start-sdk'
import {
GetPackageReq,
GetPackageRes,
GetPackagesReq,
GetPackagesRes,
MarketplacePkg,
} from '@start9labs/marketplace'
import { blake3 } from '@noble/hashes/blake3'
@Injectable()
export class LiveApiService extends ApiService {
@@ -29,17 +38,7 @@ export class LiveApiService extends ApiService {
@Inject(PATCH_CACHE) private readonly cache$: Observable<Dump<DataModel>>,
) {
super()
; (window as any).rpcClient = this
}
// for getting static files: ex icons, instructions, licenses
async getStatic(url: string): Promise<string> {
return this.httpRequest({
method: Method.GET,
url,
responseType: 'text',
})
;(window as any).rpcClient = this
}
// for sideloading packages
@@ -62,6 +61,36 @@ export class LiveApiService extends ApiService {
})
}
// for getting static files: ex. instructions, licenses
async getStaticProxy(
pkg: MarketplacePkg,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string> {
const encodedUrl = encodeURIComponent(pkg.s9pk.url)
return this.httpRequest({
method: Method.GET,
url: `/s9pk/proxy/${encodedUrl}/${path}`,
params: {
rootSighash: pkg.s9pk.commitment.rootSighash,
rootMaxsize: pkg.s9pk.commitment.rootMaxsize,
},
responseType: 'text',
})
}
async getStaticInstalled(
id: T.PackageId,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string> {
return this.httpRequest({
method: Method.GET,
url: `/s9pk/installed/${id}.s9pk/${path}`,
responseType: 'text',
})
}
// websocket
openWebsocket$<T>(
@@ -257,24 +286,63 @@ export class LiveApiService extends ApiService {
// marketplace URLs
async marketplaceProxy<T>(
path: string,
qp: Record<string, string>,
baseUrl: string,
async registryRequest<T>(
registryUrl: string,
options: RPCOptions,
): Promise<T> {
const fullUrl = `${baseUrl}${path}?${new URLSearchParams(qp).toString()}`
return this.rpcRequest({
method: 'marketplace.get',
params: { url: fullUrl },
...options,
method: `registry.${options.method}`,
params: { registry: registryUrl, ...options.params },
})
}
async checkOSUpdate(qp: RR.CheckOSUpdateReq): Promise<RR.CheckOSUpdateRes> {
return this.marketplaceProxy(
'/eos/v0/latest',
qp,
this.config.marketplace.start9,
)
const { serverId } = qp
return this.registryRequest(this.config.marketplace.start9, {
method: 'os.version.get',
params: { serverId },
})
}
async getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo> {
return this.registryRequest(registryUrl, {
method: 'info',
params: {},
})
}
async getRegistryPackage(
registryUrl: string,
id: string,
versionRange: string | null,
): Promise<GetPackageRes> {
const params: GetPackageReq = {
id,
version: versionRange,
sourceVersion: null,
otherVersions: 'short',
}
return this.registryRequest<GetPackageRes>(registryUrl, {
method: 'package.get',
params,
})
}
async getRegistryPackages(registryUrl: string): Promise<GetPackagesRes> {
const params: GetPackagesReq = {
id: null,
version: null,
sourceVersion: null,
otherVersions: 'short',
}
return this.registryRequest<GetPackagesRes>(registryUrl, {
method: 'package.get',
params,
})
}
// notification
@@ -504,6 +572,29 @@ export class LiveApiService extends ApiService {
private async httpRequest<T>(opts: HttpOptions): Promise<T> {
const res = await this.http.httpRequest<T>(opts)
if (res.headers.get('Repr-Digest')) {
// verify
const digest = res.headers.get('Repr-Digest')!
let data: Uint8Array
if (opts.responseType === 'arrayBuffer') {
data = Buffer.from(res.body as ArrayBuffer)
} else if (opts.responseType === 'text') {
data = Buffer.from(res.body as string)
} else if ((opts.responseType as string) === 'blob') {
data = Buffer.from(await (res.body as Blob).arrayBuffer())
} else {
console.warn(
`could not verify Repr-Digest for responseType ${
opts.responseType || 'json'
}`,
)
return res.body
}
const computedDigest = Buffer.from(blake3(data)).toString('base64')
if (`blake3=:${computedDigest}:` === digest) return res.body
console.debug(computedDigest, digest)
throw new Error('File digest mismatch.')
}
return res.body
}
}