Wizard refactor 2 (#615)

* new flow and endpoints

* functional

* prod build errors addressed

* little more cleanup

* transfer progress fixed

* tor address fix

* remove eslint cause sucks

* fix skeleton text color and wording

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>
This commit is contained in:
Drew Ansbacher
2021-10-07 16:51:33 -06:00
committed by Aiden McClelland
parent e58df7ec4a
commit ed395699b3
51 changed files with 18273 additions and 3137 deletions

View File

@@ -1,21 +0,0 @@
{
"root": true,
"ignorePatterns": ["projects/**/*"],
"overrides": [
{
"files": ["*.ts"],
"parserOptions": {
"project": ["tsconfig.json"],
"createDefaultProgram": true
},
"rules": {
"semi": [1, "never"]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@angular-eslint/template/recommended"],
"rules": {}
}
]
}

View File

@@ -16,7 +16,7 @@ setup.disk.list
model : string | null,
partitions : PartitionInfo[],
capacity : number,
embassy_os : EmbassyOsDiskInfo | null,
embassy-os : EmbassyOsDiskInfo | null,
}[]
setup.recovery.status

File diff suppressed because it is too large Load Diff

View File

@@ -29,23 +29,14 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "^12.2.1",
"@angular-eslint/builder": "^12.3.1",
"@angular-eslint/eslint-plugin": "^12.3.1",
"@angular-eslint/eslint-plugin-template": "^12.3.1",
"@angular-eslint/template-parser": "^12.3.1",
"@angular/cli": "^12.2.1",
"@angular/compiler": "^12.2.1",
"@angular/compiler-cli": "^12.2.1",
"@angular/language-service": "^12.2.1",
"@ionic/angular-toolkit": "^4.0.0",
"@types/node": "^16.9.1",
"@typescript-eslint/eslint-plugin": "^4.29.1",
"@typescript-eslint/parser": "^4.29.1",
"eslint": "^7.32.0",
"eslint-plugin-import": "^2.24.0",
"eslint-plugin-jsdoc": "^36.0.7",
"eslint-plugin-prefer-arrow": "^1.2.3",
"ts-node": "^10.2.0",
"tslint": "^6.1.3",
"typescript": "4.3.5"
}
}

View File

@@ -1,6 +1,19 @@
import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core'
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
const routes: Routes = [
{
path: 'product-key',
loadChildren: () => import('./pages/product-key/product-key.module').then( m => m.ProductKeyPageModule),
},
{
path: 'home',
loadChildren: () => import('./pages/home/home.module').then( m => m.HomePageModule),
},
{
path: 'recover',
loadChildren: () => import('./pages/recover/recover.module').then( m => m.RecoverPageModule),
},
{
path: 'embassy',
loadChildren: () => import('./pages/embassy/embassy.module').then( m => m.EmbassyPageModule),
@@ -9,23 +22,11 @@ const routes: Routes = [
path: 'loading',
loadChildren: () => import('./pages/loading/loading.module').then( m => m.LoadingPageModule),
},
{
path: 'product-key',
loadChildren: () => import('./pages/product-key/product-key.module').then( m => m.ProductKeyPageModule),
},
{
path: 'recover',
loadChildren: () => import('./pages/recover/recover.module').then( m => m.RecoverPageModule),
},
{
path: 'home',
loadChildren: () => import('./pages/home/home.module').then( m => m.HomePageModule),
},
{
path: 'success',
loadChildren: () => import('./pages/success/success.module').then( m => m.SuccessPageModule),
},
];
]
@NgModule({
imports: [
@@ -33,8 +34,8 @@ const routes: Routes = [
scrollPositionRestoration: 'enabled',
preloadingStrategy: PreloadAllModules,
useHash: true,
})
}),
],
exports: [RouterModule]
exports: [RouterModule],
})
export class AppRoutingModule { }

View File

@@ -1,5 +1,3 @@
<ion-app>
<ion-content class="has-header">
<ion-router-outlet></ion-router-outlet>
</ion-content>
<ion-router-outlet></ion-router-outlet>
</ion-app>

View File

@@ -1,18 +1,36 @@
import { Component, OnInit } from '@angular/core'
import { Component } from '@angular/core'
import { NavController } from '@ionic/angular'
import { ApiService } from './services/api/api.service'
import { ErrorToastService } from './services/error-toast.service'
import { StateService } from './services/state.service'
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent implements OnInit {
constructor(
export class AppComponent {
constructor (
private readonly apiService: ApiService,
private readonly errorToastService: ErrorToastService,
private readonly navCtrl: NavController,
) {}
private readonly stateService: StateService,
) { }
async ngOnInit() {
await this.navCtrl.navigateForward(`/product-key`)
async ngOnInit () {
try {
const status = await this.apiService.getStatus()
if (status.migrating || status['product-key']) {
this.stateService.hasProductKey = true
this.stateService.isMigrating = status.migrating
await this.navCtrl.navigateForward(`/product-key`)
} else {
this.stateService.hasProductKey = false
this.stateService.isMigrating = false
await this.navCtrl.navigateForward(`/recover`)
}
} catch (e) {
this.errorToastService.present(`${e.message}: ${e.details}`)
}
}
}

View File

@@ -16,16 +16,15 @@ const useMocks = require('../../config.json').useMocks as boolean
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
IonicModule.forRoot({
navAnimation: iosTransitionAnimation,
}),
AppRoutingModule,
HttpClientModule,
IonicModule.forRoot({
navAnimation: iosTransitionAnimation,
}),
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
{
{
provide: ApiService ,
useFactory: (http: HttpService) => {
if (useMocks) {
@@ -34,9 +33,9 @@ const useMocks = require('../../config.json').useMocks as boolean
return new LiveApiService(http)
}
},
deps: [HttpService]
deps: [HttpService],
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
export class AppModule { }

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EmbassyPage } from './embassy.page';
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { EmbassyPage } from './embassy.page'
const routes: Routes = [
{
path: '',
component: EmbassyPage,
}
];
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class EmbassyPageRoutingModule {}
export class EmbassyPageRoutingModule { }

View File

@@ -1,10 +1,10 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { EmbassyPage } from './embassy.page';
import { PasswordPageModule } from '../password/password.module';
import { EmbassyPageRoutingModule } from './embassy-routing.module';
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { EmbassyPage } from './embassy.page'
import { PasswordPageModule } from '../password/password.module'
import { EmbassyPageRoutingModule } from './embassy-routing.module'
import { PipesModule } from 'src/app/pipes/pipe.module'
@NgModule({
@@ -16,6 +16,6 @@ import { PipesModule } from 'src/app/pipes/pipe.module'
PasswordPageModule,
PipesModule,
],
declarations: [EmbassyPage]
declarations: [EmbassyPage],
})
export class EmbassyPageModule {}
export class EmbassyPageModule { }

View File

@@ -9,8 +9,8 @@
<ion-card color="dark">
<ion-card-header class="ion-text-center" style="padding-bottom: 8px;">
<ion-card-title>{{ loading ? 'Loading Drives' : 'Select Storage Drive'}}</ion-card-title>
<ion-card-subtitle>Select the drive where all your Embassy data will be stored.</ion-card-subtitle>
<ion-card-title>Select Storage Drive</ion-card-title>
<ion-card-subtitle>Select the drive where your Embassy data will be stored.</ion-card-subtitle>
</ion-card-header>
<ion-card-content class="ion-margin">
@@ -31,7 +31,6 @@
<ion-label class="ion-text-wrap">
<ion-skeleton-text style="width: 80%; margin: 13px 0;" animated></ion-skeleton-text>
<ion-skeleton-text style="width: 60%; margin: 10px 0;" animated></ion-skeleton-text>
<ion-skeleton-text style="width: 30%; margin: 8px 0;" animated></ion-skeleton-text>
</ion-label>
</ion-item>
</ng-container>

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core'
import { AlertController, iosTransitionAnimation, LoadingController, ModalController, NavController } from '@ionic/angular'
import { AlertController, LoadingController, ModalController, NavController } from '@ionic/angular'
import { ApiService, DiskInfo } from 'src/app/services/api/api.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { StateService } from 'src/app/services/state.service'
@@ -15,7 +15,7 @@ export class EmbassyPage {
selectedDrive: DiskInfo = null
loading = true
constructor(
constructor (
private readonly apiService: ApiService,
private readonly navCtrl: NavController,
private readonly modalController: ModalController,
@@ -61,9 +61,9 @@ export class EmbassyPage {
text: 'Continue',
handler: () => {
this.presentModalPassword(drive)
}
}
]
},
},
],
})
await alert.present()
} else {
@@ -75,21 +75,21 @@ export class EmbassyPage {
const modal = await this.modalController.create({
component: PasswordPage,
componentProps: {
storageDrive: drive
storageDrive: drive,
},
})
modal.onDidDismiss().then(async ret => {
if (!ret.data || !ret.data.password) return
const loader = await this.loadingCtrl.create({
message: 'Setting up your Embassy!'
message: 'Setting up your Embassy!',
})
await loader.present()
this.stateService.storageDrive = drive
this.stateService.embassyPassword = ret.data.password
try {
this.stateService.torAddress = (await this.stateService.setupEmbassy()).torAddress
} catch (e) {
@@ -98,10 +98,10 @@ export class EmbassyPage {
console.error(e.details)
} finally {
loader.dismiss()
if(!!this.stateService.recoveryDrive) {
await this.navCtrl.navigateForward(`/loading`, { animationDirection: 'forward', animation: iosTransitionAnimation })
if (!!this.stateService.recoveryDrive) {
await this.navCtrl.navigateForward(`/loading`, { animationDirection: 'forward' })
} else {
await this.navCtrl.navigateForward(`/success`, { animationDirection: 'forward', animation: iosTransitionAnimation })
await this.navCtrl.navigateForward(`/success`, { animationDirection: 'forward' })
}
}
})

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomePage } from './home.page';
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]
exports: [RouterModule],
})
export class HomePageRoutingModule {}
export class HomePageRoutingModule { }

View File

@@ -1,11 +1,11 @@
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 { PasswordPageModule } from '../password/password.module';
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 { PasswordPageModule } from '../password/password.module'
import { HomePageRoutingModule } from './home-routing.module';
import { HomePageRoutingModule } from './home-routing.module'
@NgModule({
@@ -16,6 +16,6 @@ import { HomePageRoutingModule } from './home-routing.module';
HomePageRoutingModule,
PasswordPageModule,
],
declarations: [HomePage]
declarations: [HomePage],
})
export class HomePageModule {}
export class HomePageModule { }

View File

@@ -11,11 +11,10 @@
<ion-card-content class="ion-margin">
<!-- fresh -->
<ion-card
(click)="embassyNav()"
routerLink="/embassy"
button="true"
color="light"
style="text-align: center; background-color: #00919b !important; height: 160px; margin-bottom: 20px; box-shadow: 4px 4px 16px var(--ion-color-light);
"
style="text-align: center; background-color: #00919b !important; height: 160px; margin-bottom: 20px; box-shadow: 4px 4px 16px var(--ion-color-light);"
>
<ion-card-header>
<ion-card-title style="font-size: 40px;">Start Fresh</ion-card-title>
@@ -25,11 +24,10 @@
<!-- recover -->
</ion-card>
<ion-card
(click)="recoverNav()"
routerLink="/recover"
button="true"
color="light"
style="text-align: center; background-color: #bf5900 !important; height: 160px; box-shadow: 4px 4px 16px var(--ion-color-light);
"
style="text-align: center; background-color: #bf5900 !important; height: 160px; box-shadow: 4px 4px 16px var(--ion-color-light);"
>
<ion-card-header>
<ion-card-title style="font-size: 40px;">Recover</ion-card-title>

View File

@@ -1,22 +1,9 @@
import { Component } from '@angular/core'
import { iosTransitionAnimation, NavController } from '@ionic/angular'
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
constructor(
private readonly navCtrl: NavController,
) {}
async recoverNav () {
await this.navCtrl.navigateForward(`/recover`, { animationDirection: 'forward', animation: iosTransitionAnimation })
}
async embassyNav () {
await this.navCtrl.navigateForward(`/embassy`, { animationDirection: 'forward', animation: iosTransitionAnimation })
}
}
export class HomePage { }

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoadingPage } from './loading.page';
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { LoadingPage } from './loading.page'
const routes: Routes = [
{
path: '',
component: LoadingPage,
}
];
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class LoadingPageRoutingModule {}
export class LoadingPageRoutingModule { }

View File

@@ -1,10 +1,10 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { LoadingPage } from './loading.page';
import { PasswordPageModule } from '../password/password.module';
import { LoadingPageRoutingModule } from './loading-routing.module';
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { LoadingPage } from './loading.page'
import { PasswordPageModule } from '../password/password.module'
import { LoadingPageRoutingModule } from './loading-routing.module'
@NgModule({
imports: [
@@ -14,6 +14,6 @@ import { LoadingPageRoutingModule } from './loading-routing.module';
LoadingPageRoutingModule,
PasswordPageModule,
],
declarations: [LoadingPage]
declarations: [LoadingPage],
})
export class LoadingPageModule {}
export class LoadingPageModule { }

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core'
import { iosTransitionAnimation, NavController } from '@ionic/angular'
import { NavController } from '@ionic/angular'
import { StateService } from 'src/app/services/state.service'
@Component({
@@ -8,20 +8,19 @@ import { StateService } from 'src/app/services/state.service'
styleUrls: ['loading.page.scss'],
})
export class LoadingPage {
constructor(
constructor (
public stateService: StateService,
private navCtrl: NavController
) {}
private navCtrl: NavController,
) { }
ngOnInit () {
this.stateService.pollDataTransferProgress()
const progSub = this.stateService.dataProgSubject.subscribe(async progress => {
if(progress === 1) {
if (progress === 1) {
progSub.unsubscribe()
await this.navCtrl.navigateForward(`/success`, { animationDirection: 'forward', animation: iosTransitionAnimation })
await this.navCtrl.navigateForward(`/success`)
}
})
}
}

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PasswordPage } from './password.page';
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { PasswordPage } from './password.page'
const routes: Routes = [
{
path: '',
component: PasswordPage,
}
];
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class PasswordPageRoutingModule {}
export class PasswordPageRoutingModule { }

View File

@@ -12,8 +12,8 @@ import { PasswordPageRoutingModule } from './password-routing.module'
CommonModule,
FormsModule,
IonicModule,
PasswordPageRoutingModule
PasswordPageRoutingModule,
],
declarations: [PasswordPage]
declarations: [PasswordPage],
})
export class PasswordPageModule {}
export class PasswordPageModule { }

View File

@@ -30,7 +30,6 @@
[ngModelOptions]="{'standalone': true}"
[type]="!unmasked1 ? 'password' : 'text'"
placeholder="Enter Password"
debounce="500"
(ionChange)="validate()"
maxlength="64"
></ion-input>
@@ -49,7 +48,6 @@
[(ngModel)]="passwordVer"
[ngModelOptions]="{'standalone': true}"
[type]="!unmasked2 ? 'password' : 'text'"
debounce="500"
(ionChange)="checkVer()"
maxlength="64"
placeholder="Retype Password"

View File

@@ -21,13 +21,13 @@ export class PasswordPage {
hasData: boolean
constructor(
constructor (
private modalController: ModalController,
private apiService: ApiService,
private loadingCtrl: LoadingController,
) {}
) { }
ngOnInit() {
ngOnInit () {
if (this.storageDrive && this.storageDrive.partitions.find(p => p.used)) {
this.hasData = true
}
@@ -36,16 +36,16 @@ export class PasswordPage {
async verifyPw () {
if (!this.recoveryDrive) this.pwError = 'No recovery drive' // unreachable
const loader = await this.loadingCtrl.create({
message: 'Verifying Password'
message: 'Verifying Password',
})
await loader.present()
try {
const isCorrectPassword = await this.apiService.verifyRecoveryPassword(this.recoveryDrive.logicalname, this.password)
if(isCorrectPassword) {
const isCorrectPassword = await this.apiService.verify03XPassword(this.recoveryDrive.logicalname, this.password)
if (isCorrectPassword) {
this.modalController.dismiss({ password: this.password })
} else {
this.pwError = "Incorrect password provided"
this.pwError = 'Incorrect password provided'
}
} catch (e) {
this.pwError = 'Error connecting to Embassy'
@@ -57,29 +57,29 @@ export class PasswordPage {
async submitPw () {
this.validate()
if (this.password !== this.passwordVer) {
this.verError="*passwords do not match"
this.verError = '*passwords do not match'
}
if(this.pwError || this.verError) return
if (this.pwError || this.verError) return
this.modalController.dismiss({ password: this.password })
}
validate () {
if(!!this.recoveryDrive) return this.pwError = ''
if (!!this.recoveryDrive) return this.pwError = ''
if (this.passwordVer) {
this.checkVer()
}
if (this.password.length < 12) {
this.pwError="*password must be 12 characters or greater"
this.pwError = '*password must be 12 characters or greater'
} else {
this.pwError = ''
}
}
checkVer () {
this.verError = this.password !== this.passwordVer ? "*passwords do not match" : ''
this.verError = this.password !== this.passwordVer ? '*passwords do not match' : ''
}
cancel () {

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { ProdKeyModal } from './prod-key-modal.page'
const routes: Routes = [
{
path: '',
component: ProdKeyModal,
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ProdKeyModalRoutingModule { }

View File

@@ -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 { ProdKeyModal } from './prod-key-modal.page'
import { ProdKeyModalRoutingModule } from './prod-key-modal-routing.module'
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ProdKeyModalRoutingModule,
],
declarations: [ProdKeyModal],
})
export class ProdKeyModalModule { }

View File

@@ -0,0 +1,47 @@
<ion-header>
<ion-toolbar color="light">
<ion-title>
Verify Recovery Product Key
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content color="light">
<form (ngSubmit)="verifyProductKey()">
<div style="padding: 8px 24px;">
<div style="padding-bottom: 16px;">
<p>Verify the product key for the chosen recovery drive.</p>
</div>
<ion-item
color="dark"
[class]="error ? 'error-border' : ''"
>
<ion-input
[(ngModel)]="productKey"
[ngModelOptions]="{'standalone': true}"
[type]="!unmasked ? 'password' : 'text'"
placeholder="Enter Product Key"
maxlength="64"
maxlength="12"
></ion-input>
<ion-button fill="clear" color="light" (click)="unmasked = !unmasked">
<ion-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
</ion-button>
</ion-item>
<p style="color: var(--ion-color-danger);">{{ error }}</p>
</div>
<input type="submit" style="display: none" />
</form>
</ion-content>
<ion-footer>
<ion-toolbar color="light">
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" (click)="cancel()">
Cancel
</ion-button>
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" strong="true" (click)="verifyProductKey()">
Verify
</ion-button>
</ion-toolbar>
</ion-footer>

View File

@@ -0,0 +1,26 @@
.password-input {
color: var(--ion-color-dark);
margin-bottom: 6px;
font-size: medium;
font-weight: 500;
* {
display: inline-block;
vertical-align: middle;
}
}
.error-border {
border: 2px solid var(--ion-color-danger);
}
.success-border {
border: 2px solid var(--ion-color-success);
}
ion-input {
font-weight: 500;
--placeholder-font-weight: 400;
width: 100%;
background: var(--ion-color-dark);
border-radius: 3px;
}

View File

@@ -0,0 +1,49 @@
import { Component, Input } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular'
import { ApiService, DiskInfo } from 'src/app/services/api/api.service'
import { HttpService } from 'src/app/services/api/http.service'
@Component({
selector: 'prod-key-modal',
templateUrl: 'prod-key-modal.page.html',
styleUrls: ['prod-key-modal.page.scss'],
})
export class ProdKeyModal {
@Input() recoveryDrive: DiskInfo
error = ''
productKey = ''
unmasked = false
constructor (
private readonly modalController: ModalController,
private readonly apiService: ApiService,
private readonly loadingCtrl: LoadingController,
private readonly httpService: HttpService,
) { }
async verifyProductKey () {
if (!this.productKey) return
const loader = await this.loadingCtrl.create({
message: 'Verifying Product Key',
})
await loader.present()
try {
await this.apiService.set02XDrive(this.recoveryDrive.logicalname)
this.httpService.productKey = this.productKey
await this.apiService.verifyProductKey()
this.modalController.dismiss({ productKey: this.productKey })
} catch (e) {
this.httpService.productKey = undefined
this.error = 'Invalid Product Key'
} finally {
loader.dismiss()
}
}
cancel () {
this.modalController.dismiss()
}
}

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductKeyPage } from './product-key.page';
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { ProductKeyPage } from './product-key.page'
const routes: Routes = [
{
path: '',
component: ProductKeyPage,
}
];
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class ProductKeyPageRoutingModule {}
export class ProductKeyPageRoutingModule { }

View File

@@ -1,10 +1,10 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { ProductKeyPage } from './product-key.page';
import { PasswordPageModule } from '../password/password.module';
import { ProductKeyPageRoutingModule } from './product-key-routing.module';
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { ProductKeyPage } from './product-key.page'
import { PasswordPageModule } from '../password/password.module'
import { ProductKeyPageRoutingModule } from './product-key-routing.module'
@NgModule({
imports: [
@@ -14,6 +14,6 @@ import { ProductKeyPageRoutingModule } from './product-key-routing.module';
ProductKeyPageRoutingModule,
PasswordPageModule,
],
declarations: [ProductKeyPage]
declarations: [ProductKeyPage],
})
export class ProductKeyPageModule {}
export class ProductKeyPageModule { }

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core'
import { iosTransitionAnimation, LoadingController, NavController } from '@ionic/angular'
import { LoadingController, NavController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { HttpService } from 'src/app/services/api/http.service'
import { StateService } from 'src/app/services/state.service'
@@ -13,35 +13,32 @@ export class ProductKeyPage {
productKey: string
error: string
constructor(
constructor (
private readonly navCtrl: NavController,
private readonly stateService: StateService,
private readonly apiService: ApiService,
private readonly loadingCtrl: LoadingController,
private readonly httpService: HttpService
) {}
private readonly httpService: HttpService,
) { }
async submit () {
if(!this.productKey) return this.error = "Must enter product key"
if (!this.productKey) return this.error = 'Must enter product key'
const loader = await this.loadingCtrl.create({
message: 'Verifying Product Key'
message: 'Verifying Product Key',
})
await loader.present()
try {
this.httpService.productKey = this.productKey
const state = await this.apiService.verifyProductKey()
if(state['is-recovering']) {
await this.navCtrl.navigateForward(`/loading`, { animationDirection: 'forward', animation: iosTransitionAnimation })
} else if (!!state['tor-address']) {
this.stateService.torAddress = state['tor-address']
await this.navCtrl.navigateForward(`/success`, { animationDirection: 'forward', animation: iosTransitionAnimation })
await this.apiService.verifyProductKey()
if (this.stateService.isMigrating) {
await this.navCtrl.navigateForward(`/loading`)
} else {
await this.navCtrl.navigateForward(`/home`, { animationDirection: 'forward', animation: iosTransitionAnimation })
await this.navCtrl.navigateForward(`/home`)
}
} catch (e) {
this.error = e.message
this.error = 'Invalid Product Key'
this.httpService.productKey = undefined
} finally {
loader.dismiss()

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { RecoverPage } from './recover.page';
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { RecoverPage } from './recover.page'
const routes: Routes = [
{
path: '',
component: RecoverPage,
}
];
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class RecoverPageRoutingModule {}
export class RecoverPageRoutingModule { }

View File

@@ -1,11 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { RecoverPage } from './recover.page';
import { PasswordPageModule } from '../password/password.module';
import { RecoverPageRoutingModule } from './recover-routing.module';
import { PipesModule } from 'src/app/pipes/pipe.module';
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { RecoverPage } from './recover.page'
import { PasswordPageModule } from '../password/password.module'
import { ProdKeyModalModule } from '../prod-key-modal/prod-key-modal.module'
import { RecoverPageRoutingModule } from './recover-routing.module'
import { PipesModule } from 'src/app/pipes/pipe.module'
@NgModule({
@@ -15,8 +16,9 @@ import { PipesModule } from 'src/app/pipes/pipe.module';
IonicModule,
RecoverPageRoutingModule,
PasswordPageModule,
ProdKeyModalModule,
PipesModule,
],
declarations: [RecoverPage]
declarations: [RecoverPage],
})
export class RecoverPageModule {}
export class RecoverPageModule { }

View File

@@ -9,7 +9,7 @@
<ion-card color="dark">
<ion-card-header class="ion-text-center" style="padding-bottom: 8px;">
<ion-card-title>{{ loading ? 'Loading Recovery Drives' : 'Select Recovery Drive'}}</ion-card-title>
<ion-card-title>Select Recovery Drive</ion-card-title>
<ion-card-subtitle>Select the drive containing the Embassy you want to recover.</ion-card-subtitle>
</ion-card-header>
@@ -50,17 +50,17 @@
<span *ngIf="drive.vendor && drive.model"> - </span>
{{ drive.model }}
</h2>
<h2> Embassy version: {{drive['embassy_os'].version}}</h2>
<h2> Embassy version: {{drive['embassy-os'].version}}</h2>
</ion-label>
<ion-icon *ngIf="drive['embassy_os'].version.startsWith('0.2') || passwords[drive.logicalname]" color="success" slot="end" name="lock-open-outline"></ion-icon>
<ion-icon *ngIf="!drive['embassy_os'].version.startsWith('0.2') && !passwords[drive.logicalname]" color="danger" slot="end" name="lock-closed-outline"></ion-icon>
<ion-icon *ngIf="(drive['embassy-os'].version.startsWith('0.2') && stateService.hasProductKey) || passwords[drive.logicalname] || prodKeys[drive.logicalname]" color="success" slot="end" name="lock-open-outline"></ion-icon>
<ion-icon *ngIf="(drive['embassy-os'].version.startsWith('0.2') && !stateService.hasProductKey && !prodKeys[drive.logicalname]) || (!drive['embassy-os'].version.startsWith('0.2') && !passwords[drive.logicalname])" color="danger" slot="end" name="lock-closed-outline"></ion-icon>
</ion-item>
</ng-container>
<ion-button
(click)="selectRecoveryDrive()"
color="light"
[disabled]="!selectedDrive || (!passwords[selectedDrive.logicalname] && !selectedDrive['embassy_os'].version.startsWith('0.2'))"
[disabled]="!selectedDrive || (!passwords[selectedDrive.logicalname] && !selectedDrive['embassy-os'].version.startsWith('0.2'))"
class="claim-button"
*ngIf="recoveryDrives.length"
>

View File

@@ -1,9 +1,10 @@
import { Component } from '@angular/core'
import { iosTransitionAnimation, ModalController, NavController } from '@ionic/angular'
import { ModalController, NavController } from '@ionic/angular'
import { ApiService, DiskInfo } from 'src/app/services/api/api.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { StateService } from 'src/app/services/state.service'
import { PasswordPage } from '../password/password.page'
import { ProdKeyModal } from '../prod-key-modal/prod-key-modal.page'
@Component({
selector: 'app-recover',
@@ -11,18 +12,19 @@ import { PasswordPage } from '../password/password.page'
styleUrls: ['recover.page.scss'],
})
export class RecoverPage {
passwords = {}
passwords = { }
prodKeys = { }
recoveryDrives = []
selectedDrive: DiskInfo = null
loading = true
constructor(
constructor (
private readonly apiService: ApiService,
private readonly navCtrl: NavController,
private readonly modalController: ModalController,
private readonly stateService: StateService,
private readonly modalController: ModalController,
readonly stateService: StateService,
private readonly errorToastService: ErrorToastService,
) {}
) { }
async ngOnInit () {
await this.getDrives()
@@ -37,7 +39,14 @@ export class RecoverPage {
async getDrives () {
try {
this.recoveryDrives = (await this.apiService.getDrives()).filter(d => !!d['embassy_os'])
let drives = (await this.apiService.getDrives()).filter(d => !!d['embassy-os'])
if (!this.stateService.hasProductKey) {
drives = drives.filter(d => d['embassy-os'].version.startsWith('0.2'))
}
this.recoveryDrives = drives
} catch (e) {
this.errorToastService.present(`${e.message}: ${e.data}`)
} finally {
@@ -45,7 +54,7 @@ export class RecoverPage {
}
}
async chooseDrive(drive: DiskInfo) {
async chooseDrive (drive: DiskInfo) {
if (this.selectedDrive?.logicalname === drive.logicalname) {
this.selectedDrive = null
@@ -54,32 +63,51 @@ export class RecoverPage {
this.selectedDrive = drive
}
if (drive['embassy_os'].version.startsWith('0.2') || this.passwords[drive.logicalname]) return
if ((drive['embassy-os'].version.startsWith('0.2') && this.stateService.hasProductKey) || this.passwords[drive.logicalname] || this.prodKeys[drive.logicalname]) return
const modal = await this.modalController.create({
component: PasswordPage,
componentProps: {
recoveryDrive: this.selectedDrive
},
cssClass: 'alertlike-modal',
})
modal.onDidDismiss().then(async ret => {
if (!ret.data) {
this.selectedDrive = null
} else if(ret.data.password) {
this.passwords[drive.logicalname] = ret.data.password
}
})
await modal.present();
if (this.stateService.hasProductKey) {
const modal = await this.modalController.create({
component: PasswordPage,
componentProps: {
recoveryDrive: this.selectedDrive,
},
cssClass: 'alertlike-modal',
})
modal.onDidDismiss().then(async ret => {
if (!ret.data) {
this.selectedDrive = null
} else if (ret.data.password) {
this.passwords[drive.logicalname] = ret.data.password
}
})
await modal.present()
} else {
const modal = await this.modalController.create({
component: ProdKeyModal,
componentProps: {
recoveryDrive: this.selectedDrive,
},
cssClass: 'alertlike-modal',
})
modal.onDidDismiss().then(async ret => {
if (!ret.data) {
this.selectedDrive = null
} else if (ret.data.productKey) {
this.prodKeys[drive.logicalname] = ret.data.productKey
}
})
await modal.present()
}
}
async selectRecoveryDrive() {
async selectRecoveryDrive () {
this.stateService.recoveryDrive = this.selectedDrive
const pw = this.passwords[this.selectedDrive.logicalname]
if(pw) {
if (pw) {
this.stateService.recoveryPassword = pw
}
await this.navCtrl.navigateForward(`/embassy`, { animationDirection: 'forward', animation: iosTransitionAnimation })
}
await this.navCtrl.navigateForward(`/embassy`)
}
}

View File

@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SuccessPage } from './success.page';
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { SuccessPage } from './success.page'
const routes: Routes = [
{
path: '',
component: SuccessPage,
}
];
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class SuccessPageRoutingModule {}
export class SuccessPageRoutingModule { }

View File

@@ -1,11 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { SuccessPage } from './success.page';
import { PasswordPageModule } from '../password/password.module';
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { SuccessPage } from './success.page'
import { PasswordPageModule } from '../password/password.module'
import { SuccessPageRoutingModule } from './success-routing.module';
import { SuccessPageRoutingModule } from './success-routing.module'
@NgModule({
@@ -16,6 +16,6 @@ import { SuccessPageRoutingModule } from './success-routing.module';
SuccessPageRoutingModule,
PasswordPageModule,
],
declarations: [SuccessPage]
declarations: [SuccessPage],
})
export class SuccessPageModule {}
export class SuccessPageModule { }

View File

@@ -8,9 +8,9 @@ import { StateService } from 'src/app/services/state.service'
styleUrls: ['success.page.scss'],
})
export class SuccessPage {
constructor(
public stateService: StateService,
private toastCtrl: ToastController
constructor (
private readonly toastCtrl: ToastController,
public readonly stateService: StateService,
) { }
window = window

View File

@@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { ConvertBytesPipe } from './convert-bytes.pipe';
import { NgModule } from '@angular/core'
import { ConvertBytesPipe } from './convert-bytes.pipe'
@NgModule({
declarations: [ConvertBytesPipe],
@@ -7,4 +7,4 @@ imports: [],
exports: [ConvertBytesPipe],
})
export class PipesModule {}
export class PipesModule { }

View File

@@ -1,30 +1,26 @@
import { Subject } from 'rxjs'
export abstract class ApiService {
abstract verifyProductKey (): Promise<VerifyProductKeyRes>;
abstract getDrives (): Promise<DiskInfo[]>;
abstract getDataTransferProgress (): Promise<TransferProgressRes>;
abstract verifyRecoveryPassword (logicalname: string, password: string): Promise<boolean>;
abstract setupEmbassy (setupInfo: {
'embassy-logicalname': string,
'embassy-password': string
'recovery-logicalname'?: string,
'recovery-password'?: string
}): Promise<SetupEmbassyRes>
// unencrypted
abstract getStatus (): Promise<GetStatusRes> // setup.status
abstract getDrives (): Promise<DiskInfo[]> // setup.disk.list
abstract set02XDrive (logicalname: string): Promise<void> // setup.recovery.v2.set
abstract getRecoveryStatus (): Promise<RecoveryStatusRes> // setup.recovery.status
// encrypted
abstract verifyProductKey (): Promise<void> // echo - throws error if invalid
abstract verify03XPassword (logicalname: string, password: string): Promise<boolean> // setup.recovery.test-password
abstract setupEmbassy (setupInfo: SetupEmbassyReq): Promise<string> // setup.execute
}
export interface VerifyProductKeyRes {
"is-recovering": boolean
"tor-address": string
export interface GetStatusRes {
'product-key': boolean
migrating: boolean
}
export interface TransferProgressRes {
'bytes-transfered': number;
'total-bytes': number;
}
export interface SetupEmbassyRes {
"tor-address": string
export interface SetupEmbassyReq {
'embassy-logicalname': string
'embassy-password': string
'recovery-logicalname'?: string
'recovery-password'?: string
}
export interface DiskInfo {
@@ -33,7 +29,12 @@ export interface DiskInfo {
model: string | null,
partitions: PartitionInfo[],
capacity: number,
embassy_os: EmbassyOsDiskInfo | null,
'embassy-os': EmbassyOsDiskInfo | null,
}
export interface RecoveryStatusRes {
'bytes-transferred': number
'total-bytes': number
}
interface PartitionInfo {

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'
import { Observable, from, interval, race } from 'rxjs'
import { map, take } from 'rxjs/operators'
import { Observable } from 'rxjs'
import * as aesjs from 'aes-js'
import * as pbkdf2 from 'pbkdf2'
@@ -19,7 +18,7 @@ export class HttpService {
this.fullUrl = `${window.location.protocol}//${window.location.hostname}:${port}/rpc/v1`
}
async rpcRequest<T> (body: RPCOptions): Promise<T> {
async rpcRequest<T> (body: RPCOptions, encrypted = true): Promise<T> {
const httpOpts = {
method: Method.POST,
@@ -27,14 +26,20 @@ export class HttpService {
url: this.fullUrl,
}
const res = await this.httpRequest<RPCResponse<T>>(httpOpts)
let res: RPCResponse<T>
if (encrypted) {
res = await this.encryptedHttpRequest<RPCResponse<T>>(httpOpts)
} else {
res = await this.httpRequest<RPCResponse<T>>(httpOpts)
}
if (isRpcError(res)) throw new RpcError(res.error)
if (isRpcSuccess(res)) return res.result
}
async httpRequest<T> (httpOpts: {
async encryptedHttpRequest<T> (httpOpts: {
body: RPCOptions;
url: string;
}): Promise<T> {
@@ -53,7 +58,7 @@ export class HttpService {
headers: {
'Content-Encoding': 'aesctr256',
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
} as any
@@ -71,6 +76,31 @@ export class HttpService {
}
})
}
async httpRequest<T> (httpOpts: {
body: RPCOptions;
url: string;
}): Promise<T> {
const urlIsRelative = httpOpts.url.startsWith('/')
const url = urlIsRelative ?
this.fullUrl + httpOpts.url :
httpOpts.url
const options = {
responseType: 'json',
body: httpOpts.body,
observe: 'events',
reportProgress: false,
headers: { 'content-type': 'application/json', accept: 'application/json' },
} as any
const req: Observable<{ body: T }> = this.http.post(url, httpOpts.body, options) as any
return (req)
.toPromise()
.then(res => res.body)
.catch(e => { throw new HttpError(e) })
}
}
function RpcError (e: RPCError['error']): void {
@@ -167,13 +197,6 @@ export interface HttpOptions {
timeout?: number
}
function withTimeout<U> (req: Observable<U>, timeout: number): Observable<U> {
return race(
from(req.toPromise()), // this guarantees it only emits on completion, intermediary emissions are suppressed.
interval(timeout).pipe(take(1), map(() => { throw new Error('timeout') })),
)
}
type AES_CTR = {
encryptPbkdf2: (secretKey: string, messageBuffer: Uint8Array) => Promise<Uint8Array>
decryptPbkdf2: (secretKey, arr: ArrayBuffer) => Promise<string>
@@ -184,10 +207,10 @@ export const AES_CTR: AES_CTR = {
const salt = window.crypto.getRandomValues(new Uint8Array(16))
const counter = window.crypto.getRandomValues(new Uint8Array(16))
const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1000, 256 / 8, 'sha256');
const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1000, 256 / 8, 'sha256')
const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter));
const encryptedBytes = aesCtr.encrypt(messageBuffer);
const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter))
const encryptedBytes = aesCtr.encrypt(messageBuffer)
return new Uint8Array([...counter, ...salt, ...encryptedBytes])
},
decryptPbkdf2: async (secretKey: string, arr: ArrayBuffer) => {
@@ -196,12 +219,12 @@ export const AES_CTR: AES_CTR = {
const salt = buff.slice(16, 32)
const cipher = buff.slice(32)
const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1000, 256 / 8, 'sha256');
const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1000, 256 / 8, 'sha256')
const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter));
const decryptedBytes = aesCtr.decrypt(cipher);
const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter))
const decryptedBytes = aesCtr.decrypt(cipher)
return aesjs.utils.utf8.fromBytes(decryptedBytes);
return aesjs.utils.utf8.fromBytes(decryptedBytes)
},
}
@@ -214,5 +237,5 @@ export function encodeUtf8 (str: string): Uint8Array {
}
export function decodeUtf8 (arr: Uint8Array): string {
return new TextDecoder().decode(arr);
return new TextDecoder().decode(arr)
}

View File

@@ -1,53 +1,66 @@
import { Injectable } from '@angular/core'
import { ApiService, DiskInfo, SetupEmbassyRes, TransferProgressRes, VerifyProductKeyRes } from './api.service'
import { ApiService, DiskInfo, GetStatusRes, RecoveryStatusRes, SetupEmbassyReq } from './api.service'
import { HttpService } from './http.service'
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class LiveApiService extends ApiService {
constructor(
private readonly http: HttpService
constructor (
private readonly http: HttpService,
) { super() }
async verifyProductKey () {
return this.http.rpcRequest<VerifyProductKeyRes>({
method: 'setup.status',
params: {}
})
}
// ** UNENCRYPTED **
async getDataTransferProgress () {
return this.http.rpcRequest<TransferProgressRes>({
method: 'setup.recovery.status',
params: {}
})
async getStatus () {
return this.http.rpcRequest<GetStatusRes>({
method: 'setup.status',
params: { },
}, false)
}
async getDrives () {
return this.http.rpcRequest<DiskInfo[]>({
method: 'setup.disk.list',
params: {}
params: { },
}, false)
}
async set02XDrive (logicalname) {
return this.http.rpcRequest<void>({
method: 'setup.recovery.v2.set',
params: { logicalname },
}, false)
}
async getRecoveryStatus () {
return this.http.rpcRequest<RecoveryStatusRes>({
method: 'setup.recovery.status',
params: { },
}, false)
}
// ** ENCRYPTED **
async verifyProductKey () {
return this.http.rpcRequest<void>({
method: 'echo',
params: { },
})
}
async verifyRecoveryPassword (logicalname: string, password: string) {
async verify03XPassword (logicalname: string, password: string) {
return this.http.rpcRequest<boolean>({
method: 'setup.recovery.test-password',
params: {logicalname, password}
params: { logicalname, password },
})
}
async setupEmbassy (setupInfo: {
'embassy-logicalname': string,
'embassy-password': string
'recovery-logicalname'?: string,
'recovery-password'?: string
}) {
return this.http.rpcRequest<SetupEmbassyRes>({
async setupEmbassy (setupInfo: SetupEmbassyReq) {
return this.http.rpcRequest<string>({
method: 'setup.execute',
params: setupInfo
params: setupInfo as any,
})
}
}

View File

@@ -1,33 +1,30 @@
import { Injectable } from '@angular/core'
import { pauseFor } from '../state.service'
import { ApiService } from './api.service'
import { ApiService, SetupEmbassyReq } from './api.service'
let tries = 0
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class MockApiService extends ApiService {
constructor() {
constructor () {
super()
}
async verifyProductKey () {
await pauseFor(2000)
return {
"is-recovering": false,
"tor-address": null
}
}
// ** UNENCRYPTED **
async getDataTransferProgress () {
tries = Math.min(tries + 1, 4)
async getStatus () {
await pauseFor(1000)
return {
'bytes-transfered': tries,
'total-bytes': 4
'product-key': true,
migrating: false,
}
}
async getDrives () {
await pauseFor(1000)
return [
{
vendor: 'Vendor',
@@ -38,17 +35,17 @@ export class MockApiService extends ApiService {
logicalname: 'sda1',
label: 'label 1',
capacity: 100000,
used: 200.1255312
used: 200.1255312,
},
{
logicalname: 'sda2',
label: 'label 2',
capacity: 50000,
used: 200.1255312
}
used: 200.1255312,
},
],
capacity: 150000,
'embassy_os': null
'embassy-os': null,
},
{
vendor: 'Vendor',
@@ -63,7 +60,7 @@ export class MockApiService extends ApiService {
// }
],
capacity: 1600.01234,
'embassy_os': null
'embassy-os': null,
},
{
vendor: 'Vendor',
@@ -74,64 +71,77 @@ export class MockApiService extends ApiService {
logicalname: 'sdc1',
label: 'label 1',
capacity: null,
used: null
}
used: null,
},
],
capacity: 100000,
'embassy_os': {
'embassy-os': {
version: '0.3.3',
}
},
},
{
vendor: 'Vendor',
model: 'Model',
logicalname: '/dev/sdd',
partitions: [
partitions: [
{
logicalname: 'sdd1',
label: null,
capacity: 10000,
used: null
}
used: null,
},
],
capacity: 10000,
'embassy_os': {
'embassy-os': {
version: '0.2.7',
}
}
},
},
]
}
async set02XDrive () {
await pauseFor(1000)
return
}
async getRecoveryStatus () {
tries = Math.min(tries + 1, 4)
return {
'bytes-transferred': tries,
'total-bytes': 4,
}
}
// ** ENCRYPTED **
async verifyProductKey () {
await pauseFor(1000)
return
}
async verify03XPassword (logicalname: string, password: string) {
await pauseFor(2000)
return password.length > 8
}
async setupEmbassy (setupInfo: SetupEmbassyReq) {
await pauseFor(3000)
return 'asdfasdfasdf.onion'
}
async getRecoveryDrives () {
await pauseFor(2000)
return [
{
logicalname: 'Name1',
version: '0.3.3',
name: 'My Embassy'
name: 'My Embassy',
},
{
logicalname: 'Name2',
version: '0.2.7',
name: 'My Embassy'
}
name: 'My Embassy',
},
]
}
async verifyRecoveryPassword (logicalname: string, password: string) {
await pauseFor(2000)
return password.length > 8
}
async setupEmbassy (setupInfo: {
'embassy-logicalname': string,
'embassy-password': string
'recovery-logicalname'?: string,
'recovery-password'?: string
}) {
await pauseFor(2000)
return { "tor-address": 'asdfasdfasdf.onion' }
}
}
let tries = 0

View File

@@ -1,52 +1,55 @@
import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject } from 'rxjs'
import { ApiService, DiskInfo } from './api/api.service'
import { ErrorToastService } from './error-toast.service';
import { ErrorToastService } from './error-toast.service'
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class StateService {
hasProductKey: boolean
isMigrating: boolean
polling = false
storageDrive: DiskInfo;
storageDrive: DiskInfo
embassyPassword: string
recoveryDrive: DiskInfo;
recoveryDrive: DiskInfo
recoveryPassword: string
dataTransferProgress: { bytesTransfered: number; totalBytes: number } | null;
dataProgress = 0;
dataTransferProgress: { bytesTransferred: number; totalBytes: number } | null
dataProgress = 0
dataProgSubject = new BehaviorSubject(this.dataProgress)
torAddress: string
constructor(
constructor (
private readonly apiService: ApiService,
private readonly errorToastService: ErrorToastService
) {}
private readonly errorToastService: ErrorToastService,
) { }
async pollDataTransferProgress(callback?: () => void) {
async pollDataTransferProgress (callback?: () => void) {
this.polling = true
await pauseFor(1000)
if (
this.dataTransferProgress?.totalBytes &&
this.dataTransferProgress.bytesTransfered === this.dataTransferProgress.totalBytes
) {return }
this.dataTransferProgress.bytesTransferred === this.dataTransferProgress.totalBytes
) return
let progress
let progress
try {
progress =await this.apiService.getDataTransferProgress()
progress = await this.apiService.getRecoveryStatus()
} catch (e) {
this.errorToastService.present(`${e.message}: ${e.details}`)
}
if (progress) {
this.dataTransferProgress = {
bytesTransfered: progress['bytes-transfered'],
totalBytes: progress['total-bytes']
bytesTransferred: progress['bytes-transferred'],
totalBytes: progress['total-bytes'],
}
if (this.dataTransferProgress.totalBytes) {
this.dataProgress = this.dataTransferProgress.bytesTransfered / this.dataTransferProgress.totalBytes
this.dataProgress = this.dataTransferProgress.bytesTransferred / this.dataTransferProgress.totalBytes
this.dataProgSubject.next(this.dataProgress)
}
}
@@ -58,13 +61,13 @@ export class StateService {
'embassy-logicalname': this.storageDrive.logicalname,
'embassy-password': this.embassyPassword,
'recovery-logicalname': this.recoveryDrive?.logicalname,
'recovery-password': this.recoveryPassword
'recovery-password': this.recoveryPassword,
})
return { torAddress: ret['tor-address'] }
return { torAddress: ret }
}
}
export const pauseFor = (ms: number) => {
const promise = new Promise(resolve => setTimeout(resolve, ms))
return promise
};
}

View File

@@ -1,3 +1,3 @@
export const environment = {
production: true
};
production: true,
}

View File

@@ -3,8 +3,8 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
production: false,
}
/*
* For easier debugging in development mode, you can import the following file

View File

@@ -1,12 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { AppModule } from './app/app.module'
import { environment } from './environments/environment'
if (environment.production) {
enableProdMode();
enableProdMode()
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
.catch(err => console.log(err))

View File

@@ -52,12 +52,12 @@
*
*/
import './zone-flags';
import './zone-flags'
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js/dist/zone' // Included with Angular CLI.
/***************************************************************************************************

View File

@@ -4,6 +4,8 @@
/** Ionic CSS Variables **/
:root {
--ion-font-family: 'Benton Sans';
--ion-text-color: var(--ion-color-dark);
--ion-text-color-rgb: var(--ion-color-dark-rgb);
/** primary **/
--ion-color-primary: #428cff;
--ion-color-primary-rgb: 66,140,255;

View File

@@ -3,4 +3,4 @@
* running with certain Web Component callbacks
*/
// eslint-disable-next-line no-underscore-dangle
(window as any).__Zone_disable_customElements = true;
(window as any).__Zone_disable_customElements = true

47
setup-wizard/tslint.json Normal file
View File

@@ -0,0 +1,47 @@
{
"rules": {
"no-unused-variable": true,
"no-unused-expression": true,
"space-before-function-paren": true,
"semicolon": [
true,
"never"
],
"no-trailing-whitespace": true,
"indent": [
true,
"spaces",
2
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-module",
"check-operator",
"check-separator",
"check-rest-spread",
"check-type",
"check-typecast",
"check-type-operator",
"check-preblock",
"check-postbrace"
],
"trailing-comma": [
true,
{
"multiline": {
"objects": "always",
"arrays": "always",
"functions": "always",
"typeLiterals": "never"
},
"singleline": "never"
}
],
"quotemark": [
true,
"single"
]
}
}