0.3.0 refactor

ui: adds overlay layer to patch-db-client

ui: getting towards mocks

ui: cleans up factory init

ui: nice type hack

ui: live api for patch

ui: api service source + http

starts up

ui: api source + http

ui: rework patchdb config, pass stashTimeout into patchDbModel

wires in temp patching into api service

ui: example of wiring patchdbmodel into page

begin integration

remove unnecessary method

linting

first data rendering

rework app initialization

http source working for ssh delete call

temp patches working

entire Embassy tab complete

not in kansas anymore

ripping, saving progress

progress for API request response types and endoint defs

Update data-model.ts

shambles, but in a good way

progress

big progress

progress

installed list working

big progress

progress

progress

begin marketplace redesign

Update api-types.ts

Update api-types.ts

marketplace improvements

cosmetic

dependencies and recommendations

begin nym auth approach

install wizard

restore flow and donations
This commit is contained in:
Aaron Greenspan
2021-02-16 13:45:09 -07:00
committed by Aiden McClelland
parent 46f32cb90b
commit 8d01ebe8b2
238 changed files with 25509 additions and 14852 deletions

View File

@@ -1,107 +0,0 @@
//////////////// Install/Uninstall ////////////////////////////////////////////////
type AppDependentBreakage = {
// id of the dependent app which will or did break (Stopped) given the action.
id: string
title: string
iconUrl: string
}
POST /apps/:appId/install(?dryrun)
body: {
version: string, //semver
}
response : ApiAppInstalledFull & { breakages: AppDependentBreakage[] }
POST /apps/:appId/uninstall(?dryrun)
response : { breakages: AppDependentBreakage[] }
/////////////////////////////// Store/Show /////////////////////////////////////////////////
type ApiAppAvailableFull = ... {
// app base data
id: string
title: string
status: AppStatus | null
versionInstalled: string | null
iconURL: string
// preview data
versionLatest: string
descriptionShort: string
// version specific data
releaseNotes: string
serviceRequirements: AppDependencyRequirement[]
// other data
descriptionLong: string,
version: string[],
}
type AppDependencyRequirement = ... {
//app base data (minus status + version installed)
id: string
title: string
iconURL: string
// dependency data
optional: string | null
default: boolean
versionSpec: string
description: string | null
violation: AppDependencyRequirementViolation | null
}
type AppDependencyRequirementViolation =
{ name: 'missing'; suggestedVersion: string; } |
{ name: 'incompatible-version'; suggestedVersion: string; } |
{ name: 'incompatible-config'; ruleViolations: string; auto-configurable: boolean } | // (auto-configurable for if/when we do that)
{ name: 'incompatible-status'; status: AppStatus; }
// Get App Available Full
GET /apps/:appId/store
response: ApiAppAvailableFull
// Get Version Specific Data for an App Available
GET /apps/:appId/store/:version
response: {
// version specific data
releaseNotes: string
serviceRequirements: AppDependencyRequirement[]
}
///////////////////////////// Installed/Show ///////////////////////////////////////////
type ApiAppInstalledFull {
// app base data
id: string
title: string
status: AppStatus | null
versionInstalled: string | null
iconURL: string
// preview data
// other data
instructions: string | null
lastBackup: string | null
configuredRequirements: AppDependencyRequirement[] | null // null if not yet configured
}
// Get App Installed Full
GET /apps/:appId/installed
reseponse: AppInstalledFull

View File

@@ -1,43 +1,307 @@
import { ConfigSpec } from 'src/app/app-config/config-types'
import { AppAvailableFull, AppInstalledFull, AppInstalledPreview } from 'src/app/models/app-types'
import { Rules } from '../../models/app-model'
import { SSHFingerprint, ServerStatus, ServerSpecs } from '../../models/server-model'
import { Dump, Operation, Revision } from 'patch-db-client'
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { DataModel, DependencyError, Manifest, URL } from 'src/app/models/patch-db/data-model'
/** SERVER **/
export module RR {
export interface ApiServer {
name: string
status: ServerStatus
versionInstalled: string
alternativeRegistryUrl: string | null
specs: ServerSpecs
wifi: {
ssids: string[]
current: string | null
// DB
export type GetRevisionsRes = Revision[] | Dump<DataModel>
export type GetDumpRes = Dump<DataModel>
export type SetDBValueReq = WithExpire<{ pointer: string, value: any }> // db.put.ui
export type SetDBValueRes = WithRevision<null>
// auth
export type SubmitPinReq = { pin: string } // auth.pin - unauthed
export type SubmitPinRes = null
export type SubmitPasswordReq = { password: string } // auth.password - unauthed
export type SubmitPasswordRes = null
export type LogoutReq = { } // auth.logout
export type LogoutRes = null
// server
export type GetServerLogsReq = { before?: string } // server.logs
export type GetServerLogsRes = Log[]
export type GetServerMetricsReq = { } // server.metrics
export type GetServerMetricsRes = ServerMetrics
export type UpdateServerReq = WithExpire<{ }> // server.update
export type UpdateServerRes = WithRevision<null>
export type RestartServerReq = { } // server.restart
export type RestartServerRes = null
export type ShutdownServerReq = { } // server.shutdown
export type ShutdownServerRes = null
// network
export type RefreshLanReq = { } // network.lan.refresh
export type RefreshLanRes = null
// registry
export type SetRegistryReq = WithExpire<{ url: string }> // registry.set
export type SetRegistryRes = WithRevision<null>
// notification
export type GetNotificationsReq = WithExpire<{ page: number, 'per-page': number }> // notification.list
export type GetNotificationsRes = WithRevision<ServerNotification<number>[]>
export type DeleteNotificationReq = { id: string } // notification.delete
export type DeleteNotificationRes = null
// wifi
export type AddWifiReq = { // wifi.add
ssid: string
password: string
country: string
priority: number
connect: boolean
}
ssh: SSHFingerprint[]
serverId: string
welcomeAck: boolean
autoCheckUpdates: boolean
export type AddWifiRes = null
export type ConnectWifiReq = WithExpire<{ ssid: string }> // wifi.connect
export type ConnectWifiRes = WithRevision<null>
export type DeleteWifiReq = WithExpire<{ ssid: string }> // wifi.delete
export type DeleteWifiRes = WithRevision<null>
// ssh
export type GetSSHKeysReq = { } // ssh.get
export type GetSSHKeysRes = SSHKeys
export type AddSSHKeyReq = { pubkey: string } // ssh.add
export type AddSSHKeyRes = SSHKeys
export type DeleteSSHKeyReq = { hash: string } // ssh.delete
export type DeleteSSHKeyRes = null
// backup
export type CreateBackupReq = WithExpire<{ logicalname: string, password: string }> // backup.create
export type CreateBackupRes = WithRevision<null>
export type RestoreBackupReq = { logicalname: string, password: string } // backup.restore - unauthed
export type RestoreBackupRes = null
// disk
export type GetDisksReq = { } // disk.list
export type GetDisksRes = DiskInfo
export type EjectDisksReq = { logicalname: string } // disk.eject
export type EjectDisksRes = null
// package
export type GetPackagePropertiesReq = { id: string } // package.properties
export type GetPackagePropertiesRes<T extends number> = PackagePropertiesVersioned<T>
export type GetPackageLogsReq = { id: string, before?: string } // package.logs
export type GetPackageLogsRes = Log[]
export type InstallPackageReq = WithExpire<{ id: string, version: string }> // package.install
export type InstallPackageRes = WithRevision<null>
export type DryUpdatePackageReq = { id: string, version: string } // package.update.dry
export type DryUpdatePackageRes = BreakageRes
export type GetPackageConfigReq = { id: string } // package.config.get
export type GetPackageConfigRes = { spec: ConfigSpec, config: object }
export type DrySetPackageConfigReq = { id: string, config: object } // package.config.set.dry
export type DrySetPackageConfigRes = BreakageRes
export type SetPackageConfigReq = WithExpire<DrySetPackageConfigReq> // package.config.set
export type SetPackageConfigRes = WithRevision<null>
export type RestorePackageReq = WithExpire<{ id: string, logicalname: string, password: string }> // package.backup.restore
export type RestorePackageRes = WithRevision<null>
export type ExecutePackageActionReq = { id: string, 'action-id': string, input?: object } // package.action
export type ExecutePackageActionRes = ActionResponse
export type StartPackageReq = WithExpire<{ id: string }> // package.start
export type StartPackageRes = WithRevision<null>
export type DryStopPackageReq = StopPackageReq // package.stop.dry
export type DryStopPackageRes = BreakageRes
export type StopPackageReq = WithExpire<{ id: string }> // package.stop
export type StopPackageRes = WithRevision<null>
export type DryRemovePackageReq = RemovePackageReq // package.remove.dry
export type DryRemovePackageRes = BreakageRes
export type RemovePackageReq = WithExpire<{ id: string }> // package.remove
export type RemovePackageRes = WithRevision<null>
export type DryConfigureDependencyReq = { 'dependency-id': string, 'dependent-id': string } // package.dependency.configure.dry
export type DryConfigureDependencyRes = object
// marketplace
export type GetMarketplaceDataReq = { }
export type GetMarketplaceDataRes = MarketplaceData
export type GetMarketplaceEOSReq = { }
export type GetMarketplaceEOSRes = MarketplaceEOS
export type GetAvailableListReq = { category?: string, query?: string, page: number, 'per-page': number }
export type GetAvailableListRes = AvailablePreview[]
export type GetAvailableShowReq = { id: string, version?: string }
export type GetAvailableShowRes = AvailableShow
}
/** APPS **/
export type ApiAppAvailableFull = Omit<AppAvailableFull, 'versionViewing'>
export type WithExpire<T> = { 'expire-id'?: string } & T
export type WithRevision<T> = { response: T, revision?: Revision }
export type ApiAppInstalledPreview = Omit<AppInstalledPreview, 'hasUI' | 'launchable'>
export type ApiAppInstalledFull = Omit<AppInstalledFull, 'hasFetchedFull' | 'hasUI' | 'launchable'>
export interface ApiAppConfig {
spec: ConfigSpec
config: object | null
rules: Rules[]
export interface MarketplaceData {
categories: string[]
}
/** MISC **/
export type Unit = { never?: never; } // hack for the unit typ
export type V1Status = {
status: 'nothing' | 'instructions' | 'available'
export interface MarketplaceEOS {
version: string
headline: string
notes: string
}
export interface AvailablePreview {
id: string
title: string
version: string
icon: URL
descriptionShort: string
}
export interface AvailableShow {
icon: URL
manifest: Manifest
categories: string[]
versions: string[]
'dependency-metadata': {
[id: string]: {
title: string
icon: URL
}
}
}
export interface BreakageRes {
patch: Operation[],
breakages: Breakages
}
export interface Breakages {
[id: string]: TaggedDependencyError
}
export interface TaggedDependencyError {
dependency: string,
error: DependencyError,
}
export interface Log {
timestamp: string
log: string
}
export interface ActionResponse {
message: string
value: string | number | boolean | null
copyable: boolean
qr: boolean
}
export interface ServerMetrics {
[key: string]: {
[key: string]: {
value: string | number | null
unit?: string
}
}
}
export interface DiskInfo {
[id: string]: DiskInfoEntry
}
export interface DiskInfoEntry {
size: string
description: string | null
partitions: PartitionInfo
}
export interface PartitionInfo {
[logicalname: string]: PartitionInfoEntry
}
export interface PartitionInfoEntry {
'is-mounted': boolean // We do not allow backups to mounted partitions
size: string | null
label: string | null
}
export interface ServerSpecs {
[key: string]: string | number
}
export interface SSHKeys {
[hash: string]: SSHKeyEntry
}
export interface SSHKeyEntry {
alg: string
hostname: string
hash: string
}
export type ServerNotifications = ServerNotification<any>[]
export interface ServerNotification<T extends number> {
id: string
'package-id': string | null
'created-at': string
code: T
level: NotificationLevel
title: string
message: string
data: NotificationData<T>
}
export enum NotificationLevel {
Success = 'success',
Info = 'info',
Warning = 'warning',
Error = 'error',
}
export type NotificationData<T> = T extends 0 ? null :
T extends 1 ? BackupReport :
any
export interface BackupReport {
server: {
attempted: boolean
error: string | null
}
packages: {
[id: string]: {
error: string | null
}
}
}

View File

@@ -1,14 +1,12 @@
import { HttpService } from '../http.service'
import { AppModel } from '../../models/app-model'
import { MockApiService } from './mock-api.service'
import { LiveApiService } from './live-api.service'
import { ServerModel } from 'src/app/models/server-model'
import { ConfigService } from '../config.service'
export function ApiServiceFactory (config: ConfigService, http: HttpService, appModel: AppModel, serverModel: ServerModel) {
if (config.api.useMocks) {
return new MockApiService(appModel, serverModel, config)
export function ApiServiceFactory (config: ConfigService, http: HttpService) {
if (config.api.mocks) {
return new MockApiService(config)
} else {
return new LiveApiService(http, appModel, serverModel, config)
return new LiveApiService(http, config)
}
}

View File

@@ -1,124 +1,209 @@
import { Rules } from '../../models/app-model'
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
import { S9Notification, SSHFingerprint, ServerMetrics, DiskInfo } from '../../models/server-model'
import { Subject, Observable } from 'rxjs'
import { Unit, ApiServer, ApiAppInstalledFull, ApiAppConfig, ApiAppAvailableFull, ApiAppInstalledPreview, V1Status } from './api-types'
import { AppMetrics, AppMetricsVersioned } from 'src/app/util/metrics.util'
import { ConfigSpec } from 'src/app/app-config/config-types'
import { Http, Source, Update, Operation, Revision } from 'patch-db-client'
import { RR } from './api-types'
import { DataModel } from 'src/app/models/patch-db/data-model'
import { filter } from 'rxjs/operators'
import * as uuid from 'uuid'
export abstract class ApiService {
private $unauthorizedApiResponse$: Subject<{ }> = new Subject()
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
protected readonly sync = new Subject<Update<DataModel>>()
private syncing = true
watch401$ (): Observable<{ }> {
return this.$unauthorizedApiResponse$.asObservable()
/** PatchDb Source interface. Post/Patch requests provide a source of patches to the db. */
// sequenceStream '_' is not used by the live api, but is overridden by the mock
watch$ (_?: Observable<number>): Observable<Update<DataModel>> {
return this.sync.asObservable().pipe(filter(() => this.syncing))
}
authenticatedRequestsEnabled: boolean = false
// used for determining internet connectivity
abstract ping (): Promise<void>
protected received401 () {
this.authenticatedRequestsEnabled = false
this.$unauthorizedApiResponse$.next()
// for getting static files: ex icons, instructions, licenses
abstract getStatic (url: string): Promise<string>
// db
abstract getRevisions (since: number): Promise<RR.GetRevisionsRes>
abstract getDump (): Promise<RR.GetDumpRes>
protected abstract setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes>
setDbValue = (params: RR.SetDBValueReq) => this.syncResponse(
() => this.setDbValueRaw(params),
)()
// auth
abstract submitPin (params: RR.SubmitPinReq): Promise<RR.SubmitPinRes>
abstract submitPassword (params: RR.SubmitPasswordReq): Promise<RR.SubmitPasswordReq>
abstract logout (params: RR.LogoutReq): Promise<RR.LogoutRes>
// server
abstract getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes>
abstract getServerMetrics (params: RR.GetServerMetricsReq): Promise<RR.GetServerMetricsRes>
protected abstract updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
updateServer = (params: RR.UpdateServerReq) => this.syncResponse(
() => this.updateServerRaw(params),
)()
abstract restartServer (params: RR.UpdateServerReq): Promise<RR.RestartServerRes>
abstract shutdownServer (params: RR.ShutdownServerReq): Promise<RR.ShutdownServerRes>
// network
abstract refreshLan (params: RR.RefreshLanReq): Promise<RR.RefreshLanRes>
// registry
protected abstract setRegistryRaw (params: RR.SetRegistryReq): Promise<RR.SetRegistryRes>
setRegistry = (params: RR.SetRegistryReq) => this.syncResponse(
() => this.setRegistryRaw(params),
)()
// notification
abstract getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes>
getNotifications = (params: RR.GetNotificationsReq) => this.syncResponse<RR.GetNotificationsRes['response'], any>(
() => this.getNotificationsRaw(params),
)()
abstract deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes>
// wifi
abstract addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes>
protected abstract connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
connectWifi = (params: RR.ConnectWifiReq) => this.syncResponse(
() => this.connectWifiRaw(params),
)()
protected abstract deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.ConnectWifiRes>
deleteWifi = (params: RR.DeleteWifiReq) => this.syncResponse(
() => this.deleteWifiRaw(params),
)()
// ssh
abstract getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes>
abstract addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes>
abstract deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes>
// backup
protected abstract createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes>
createBackup = (params: RR.CreateBackupReq) => this.syncResponse(
() => this.createBackupRaw(params),
)()
protected abstract restoreBackupRaw (params: RR.RestoreBackupReq): Promise<RR.RestoreBackupRes>
restoreBackup = (params: RR.RestoreBackupReq) => this.syncResponse(
() => this.restoreBackupRaw(params),
)()
// disk
abstract getDisks (params: RR.GetDisksReq): Promise<RR.GetDisksRes>
abstract ejectDisk (params: RR.EjectDisksReq): Promise<RR.EjectDisksRes>
// package
abstract getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes<any>['data']>
abstract getPackageLogs (params: RR.GetPackageLogsReq): Promise<RR.GetPackageLogsRes>
protected abstract installPackageRaw (params: RR.InstallPackageReq): Promise<RR.InstallPackageRes>
installPackage = (params: RR.InstallPackageReq) => this.syncResponse(
() => this.installPackageRaw(params),
)()
abstract dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes>
abstract getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes>
abstract drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes>
protected abstract setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes>
setPackageConfig = (params: RR.SetPackageConfigReq) => this.syncResponse(
() => this.setPackageConfigRaw(params),
)()
protected abstract restorePackageRaw (params: RR.RestorePackageReq): Promise<RR.RestorePackageRes>
restorePackage = (params: RR.RestorePackageReq) => this.syncResponse(
() => this.restorePackageRaw(params),
)()
abstract executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes>
protected abstract startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes>
startPackage = (params: RR.StartPackageReq) => this.syncResponse(
() => this.startPackageRaw(params),
)()
abstract dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes>
protected abstract stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes>
stopPackage = (params: RR.StopPackageReq) => this.syncResponse(
() => this.stopPackageRaw(params),
)()
abstract dryRemovePackage (params: RR.DryRemovePackageReq): Promise<RR.DryRemovePackageRes>
protected abstract removePackageRaw (params: RR.RemovePackageReq): Promise<RR.RemovePackageRes>
removePackage = (params: RR.RemovePackageReq) => this.syncResponse(
() => this.removePackageRaw(params),
)()
abstract dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes>
// marketplace
abstract getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes>
abstract getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes>
abstract getAvailableList (params: RR.GetAvailableListReq): Promise<RR.GetAvailableListRes>
abstract getAvailableShow (params: RR.GetAvailableShowReq): Promise<RR.GetAvailableShowRes>
// Helper allowing quick decoration to sync the response patch and return the response contents.
// Pass in a tempUpdate function which returns a UpdateTemp corresponding to a temporary
// state change you'd like to enact prior to request and expired when request terminates.
private syncResponse<T, F extends (...args: any[]) => Promise<{ response: T, revision?: Revision }>> (f: F, temp?: Operation): (...args: Parameters<F>) => Promise<T> {
return (...a) => {
let expireId = undefined
if (temp) {
expireId = uuid.v4()
this.sync.next({ patch: [temp], expiredBy: expireId })
}
return f(a).then(({ response, revision }) => {
if (revision) this.sync.next(revision)
return response
}) as any
}
}
abstract testConnection (url: string): Promise<true>
abstract getCheckAuth (): Promise<Unit> // Throws an error on failed auth.
abstract postLogin (password: string): Promise<Unit> // Throws an error on failed auth.
abstract postLogout (): Promise<Unit> // Throws an error on failed auth.
abstract getServer (timeout?: number): Promise<ApiServer>
abstract getVersionLatest (): Promise<ReqRes.GetVersionLatestRes>
abstract getServerMetrics (): Promise<ReqRes.GetServerMetricsRes>
abstract getNotifications (page: number, perPage: number): Promise<S9Notification[]>
abstract deleteNotification (id: string): Promise<Unit>
abstract toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit>
abstract updateAgent (version: any): Promise<Unit>
abstract acknowledgeOSWelcome (version: string): Promise<Unit>
abstract getAvailableApps (): Promise<AppAvailablePreview[]>
abstract getAvailableApp (appId: string): Promise<AppAvailableFull>
abstract getAvailableAppVersionSpecificInfo (appId: string, versionSpec: string): Promise<AppAvailableVersionSpecificInfo>
abstract getInstalledApp (appId: string): Promise<AppInstalledFull>
abstract getAppMetrics (appId: string): Promise<AppMetrics>
abstract getInstalledApps (): Promise<AppInstalledPreview[]>
abstract getExternalDisks (): Promise<DiskInfo[]>
abstract getAppConfig (appId: string): Promise<{ spec: ConfigSpec, config: object, rules: Rules[] }>
abstract getAppLogs (appId: string, params?: ReqRes.GetAppLogsReq): Promise<string[]>
abstract getServerLogs (): Promise<string[]>
abstract installApp (appId: string, version: string, dryRun?: boolean): Promise<AppInstalledFull & { breakages: DependentBreakage[] }>
abstract uninstallApp (appId: string, dryRun?: boolean): Promise<{ breakages: DependentBreakage[] }>
abstract startApp (appId: string): Promise<Unit>
abstract stopApp (appId: string, dryRun?: boolean): Promise<{ breakages: DependentBreakage[] }>
abstract restartApp (appId: string): Promise<Unit>
abstract createAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit>
abstract restoreAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit>
abstract stopAppBackup (appId: string): Promise<Unit>
abstract patchAppConfig (app: AppInstalledPreview, config: object, dryRun?: boolean): Promise<{ breakages: DependentBreakage[] }>
abstract postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise<{ config: object, breakages: DependentBreakage[] }>
abstract patchServerConfig (attr: string, value: any): Promise<Unit>
abstract wipeAppData (app: AppInstalledPreview): Promise<Unit>
abstract addSSHKey (sshKey: string): Promise<Unit>
abstract deleteSSHKey (sshKey: SSHFingerprint): Promise<Unit>
abstract addWifi (ssid: string, password: string, country: string, connect: boolean): Promise<Unit>
abstract connectWifi (ssid: string): Promise<Unit>
abstract deleteWifi (ssid: string): Promise<Unit>
abstract restartServer (): Promise<Unit>
abstract shutdownServer (): Promise<Unit>
abstract ejectExternalDisk (logicalName: string): Promise<Unit>
abstract serviceAction (appId: string, serviceAction: ServiceAction): Promise<ReqRes.ServiceActionResponse>
abstract refreshLAN (): Promise<Unit>
abstract checkV1Status (): Promise<V1Status>
// @TODO better types?
// private async process<T, F extends (args: object) => Promise<{ response: T, revision?: Revision }>> (f: F, temps: Operation[] = []): Promise<T> {
// let expireId = undefined
// if (temps.length) {
// expireId = uuid.v4()
// this.sync.next({ patch: temps, expiredBy: expireId })
// }
// const { response, revision } = await f({ ...f.arguments, expireId })
// if (revision) this.sync.next(revision)
// return response
// }
}
export function isRpcFailure<Error, Result> (arg: { error: Error } | { result: Result }): arg is { error: Error } {
return !!(arg as any).error
}
export function isRpcSuccess<Error, Result> (arg: { error: Error } | { result: Result }): arg is { result: Result } {
return !!(arg as any).result
}
export module ReqRes {
export type GetVersionRes = { version: string }
export type PostLoginReq = { password: string }
export type PostLoginRes = Unit
export type ServiceActionRequest = {
jsonrpc: '2.0',
id: string,
method: string
}
export type ServiceActionResponse = {
jsonrpc: '2.0',
id: string
} & ({ error: { code: number, message: string } } | { result: string })
export type GetCheckAuthRes = { }
export type GetServerRes = ApiServer
export type GetVersionLatestRes = { versionLatest: string, releaseNotes: string }
export type GetServerMetricsRes = ServerMetrics
export type GetAppAvailableRes = ApiAppAvailableFull
export type GetAppAvailableVersionInfoRes = AppAvailableVersionSpecificInfo
export type GetAppsAvailableRes = AppAvailablePreview[]
export type GetExternalDisksRes = DiskInfo[]
export type GetAppInstalledRes = ApiAppInstalledFull
export type GetAppConfigRes = ApiAppConfig
export type GetAppLogsReq = { after?: string, before?: string, page?: string, perPage?: string }
export type GetServerLogsReq = { }
export type GetAppLogsRes = string[]
export type GetServerLogsRes = string[]
export type GetAppMetricsRes = AppMetricsVersioned<number>
export type GetAppsInstalledRes = ApiAppInstalledPreview[]
export type PostInstallAppReq = { version: string }
export type PostInstallAppRes = ApiAppInstalledFull & { breakages: DependentBreakage[] }
export type PostUpdateAgentReq = { version: string }
export type PostAppBackupCreateReq = { logicalname: string, password: string }
export type PostAppBackupCreateRes = Unit
export type PostAppBackupRestoreReq = { logicalname: string, password: string }
export type PostAppBackupRestoreRes = Unit
export type PostAppBackupStopRes = Unit
export type PatchAppConfigReq = { config: object }
export type PatchServerConfigReq = { value: string }
export type GetNotificationsReq = { page: string, perPage: string }
export type GetNotificationsRes = S9Notification[]
export type PostAddWifiReq = { ssid: string, password: string, country: string, skipConnect: boolean }
export type PostConnectWifiReq = { country: string }
export type PostAddSSHKeyReq = { sshKey: string }
export type PostAddSSHKeyRes = SSHFingerprint
}
// used for type inference in syncResponse
type ExtractResultPromise<T extends Promise<any>> = T extends Promise<infer R> ? Promise<R> : any

View File

@@ -1,338 +1,223 @@
import { Injectable } from '@angular/core'
import { HttpService, Method, HttpOptions } from '../http.service'
import { AppModel, AppStatus } from '../../models/app-model'
import { AppAvailablePreview, AppAvailableFull, AppInstalledFull, AppInstalledPreview, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
import { S9Notification, SSHFingerprint, ServerModel, DiskInfo } from '../../models/server-model'
import { ApiService, ReqRes } from './api.service'
import { ApiAppInstalledPreview, ApiServer, Unit, V1Status } from './api-types'
import { HttpErrorResponse } from '@angular/common/http'
import { isUnauthorized } from 'src/app/util/web.util'
import { Replace } from 'src/app/util/types.util'
import { AppMetrics, parseMetricsPermissive } from 'src/app/util/metrics.util'
import { modulateTime } from 'src/app/util/misc.util'
import { Observable, of, throwError } from 'rxjs'
import { catchError, mapTo } from 'rxjs/operators'
import * as uuid from 'uuid'
import { HttpService, Method } from '../http.service'
import { ApiService } from './api.service'
import { RR } from './api-types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { ConfigService } from '../config.service'
@Injectable()
export class LiveApiService extends ApiService {
constructor (
private readonly http: HttpService,
// TODO remove app + server model from here. updates to state should be done in a separate class wrapping ApiService + App/ServerModel
private readonly appModel: AppModel,
private readonly serverModel: ServerModel,
private readonly config: ConfigService,
) { super() }
testConnection (url: string): Promise<true> {
return this.http.raw.get(url).pipe(mapTo(true as true), catchError(e => catchHttpStatusError(e))).toPromise()
async ping (): Promise<void> {
return this.http.rpcRequest({ method: 'ping', params: { } })
}
// Used to check whether password or key is valid. If so, it will be used implicitly by all other calls.
async getCheckAuth (): Promise<Unit> {
return this.http.serverRequest<Unit>({ method: Method.GET, url: '/authenticate' }, { version: '' })
async getStatic (url: string): Promise<string> {
return this.http.httpRequest({ method: Method.GET, url })
}
async postLogin (password: string): Promise<Unit> {
return this.http.serverRequest<Unit>({ method: Method.POST, url: '/auth/login', data: { password } }, { version: '' })
// db
async getRevisions (since: number): Promise<RR.GetRevisionsRes> {
return this.http.rpcRequest({ method: 'db.revisions', params: { since } })
}
async postLogout (): Promise<Unit> {
return this.http.serverRequest<Unit>({ method: Method.POST, url: '/auth/logout' }, { version: '' }).then(() => { this.authenticatedRequestsEnabled = false; return { } })
async getDump (): Promise<RR.GetDumpRes> {
return this.http.rpcRequest({ method: 'db.dump' })
}
async getServer (timeout?: number): Promise<ApiServer> {
return this.authRequest<ReqRes.GetServerRes>({ method: Method.GET, url: '/', readTimeout: timeout })
async setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
return this.http.rpcRequest({ method: 'db.put.ui', params })
}
async acknowledgeOSWelcome (version: string): Promise<Unit> {
return this.authRequest<Unit>({ method: Method.POST, url: `/welcome/${version}` })
// auth
async submitPin (params: RR.SubmitPinReq): Promise<RR.SubmitPinRes> {
return this.http.rpcRequest({ method: 'auth.pin', params })
}
async getVersionLatest (): Promise<ReqRes.GetVersionLatestRes> {
return this.authRequest<ReqRes.GetVersionLatestRes>({ method: Method.GET, url: '/versionLatest' }, { version: '' })
async submitPassword (params: RR.SubmitPasswordReq): Promise<RR.SubmitPasswordRes> {
return this.http.rpcRequest({ method: 'auth.password', params })
}
async getServerMetrics (): Promise<ReqRes.GetServerMetricsRes> {
return this.authRequest<ReqRes.GetServerMetricsRes>({ method: Method.GET, url: `/metrics` })
async logout (params: RR.LogoutReq): Promise<RR.LogoutRes> {
return this.http.rpcRequest({ method: 'auth.logout', params })
}
async getNotifications (page: number, perPage: number): Promise<S9Notification[]> {
const params: ReqRes.GetNotificationsReq = {
page: String(page),
perPage: String(perPage),
}
return this.authRequest<ReqRes.GetNotificationsRes>({ method: Method.GET, url: `/notifications`, params })
// server
async getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
return this.http.rpcRequest( { method: 'server.logs', params })
}
async deleteNotification (id: string): Promise<Unit> {
return this.authRequest({ method: Method.DELETE, url: `/notifications/${id}` })
async getServerMetrics (params: RR.GetServerMetricsReq): Promise<RR.GetServerMetricsRes> {
return this.http.rpcRequest({ method: 'server.metrics', params })
}
async getExternalDisks (): Promise<DiskInfo[]> {
return this.authRequest<ReqRes.GetExternalDisksRes>({ method: Method.GET, url: `/disks` })
async updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
return this.http.rpcRequest({ method: 'server.update', params })
}
// TODO: EJECT-DISKS
async ejectExternalDisk (logicalName: string): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: `/disks/eject`, data: { logicalName } })
async restartServer (params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
return this.http.rpcRequest({ method: 'server.restart', params })
}
async updateAgent (version: string): Promise<Unit> {
const data: ReqRes.PostUpdateAgentReq = {
version: `=${version}`,
}
return this.authRequest({ method: Method.POST, url: '/update', data })
async shutdownServer (params: RR.ShutdownServerReq): Promise<RR.ShutdownServerRes> {
return this.http.rpcRequest({ method: 'server.shutdown', params })
}
async getAvailableAppVersionSpecificInfo (appId: string, versionSpec: string): Promise<AppAvailableVersionSpecificInfo> {
return this
.authRequest<Replace<ReqRes.GetAppAvailableVersionInfoRes, 'versionViewing', 'version'>>({ method: Method.GET, url: `/apps/${appId}/store/${versionSpec}` })
.then(res => ({ ...res, versionViewing: res.version }))
.then(res => {
delete res['version']
return res
})
// network
async refreshLan (params: RR.RefreshLanReq): Promise<RR.RefreshLanRes> {
return this.http.rpcRequest({ method: 'network.lan.refresh', params })
}
async getAvailableApps (): Promise<AppAvailablePreview[]> {
const res = await this.authRequest<ReqRes.GetAppsAvailableRes>({ method: Method.GET, url: '/apps/store' })
return res.map(a => {
const latestVersionTimestamp = new Date(a.latestVersionTimestamp)
if (isNaN(latestVersionTimestamp as any)) throw new Error(`Invalid latestVersionTimestamp ${a.latestVersionTimestamp}`)
return { ...a, latestVersionTimestamp }
})
// registry
async setRegistryRaw (params: RR.SetRegistryReq): Promise<RR.SetRegistryRes> {
return this.http.rpcRequest({ method: 'registry.set', params })
}
async getAvailableApp (appId: string): Promise<AppAvailableFull> {
return this.authRequest<ReqRes.GetAppAvailableRes>({ method: Method.GET, url: `/apps/${appId}/store` })
.then(res => {
return {
...res,
versionViewing: res.versionLatest,
}
})
// notification
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes> {
return this.http.rpcRequest({ method: 'notifications.list', params })
}
async getInstalledApp (appId: string): Promise<AppInstalledFull> {
return this.authRequest<ReqRes.GetAppInstalledRes>({ method: Method.GET, url: `/apps/${appId}/installed` })
.then(app => {
return {
...app,
hasFetchedFull: true,
hasUI: this.config.hasUI(app),
launchable: this.config.isLaunchable(app),
}
})
async deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes> {
return this.http.rpcRequest({ method: 'notifications.delete', params })
}
async getInstalledApps (): Promise<AppInstalledPreview[]> {
return this.authRequest<ReqRes.GetAppsInstalledRes>({ method: Method.GET, url: `/apps/installed` })
.then(apps => {
return apps.map(app => {
return {
...app,
hasUI: this.config.hasUI(app),
launchable: this.config.isLaunchable(app),
}
})
})
// wifi
async addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
return this.http.rpcRequest({ method: 'wifi.add', params })
}
async getAppConfig (appId: string): Promise<ReqRes.GetAppConfigRes> {
return this.authRequest<ReqRes.GetAppConfigRes>({ method: Method.GET, url: `/apps/${appId}/config` })
async connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
return this.http.rpcRequest({ method: 'wifi.connect', params })
}
async getAppLogs (appId: string, params: ReqRes.GetAppLogsReq = { }): Promise<string[]> {
return this.authRequest<ReqRes.GetAppLogsRes>({ method: Method.GET, url: `/apps/${appId}/logs`, params: params as any })
async deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
return this.http.rpcRequest({ method: 'wifi.delete', params })
}
async getServerLogs (): Promise<string[]> {
return this.authRequest<ReqRes.GetServerLogsRes>({ method: Method.GET, url: `/logs` })
// ssh
async getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
return this.http.rpcRequest({ method: 'ssh.get', params })
}
async getAppMetrics (appId: string): Promise<AppMetrics> {
return this.authRequest<ReqRes.GetAppMetricsRes | string>({ method: Method.GET, url: `/apps/${appId}/metrics` })
.then(parseMetricsPermissive)
async addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
return this.http.rpcRequest({ method: 'ssh.add', params })
}
async installApp (appId: string, version: string, dryRun: boolean = false): Promise<AppInstalledFull & { breakages: DependentBreakage[] }> {
const data: ReqRes.PostInstallAppReq = {
version,
}
return this.authRequest<ReqRes.PostInstallAppRes>({ method: Method.POST, url: `/apps/${appId}/install${dryRunParam(dryRun, true)}`, data })
.then(app => {
return {
...app,
hasFetchedFull: false,
hasUI: this.config.hasUI(app),
launchable: this.config.isLaunchable(app),
}
})
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
return this.http.rpcRequest({ method: 'ssh.delete', params })
}
async uninstallApp (appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/uninstall${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
// backup
async createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
return this.http.rpcRequest({ method: 'backup.create', params })
}
async startApp (appId: string): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/start`, readTimeout: 60000 })
.then(() => this.appModel.update({ id: appId, status: AppStatus.RUNNING }))
.then(() => ({ }))
async restoreBackupRaw (params: RR.RestoreBackupReq): Promise<RR.RestoreBackupRes> {
return this.http.rpcRequest({ method: 'backup.restore', params })
}
async stopApp (appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
const res = await this.authRequest<{ breakages: DependentBreakage[] }>({ method: Method.POST, url: `/apps/${appId}/stop${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
if (!dryRun) this.appModel.update({ id: appId, status: AppStatus.STOPPING }, modulateTime(new Date(), 5, 'seconds'))
return res
// disk
getDisks (params: RR.GetDisksReq): Promise<RR.GetDisksRes> {
return this.http.rpcRequest({ method: 'disk.list', params })
}
async restartApp (appId: string): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/restart`, readTimeout: 60000 })
.then(() => ({ } as any))
ejectDisk (params: RR.EjectDisksReq): Promise<RR.EjectDisksRes> {
return this.http.rpcRequest({ method: 'disk.eject', params })
}
async createAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit> {
const data: ReqRes.PostAppBackupCreateReq = {
password: password || undefined,
logicalname,
}
return this.authRequest<ReqRes.PostAppBackupCreateRes>({ method: Method.POST, url: `/apps/${appId}/backup`, data, readTimeout: 60000 })
.then(() => this.appModel.update({ id: appId, status: AppStatus.CREATING_BACKUP }))
.then(() => ({ }))
// package
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes<any>['data']> {
return this.http.rpcRequest({ method: 'package.properties', params })
.then(parsePropertiesPermissive)
}
async stopAppBackup (appId: string): Promise<Unit> {
return this.authRequest<ReqRes.PostAppBackupStopRes>({ method: Method.POST, url: `/apps/${appId}/backup/stop`, readTimeout: 60000 })
.then(() => this.appModel.update({ id: appId, status: AppStatus.STOPPED }))
.then(() => ({ }))
async getPackageLogs (params: RR.GetPackageLogsReq): Promise<RR.GetPackageLogsRes> {
return this.http.rpcRequest( { method: 'package.logs', params })
}
async restoreAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit> {
const data: ReqRes.PostAppBackupRestoreReq = {
password: password || undefined,
logicalname,
}
return this.authRequest<ReqRes.PostAppBackupRestoreRes>({ method: Method.POST, url: `/apps/${appId}/backup/restore`, data, readTimeout: 60000 })
.then(() => this.appModel.update({ id: appId, status: AppStatus.RESTORING_BACKUP }))
.then(() => ({ }))
async installPackageRaw (params: RR.InstallPackageReq): Promise<RR.InstallPackageRes> {
return this.http.rpcRequest({ method: 'package.install', params })
}
async patchAppConfig (app: AppInstalledPreview, config: object, dryRun = false): Promise<{ breakages: DependentBreakage[] }> {
const data: ReqRes.PatchAppConfigReq = {
config,
}
return this.authRequest({ method: Method.PATCH, url: `/apps/${app.id}/config${dryRunParam(dryRun, true)}`, data, readTimeout: 60000 })
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> {
return this.http.rpcRequest({ method: 'package.update.dry', params })
}
async postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise<{ config: object, breakages: DependentBreakage[] }> {
return this.authRequest({ method: Method.POST, url: `/apps/${dependencyId}/autoconfig/${dependentId}${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
async getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.get', params })
}
async patchServerConfig (attr: string, value: any): Promise<Unit> {
const data: ReqRes.PatchServerConfigReq = {
value,
}
return this.authRequest({ method: Method.PATCH, url: `/${attr}`, data, readTimeout: 60000 })
.then(() => this.serverModel.update({ [attr]: value }))
.then(() => ({ }))
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.set.dry', params })
}
async wipeAppData (app: AppInstalledPreview): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: `/apps/${app.id}/wipe`, readTimeout: 60000 }).then((res) => {
this.appModel.update({ id: app.id, status: AppStatus.NEEDS_CONFIG })
return res
})
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.set', params })
}
async toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/lan/${toggle}` })
async restorePackageRaw (params: RR.RestorePackageReq): Promise<RR.RestorePackageRes> {
return this.http.rpcRequest({ method: 'package.restore', params })
}
async addSSHKey (sshKey: string): Promise<Unit> {
const data: ReqRes.PostAddSSHKeyReq = {
sshKey,
}
const fingerprint = await this.authRequest<ReqRes.PostAddSSHKeyRes>({ method: Method.POST, url: `/sshKeys`, data })
this.serverModel.update({ ssh: [...this.serverModel.peek().ssh, fingerprint] })
return { }
async executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes> {
return this.http.rpcRequest({ method: 'package.action', params })
}
async addWifi (ssid: string, password: string, country: string, connect: boolean): Promise<Unit> {
const data: ReqRes.PostAddWifiReq = {
ssid,
password,
country,
skipConnect: !connect,
}
return this.authRequest({ method: Method.POST, url: `/wifi`, data })
async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
return this.http.rpcRequest({ method: 'package.start', params })
}
async connectWifi (ssid: string): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: encodeURI(`/wifi/${ssid}`) })
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop.dry', params })
}
async deleteWifi (ssid: string): Promise<Unit> {
return this.authRequest({ method: Method.DELETE, url: encodeURI(`/wifi/${ssid}`) })
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop', params })
}
async deleteSSHKey (fingerprint: SSHFingerprint): Promise<Unit> {
await this.authRequest({ method: Method.DELETE, url: `/sshKeys/${fingerprint.hash}` })
const ssh = this.serverModel.peek().ssh
this.serverModel.update({ ssh: ssh.filter(s => s !== fingerprint) })
return { }
async dryRemovePackage (params: RR.DryRemovePackageReq): Promise<RR.DryRemovePackageRes> {
return this.http.rpcRequest({ method: 'package.remove.dry', params })
}
async restartServer (): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: '/restart', readTimeout: 60000 })
async removePackageRaw (params: RR.RemovePackageReq): Promise<RR.RemovePackageRes> {
return this.http.rpcRequest({ method: 'package.remove', params })
}
async shutdownServer (): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: '/shutdown', readTimeout: 60000 })
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes> {
return this.http.rpcRequest({ method: 'package.dependency.configure.dry', params })
}
async serviceAction (appId: string, s: ServiceAction): Promise<ReqRes.ServiceActionResponse> {
const data: ReqRes.ServiceActionRequest = {
jsonrpc: '2.0',
id: uuid.v4(),
method: s.id,
}
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/actions`, data, readTimeout: 300000 })
// marketplace
async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> {
return this.http.rpcRequest({ method: 'marketplace.data', params })
}
async refreshLAN (): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: '/network/lan/reset' })
async getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> {
return this.http.rpcRequest({ method: 'marketplace.eos', params })
}
async checkV1Status (): Promise<V1Status> {
return this.http.request({ method: Method.GET, url: 'https://registry.start9labs.com/sys/status' })
async getAvailableList (params: RR.GetAvailableListReq): Promise<RR.GetAvailableListRes> {
return this.http.rpcRequest({ method: 'marketplace.available.list', params })
}
private async authRequest<T> (opts: HttpOptions, overrides: Partial<{ version: string }> = { }): Promise<T> {
if (!this.authenticatedRequestsEnabled) throw new Error(`Authenticated requests are not enabled. Do you need to login?`)
opts.withCredentials = true
return this.http.serverRequest<T>(opts, overrides).catch((e: HttpError) => {
console.log(`Got a server error!`, e)
if (isUnauthorized(e)) this.received401()
throw e
})
}
}
type HttpError = HttpErrorResponse & { error: { code: string, message: string } }
const dryRunParam = (dryRun: boolean, first: boolean) => {
if (!dryRun) return ''
return first ? `?dryrun` : `&dryrun`
}
function catchHttpStatusError (error: HttpErrorResponse): Observable<true> {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
return throwError('Not Connected')
} else {
return of(true)
async getAvailableShow (params: RR.GetAvailableShowReq): Promise<RR.GetAvailableShowRes> {
return this.http.rpcRequest({ method: 'marketplace.available', params })
}
}

View File

@@ -1,8 +1,5 @@
# Size Limit [![Cult Of Martians][cult-img]][cult]
<img src="https://ai.github.io/size-limit/logo.svg" align="right"
alt="Size Limit logo by Anton Lovchikov" width="120" height="178">
Size Limit is a performance budget tool for JavaScript. It checks every commit
on CI, calculates the real cost of your JS for end-users and throws an error
if the cost exceeds the limit.
@@ -159,192 +156,6 @@ interactive elements, using React/Vue/Svelte lib or vanilla JS.
</details>
### Big Libraries
JS libraries > 10 KB in size.
This preset includes headless Chrome, and will measure your libs execution
time. You likely dont need this overhead for a small 2 KB lib, but for larger
ones the execution time is a more accurate and understandable metric that
the size in bytes. Library like [React] is a good example for this preset.
<details><summary><b>Show instructions</b></summary>
1. Install preset:
```sh
$ npm install --save-dev size-limit @size-limit/preset-big-lib
```
2. Add the `size-limit` section and the `size` script to your `package.json`:
```diff
+ "size-limit": [
+ {
+ "path": "dist/react.production-*.js"
+ }
+ ],
"scripts": {
"build": "webpack ./scripts/rollup/build.js",
+ "size": "npm run build && size-limit",
"test": "jest && eslint ."
}
```
3. If you use ES modules you can test the size after tree-shaking with `import`
option:
```diff
"size-limit": [
{
"path": "dist/react.production-*.js",
+ "import": "{ createComponent }"
}
],
```
4. Heres how you can get the size for your current project:
```sh
$ npm run size
Package size: 30.08 KB with all dependencies, minified and gzipped
Loading time: 602 ms on slow 3G
Running time: 214 ms on Snapdragon 410
Total time: 815 ms
```
5. Now, lets set the limit. Add 25% to the current total time and use that
as the limit in your `package.json`:
```diff
"size-limit": [
{
+ "limit": "1 s",
"path": "dist/react.production-*.js"
}
],
```
6. Add a `size` script to your test suite:
```diff
"scripts": {
"build": "rollup ./scripts/rollup/build.js",
"size": "npm run build && size-limit",
- "test": "jest && eslint ."
+ "test": "jest && eslint . && npm run size"
}
```
7. If you dont have a continuous integration service running, dont forget
to add one — start with [Travis CI].
8. Add the library size to docs, it will help users to choose your project:
```diff
# Project Name
Short project description
* **Fast.** 10% faster than competitor.
+ * **Small.** 15 KB (minified and gzipped).
+ [Size Limit](https://github.com/ai/size-limit) controls the size.
```
</details>
### Small Libraries
JS libraries < 10 KB in size.
This preset will only measure the size, without the execution time, so its
suitable for small libraries. If your library is larger, you likely want
the Big Libraries preset above. [Nano ID] or [Storeon] are good examples
for this preset.
<details><summary><b>Show instructions</b></summary>
1. First, install `size-limit`:
```sh
$ npm install --save-dev size-limit @size-limit/preset-small-lib
```
2. Add the `size-limit` section and the `size` script to your `package.json`:
```diff
+ "size-limit": [
+ {
+ "path": "index.js"
+ }
+ ],
"scripts": {
+ "size": "size-limit",
"test": "jest && eslint ."
}
```
3. Heres how you can get the size for your current project:
```sh
$ npm run size
Package size: 177 B with all dependencies, minified and gzipped
```
4. If your project size starts to look bloated, run `--why` for analysis:
```sh
$ npm run size -- --why
```
5. Now, lets set the limit. Determine the current size of your library,
add just a little bit (a kilobyte, maybe) and use that as the limit
in your `package.json`:
```diff
"size-limit": [
{
+ "limit": "9 KB",
"path": "index.js"
}
],
```
6. Add the `size` script to your test suite:
```diff
"scripts": {
"size": "size-limit",
- "test": "jest && eslint ."
+ "test": "jest && eslint . && npm run size"
}
```
7. If you dont have a continuous integration service running, dont forget
to add one — start with [Travis CI].
8. Add the library size to docs, it will help users to choose your project:
```diff
# Project Name
Short project description
* **Fast.** 10% faster than competitor.
+ * **Small.** 500 bytes (minified and gzipped). No dependencies.
+ [Size Limit](https://github.com/ai/size-limit) controls the size.
```
</details>
[Travis CI]: https://github.com/dwyl/learn-travis
[Storeon]: https://github.com/ai/storeon/
[Nano ID]: https://github.com/ai/nanoid/
[React]: https://github.com/facebook/react/
## Reports
Size Limit has a [GitHub action] that comments and rejects pull requests based
@@ -371,99 +182,6 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
```
## Config
Size Limits supports three ways to define config.
1. `size-limit` section in `package.json`:
```json
"size-limit": [
{
"path": "index.js",
"import": "{ createStore }",
"limit": "500 ms"
}
]
```
2. or a separate `.size-limit.json` config file:
```js
[
{
"path": "index.js",
"import": "{ createStore }",
"limit": "500 ms"
}
]
```
3. or a more flexible `.size-limit.js` config file:
```js
module.exports = [
{
path: "index.js",
import: "{ createStore }",
limit: "500 ms"
}
]
```
Each section in the config can have these options:
* **path**: relative paths to files. The only mandatory option.
It could be a path `"index.js"`, a [pattern] `"dist/app-*.js"`
or an array `["index.js", "dist/app-*.js", "!dist/app-exclude.js"]`.
* **import**: partial import to test tree-shaking. It could be `"{ lib }"`
to test `import { lib } from 'lib'` or `{ "a.js": "{ a }", "b.js": "{ b }" }`
to test multiple files.
* **limit**: size or time limit for files from the `path` option. It should be
a string with a number and unit, separated by a space.
Format: `100 B`, `10 KB`, `500 ms`, `1 s`.
* **name**: the name of the current section. It will only be useful
if you have multiple sections.
* **entry**: when using a custom webpack config, a webpack entry could be given.
It could be a string or an array of strings.
By default, the total size of all entry points will be checked.
* **webpack**: with `false` it will disable webpack.
* **running**: with `false` it will disable calculating running time.
* **gzip**: with `false` it will disable gzip compression.
* **brotli**: with `true` it will use brotli compression and disable gzip compression.
* **config**: a path to a custom webpack config.
* **ignore**: an array of files and dependencies to exclude from
the project size calculation.
If you use Size Limit to track the size of CSS files, make sure to set
`webpack: false`. Otherwise, you will get wrong numbers, because webpack
inserts `style-loader` runtime (≈2 KB) into the bundle.
[pattern]: https://github.com/sindresorhus/globby#globbing-patterns
## Plugins and Presets
Plugins:
* `@size-limit/file` checks the size of files with Gzip, Brotli
or without compression.
* `@size-limit/webpack` adds your library to empty webpack project
and prepares bundle file for `file` plugin.
* `@size-limit/time` uses headless Chrome to track time to execute JS.
* `@size-limit/dual-publish` compiles files to ES modules with [`dual-publish`]
to check size after tree-shaking.
Plugin presets:
* `@size-limit/preset-app` contains `file` and `time` plugins.
* `@size-limit/preset-big-lib` contains `webpack`, `file`, and `time` plugins.
* `@size-limit/preset-small-lib` contains `webpack` and `file` plugins.
[`dual-publish`]: https://github.com/ai/dual-publish
## JS API
```js

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff