mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
feat: move all frontend projects under the same Angular workspace (#1141)
* feat: move all frontend projects under the same Angular workspace * Refactor/angular workspace (#1154) * update frontend build steps Co-authored-by: waterplea <alexander@inkin.ru> Co-authored-by: Matt Hill <matthewonthemoon@gmail.com> Co-authored-by: Lucy Cifferello <12953208+elvece@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./pages/home/home.module').then( m => m.HomePageModule)
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
loadChildren: () => import('./pages/logs/logs.module').then( m => m.LogsPageModule)
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
scrollPositionRestoration: 'enabled',
|
||||
preloadingStrategy: PreloadAllModules,
|
||||
useHash: true,
|
||||
})
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
@@ -0,0 +1,3 @@
|
||||
<ion-app>
|
||||
<ion-router-outlet></ion-router-outlet>
|
||||
</ion-app>
|
||||
10
frontend/projects/diagnostic-ui/src/app/app.component.ts
Normal file
10
frontend/projects/diagnostic-ui/src/app/app.component.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: ['app.component.scss'],
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor() {}
|
||||
}
|
||||
45
frontend/projects/diagnostic-ui/src/app/app.module.ts
Normal file
45
frontend/projects/diagnostic-ui/src/app/app.module.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ErrorHandler, NgModule } from '@angular/core'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { RouteReuseStrategy } from '@angular/router'
|
||||
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'
|
||||
import { AppComponent } from './app.component'
|
||||
import { AppRoutingModule } from './app-routing.module'
|
||||
import { HttpClientModule } from '@angular/common/http'
|
||||
import { ApiService } from './services/api/api.service'
|
||||
import { MockApiService } from './services/api/mock-api.service'
|
||||
import { LiveApiService } from './services/api/live-api.service'
|
||||
import { HttpService } from './services/http.service'
|
||||
import { GlobalErrorHandler } from './services/global-error-handler.service'
|
||||
import { WorkspaceConfig } from '@shared'
|
||||
|
||||
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
entryComponents: [],
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
BrowserModule,
|
||||
IonicModule.forRoot({
|
||||
mode: 'md',
|
||||
}),
|
||||
AppRoutingModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
{
|
||||
provide: ApiService,
|
||||
useFactory: (http: HttpService) => {
|
||||
if (useMocks) {
|
||||
return new MockApiService()
|
||||
} else {
|
||||
return new LiveApiService(http)
|
||||
}
|
||||
},
|
||||
deps: [HttpService],
|
||||
},
|
||||
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { HomePage } from './home.page'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomePage,
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class HomePageRoutingModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { HomePage } from './home.page'
|
||||
import { HomePageRoutingModule } from './home-routing.module'
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
HomePageRoutingModule
|
||||
],
|
||||
declarations: [HomePage]
|
||||
})
|
||||
export class HomePageModule {}
|
||||
@@ -0,0 +1,34 @@
|
||||
<ion-content>
|
||||
<div style="padding: 48px">
|
||||
<ng-container *ngIf="!restarted; else refresh">
|
||||
<h1 class="ion-text-center" style="padding-bottom: 36px; font-size: calc(2vw + 14px);">EmbassyOS - Diagnostic Mode</h1>
|
||||
<h2 style="padding-bottom: 16px; font-size: calc(1vw + 14px); font-weight: bold;">EmbassyOS launch error:</h2>
|
||||
<div class="code-block">
|
||||
<code><ion-text color="warning">{{ error.problem }}</ion-text></code>
|
||||
</div>
|
||||
<ion-button routerLink="logs">
|
||||
View Logs
|
||||
</ion-button>
|
||||
<h2 style="padding: 32px 0 16px 0; font-size: calc(1vw + 12px); font-weight: bold;">Possible solution:</h2>
|
||||
<div class="code-block">
|
||||
<code><ion-text color="success">{{ error.solution }}</ion-text></code>
|
||||
</div>
|
||||
<ion-button (click)="restart()">
|
||||
Restart Embassy
|
||||
</ion-button>
|
||||
<div *ngIf="error.code === 15 || error.code === 25" class="ion-padding-top">
|
||||
<ion-button *ngIf="error.code === 15" (click)="forgetDrive()">
|
||||
{{ error.code === 15 ? 'Setup Current Drive' : 'Enter Recovery Mode' }}
|
||||
</ion-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #refresh>
|
||||
<h1 class="ion-text-center" style="padding-bottom: 36px; font-size: calc(2vw + 12px);">Embassy is restarting</h1>
|
||||
<h2 style="padding-bottom: 16px; font-size: calc(1vw + 12px);">Wait for Embassy restart, then refresh this page or click REFRESH below.</h2>
|
||||
<ion-button (click)="refreshPage()">
|
||||
Refresh
|
||||
</ion-button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,5 @@
|
||||
.code-block {
|
||||
background-color: rgb(69, 69, 69);
|
||||
padding: 12px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { LoadingController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: 'home.page.html',
|
||||
styleUrls: ['home.page.scss'],
|
||||
})
|
||||
export class HomePage {
|
||||
error: {
|
||||
code: number
|
||||
problem: string
|
||||
solution: string
|
||||
} = { } as any
|
||||
solutions: string[] = []
|
||||
restarted = false
|
||||
|
||||
constructor (
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly api: ApiService,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
try {
|
||||
const error = await this.api.getError()
|
||||
// incorrect drive
|
||||
if (error.code === 15) {
|
||||
this.error = {
|
||||
code: 15,
|
||||
problem: 'Unknown storage drive detected',
|
||||
solution: 'To use a different storage drive, replace the current one and click RESTART EMBASSY below. To use the current storage drive, click USE CURRENT DRIVE below, then follow instructions. No data will be erased during this process.'
|
||||
}
|
||||
// no drive
|
||||
} else if (error.code === 20) {
|
||||
this.error = {
|
||||
code: 20,
|
||||
problem: 'Storage drive not found',
|
||||
solution: 'Insert your EmbassyOS storage drive and click RESTART EMBASSY below.'
|
||||
}
|
||||
// drive corrupted
|
||||
} else if (error.code === 25) {
|
||||
this.error = {
|
||||
code: 25,
|
||||
problem: 'Storage drive corrupted. This could be the result of data corruption or a physical damage.',
|
||||
solution: 'It may or may not be possible to re-use this drive by reformatting and recovering from backup. To enter recovery mode, click ENTER RECOVERY MODE below, then follow instructions. No data will be erased during this step.'
|
||||
}
|
||||
} else {
|
||||
this.error = {
|
||||
code: error.code,
|
||||
problem: error.message,
|
||||
solution: 'Please conact support.'
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async restart (): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.restart()
|
||||
this.restarted = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async forgetDrive (): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.forgetDrive()
|
||||
await this.api.restart()
|
||||
this.restarted = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
refreshPage (): void {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { LogsPage } from './logs.page'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LogsPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
],
|
||||
declarations: [LogsPage],
|
||||
})
|
||||
export class LogsPageModule { }
|
||||
@@ -0,0 +1,47 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button defaultHref="/"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Logs</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content
|
||||
[scrollEvents]="true"
|
||||
(ionScroll)="scrollEvent()"
|
||||
style="height: 100%;"
|
||||
id="ion-content"
|
||||
class="ion-padding"
|
||||
>
|
||||
<ion-infinite-scroll id="scroller" *ngIf="!loading && needInfinite" position="top" threshold="0" (ionInfinite)="loadData($event)">
|
||||
<ion-infinite-scroll-content loadingSpinner="lines"></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
|
||||
<div id="container">
|
||||
<div id="template" style="white-space: pre-line;"></div>
|
||||
</div>
|
||||
<div id="button-div" *ngIf="!loading" style="width: 100%; text-align: center;">
|
||||
<ion-button *ngIf="!loadingMore" (click)="loadMore()" strong color="dark">
|
||||
Load More
|
||||
<ion-icon slot="end" name="refresh"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-spinner *ngIf="loadingMore" name="lines" color="warning"></ion-spinner>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="!loading"
|
||||
[ngStyle]="{
|
||||
'position': 'fixed',
|
||||
'bottom': '50px',
|
||||
'right': isOnBottom ? '-52px' : '30px',
|
||||
'border-radius': '100%',
|
||||
'transition': 'right 0.4s ease-out'
|
||||
}"
|
||||
>
|
||||
<ion-button style="width: 50px; height: 50px; --padding-start: 0px; --padding-end: 0px; --border-radius: 100%;" color="dark" (click)="scrollToBottom()" strong>
|
||||
<ion-icon name="chevron-down"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
</ion-content>
|
||||
113
frontend/projects/diagnostic-ui/src/app/pages/logs/logs.page.ts
Normal file
113
frontend/projects/diagnostic-ui/src/app/pages/logs/logs.page.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { IonContent } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
var Convert = require('ansi-to-html')
|
||||
var convert = new Convert({
|
||||
bg: 'transparent',
|
||||
})
|
||||
|
||||
@Component({
|
||||
selector: 'logs',
|
||||
templateUrl: './logs.page.html',
|
||||
styleUrls: ['./logs.page.scss'],
|
||||
})
|
||||
export class LogsPage {
|
||||
@ViewChild(IonContent) private content: IonContent
|
||||
loading = true
|
||||
loadingMore = false
|
||||
logs: string
|
||||
needInfinite = true
|
||||
startCursor: string
|
||||
endCursor: string
|
||||
limit = 200
|
||||
scrollToBottomButton = false
|
||||
isOnBottom = true
|
||||
|
||||
constructor (
|
||||
private readonly api: ApiService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.getLogs()
|
||||
}
|
||||
|
||||
async getLogs () {
|
||||
try {
|
||||
// get logs
|
||||
const logs = await this.fetch()
|
||||
if (!logs.length) return
|
||||
|
||||
const container = document.getElementById('container')
|
||||
const beforeContainerHeight = container.scrollHeight
|
||||
const newLogs = document.getElementById('template').cloneNode(true) as HTMLElement
|
||||
newLogs.innerHTML = logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') + (logs.length ? '\n' : '')
|
||||
|
||||
container.prepend(newLogs)
|
||||
const afterContainerHeight = container.scrollHeight
|
||||
|
||||
// scroll down
|
||||
scrollBy(0, afterContainerHeight - beforeContainerHeight)
|
||||
this.content.scrollToPoint(0, afterContainerHeight - beforeContainerHeight)
|
||||
|
||||
if (logs.length < this.limit) {
|
||||
this.needInfinite = false
|
||||
}
|
||||
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
async fetch (isBefore: boolean = true) {
|
||||
try {
|
||||
const cursor = isBefore ? this.startCursor : this.endCursor
|
||||
|
||||
const logsRes = await this.api.getLogs({
|
||||
cursor,
|
||||
before_flag: !!cursor ? isBefore : undefined,
|
||||
limit: this.limit,
|
||||
})
|
||||
|
||||
if ((isBefore || this.startCursor) && logsRes['start-cursor']) {
|
||||
this.startCursor = logsRes['start-cursor']
|
||||
}
|
||||
|
||||
if ((!isBefore || !this.endCursor) && logsRes['end-cursor']) {
|
||||
this.endCursor = logsRes['end-cursor']
|
||||
}
|
||||
this.loading = false
|
||||
|
||||
return logsRes.entries
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async loadMore () {
|
||||
try {
|
||||
this.loadingMore = true
|
||||
const logs = await this.fetch(false)
|
||||
if (!logs.length) return this.loadingMore = false
|
||||
|
||||
const container = document.getElementById('container')
|
||||
const newLogs = document.getElementById('template').cloneNode(true) as HTMLElement
|
||||
newLogs.innerHTML = logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') + (logs.length ? '\n' : '')
|
||||
container.append(newLogs)
|
||||
this.loadingMore = false
|
||||
this.scrollEvent()
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
scrollEvent () {
|
||||
const buttonDiv = document.getElementById('button-div')
|
||||
this.isOnBottom = buttonDiv.getBoundingClientRect().top < window.innerHeight
|
||||
}
|
||||
|
||||
scrollToBottom () {
|
||||
this.content.scrollToBottom(500)
|
||||
}
|
||||
|
||||
async loadData (e: any): Promise<void> {
|
||||
await this.getLogs()
|
||||
e.target.complete()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
export abstract class ApiService {
|
||||
abstract getError (): Promise<GetErrorRes>
|
||||
abstract restart (): Promise<void>
|
||||
abstract forgetDrive (): Promise<void>
|
||||
abstract getLogs (params: GetLogsReq): Promise<GetLogsRes>
|
||||
}
|
||||
|
||||
export interface GetErrorRes {
|
||||
code: number,
|
||||
message: string,
|
||||
data: { details: string }
|
||||
}
|
||||
|
||||
export type GetLogsReq = { cursor?: string, before_flag?: boolean, limit?: number }
|
||||
export type GetLogsRes = LogsRes
|
||||
|
||||
export type LogsRes = { entries: Log[], 'start-cursor'?: string, 'end-cursor'?: string }
|
||||
|
||||
export interface Log {
|
||||
timestamp: string
|
||||
message: string
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Injectable } from "@angular/core"
|
||||
import { HttpService } from "../http.service"
|
||||
import { ApiService, GetErrorRes, GetLogsReq, GetLogsRes } from "./api.service"
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
|
||||
constructor (
|
||||
private readonly http: HttpService,
|
||||
) { super() }
|
||||
|
||||
getError (): Promise<GetErrorRes> {
|
||||
return this.http.rpcRequest<GetErrorRes>({
|
||||
method: 'diagnostic.error',
|
||||
params: { },
|
||||
})
|
||||
}
|
||||
|
||||
restart (): Promise<void> {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'diagnostic.restart',
|
||||
params: { },
|
||||
})
|
||||
}
|
||||
|
||||
forgetDrive (): Promise<void> {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'diagnostic.forget-disk',
|
||||
params: { },
|
||||
})
|
||||
}
|
||||
|
||||
getLogs (params: GetLogsReq): Promise<GetLogsRes> {
|
||||
return this.http.rpcRequest<GetLogsRes>({
|
||||
method: 'diagnostic.logs',
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Injectable } from "@angular/core"
|
||||
import { pauseFor } from "../../util/misc.util"
|
||||
import { ApiService, GetErrorRes, GetLogsReq, GetLogsRes, Log } from "./api.service"
|
||||
|
||||
@Injectable()
|
||||
export class MockApiService extends ApiService {
|
||||
|
||||
constructor () { super() }
|
||||
|
||||
async getError (): Promise<GetErrorRes> {
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
code: 15,
|
||||
message: 'Unknown Embassy',
|
||||
data: { details: 'Some details about the error here' }
|
||||
}
|
||||
}
|
||||
|
||||
async restart (): Promise<void> {
|
||||
await pauseFor(1000)
|
||||
return null
|
||||
}
|
||||
|
||||
async forgetDrive (): Promise<void> {
|
||||
await pauseFor(1000)
|
||||
return null
|
||||
}
|
||||
|
||||
async getLogs (params: GetLogsReq): Promise<GetLogsRes> {
|
||||
await pauseFor(1000)
|
||||
let entries: Log[]
|
||||
if (Math.random() < .2) {
|
||||
entries = packageLogs
|
||||
} else {
|
||||
const arrLength = params.limit ? Math.ceil(params.limit / packageLogs.length) : 10
|
||||
entries = new Array(arrLength).fill(packageLogs).reduce((acc, val) => acc.concat(val), [])
|
||||
}
|
||||
return {
|
||||
entries,
|
||||
'start-cursor': 'startCursor',
|
||||
'end-cursor': 'endCursor',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const packageLogs = [
|
||||
{
|
||||
timestamp: '2019-12-26T14:20:30.872Z',
|
||||
message: '****** START *****',
|
||||
},
|
||||
{
|
||||
timestamp: '2019-12-26T14:21:30.872Z',
|
||||
message: 'ServerLogs ServerLogs ServerLogs ServerLogs ServerLogs',
|
||||
},
|
||||
{
|
||||
timestamp: '2019-12-26T14:22:30.872Z',
|
||||
message: '****** FINISH *****',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ErrorHandler, Injectable } from '@angular/core'
|
||||
|
||||
@Injectable()
|
||||
export class GlobalErrorHandler implements ErrorHandler {
|
||||
|
||||
handleError (error: any): void {
|
||||
const chunkFailedMessage = /Loading chunk [\d]+ failed/
|
||||
|
||||
if (chunkFailedMessage.test(error.message)) {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class HttpService {
|
||||
|
||||
constructor (
|
||||
private readonly http: HttpClient,
|
||||
) { }
|
||||
|
||||
async rpcRequest<T> (options: RPCOptions): Promise<T> {
|
||||
const res = await this.httpRequest<RPCResponse<T>>(options)
|
||||
if (isRpcError(res)) throw new RpcError(res.error)
|
||||
if (isRpcSuccess(res)) return res.result
|
||||
}
|
||||
|
||||
async httpRequest<T> (body: RPCOptions): Promise<T> {
|
||||
const url = `${window.location.protocol}//${window.location.hostname}:${window.location.port}/rpc/v1`
|
||||
return this.http.post(url, body)
|
||||
.toPromise().then(a => a as T)
|
||||
.catch(e => { throw new HttpError(e) })
|
||||
}
|
||||
}
|
||||
|
||||
function RpcError (e: RPCError['error']): void {
|
||||
const { code, message, data } = e
|
||||
|
||||
this.code = code
|
||||
this.message = message
|
||||
|
||||
if (typeof data === 'string') {
|
||||
this.details = e.data
|
||||
this.revision = null
|
||||
} else {
|
||||
this.details = data.details
|
||||
}
|
||||
}
|
||||
|
||||
function HttpError (e: HttpErrorResponse): void {
|
||||
const { status, statusText } = e
|
||||
|
||||
this.code = status
|
||||
this.message = statusText
|
||||
this.details = null
|
||||
this.revision = null
|
||||
}
|
||||
|
||||
function isRpcError<Error, Result> (arg: { error: Error } | { result: Result}): arg is { error: Error } {
|
||||
return !!(arg as any).error
|
||||
}
|
||||
|
||||
function isRpcSuccess<Error, Result> (arg: { error: Error } | { result: Result}): arg is { result: Result } {
|
||||
return !!(arg as any).result
|
||||
}
|
||||
|
||||
export interface RPCOptions {
|
||||
method: string
|
||||
params: { [param: string]: Params }
|
||||
}
|
||||
|
||||
export interface RequestError {
|
||||
code: number
|
||||
message: string
|
||||
details: string
|
||||
}
|
||||
|
||||
export type Params = string | number | boolean | object | string[] | number[]
|
||||
|
||||
interface RPCBase {
|
||||
jsonrpc: '2.0'
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface RPCRequest<T> extends RPCBase {
|
||||
method: string
|
||||
params?: T
|
||||
}
|
||||
|
||||
export interface RPCSuccess<T> extends RPCBase {
|
||||
result: T
|
||||
}
|
||||
|
||||
export interface RPCError extends RPCBase {
|
||||
error: {
|
||||
code: number,
|
||||
message: string
|
||||
data?: {
|
||||
details: string
|
||||
} | string
|
||||
}
|
||||
}
|
||||
|
||||
export type RPCResponse<T> = RPCSuccess<T> | RPCError
|
||||
|
||||
type HttpError = HttpErrorResponse & { error: { code: string, message: string } }
|
||||
@@ -0,0 +1,3 @@
|
||||
export function pauseFor (ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
Reference in New Issue
Block a user