From ce01cb8bc0cdaf64d02b090e319cb98714d4b953 Mon Sep 17 00:00:00 2001 From: Ptera Date: Fri, 8 Dec 2023 10:22:47 -0500 Subject: [PATCH] Allow add new RPC (#117) * Allow add new RPC * Allow add new RPC * Set reasonable default * Lint prettier * Lint prettier * Lint prettier * Update changelog * Update changelog --- CHANGELOG.md | 6 ++ cypress/e2e/message_sign.cy.js | 3 - cypress/robots/signing.robot.ts | 16 +++-- package.json | 2 +- src/app/app.module.ts | 6 ++ .../actions/add-rpc/add-rpc.component.scss | 11 ++++ .../actions/add-rpc/add-rpc.component.ts | 66 +++++++++++++++++++ .../add-rpc/add-rpc-bottom-sheet.component.ts | 14 ++++ .../add-rpc/add-rpc-dialog.component.ts | 14 ++++ .../account-table/account-table.component.ts | 4 -- .../pages/dashboard/dashboard.component.ts | 3 +- src/app/pages/settings/settings.component.ts | 64 ++++++++++++++++-- src/app/services/app-state.service.ts | 4 ++ src/app/services/datasource.service.ts | 34 +++++++++- src/app/services/wallet-events.service.ts | 25 +++++++ src/app/services/wallet-storage.service.ts | 20 ++++-- 16 files changed, 264 insertions(+), 28 deletions(-) create mode 100644 src/app/overlays/actions/add-rpc/add-rpc.component.scss create mode 100644 src/app/overlays/actions/add-rpc/add-rpc.component.ts create mode 100644 src/app/overlays/bottom-sheet/add-rpc/add-rpc-bottom-sheet.component.ts create mode 100644 src/app/overlays/dialogs/add-rpc/add-rpc-dialog.component.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 268bb5cb..f4993d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## v4.4.0 (December 8, 2023) + +### Added + +- Added option to add custom RPC node on settings page. + ## v4.3.0 (September 30, 2023) ### Added diff --git a/cypress/e2e/message_sign.cy.js b/cypress/e2e/message_sign.cy.js index 79cd8c5d..ce9f7c37 100644 --- a/cypress/e2e/message_sign.cy.js +++ b/cypress/e2e/message_sign.cy.js @@ -34,7 +34,6 @@ describe('Message Sign', () => { ); signingRobot .checkSigningPageExists() - .clickMessageSigningExpand() .checkMessageSignButtonDisabled() .enterMessage('samplemessage') .clickMessageSignButton() @@ -63,7 +62,6 @@ describe('Message Sign', () => { ); signingRobot .checkSigningPageExists() - .clickMessageSigningExpand() .checkMessageSignButtonDisabled() .enterMessage('samplemessage') .clickMessageSignButton() @@ -92,7 +90,6 @@ describe('Message Sign', () => { ); signingRobot .checkSigningPageExists() - .clickMessageSigningExpand() .clickMessageSignButton() .checkSignatureEquals( 'C4E6ADE8957E39D4BC18CC703A848B9F0251B406D47EA1A7B5A045AAFBF185AC25438EF21E4FDF55A7E724D3C5A3011D28D27F751545B9AED93F3291156B7F03' diff --git a/cypress/robots/signing.robot.ts b/cypress/robots/signing.robot.ts index 3aca627b..c9aedf43 100644 --- a/cypress/robots/signing.robot.ts +++ b/cypress/robots/signing.robot.ts @@ -1,6 +1,9 @@ /// export class SigningRobot { + // TODO: Remove this; should not be required. + reasonableInputTimeEntryDelayMs = 1000; + checkSigningPageExists(): SigningRobot { cy.get('[data-cy=signing-page]').should('exist'); return this; @@ -17,26 +20,30 @@ export class SigningRobot { } enterMessage(message: string): SigningRobot { - cy.wait(100); + cy.wait(this.reasonableInputTimeEntryDelayMs); cy.get('[data-cy=signing-input]').type(message); + cy.wait(this.reasonableInputTimeEntryDelayMs); return this; } enterVerificationAddress(address: string): SigningRobot { - cy.wait(100); + cy.wait(this.reasonableInputTimeEntryDelayMs); cy.get('[data-cy=verification-address-input]').type(address); + cy.wait(this.reasonableInputTimeEntryDelayMs); return this; } enterVerificationMessage(message: string): SigningRobot { - cy.wait(100); + cy.wait(this.reasonableInputTimeEntryDelayMs); cy.get('[data-cy=verification-message-input]').type(message); + cy.wait(this.reasonableInputTimeEntryDelayMs); return this; } enterVerificationSignature(sig: string): SigningRobot { - cy.wait(100); + cy.wait(this.reasonableInputTimeEntryDelayMs); cy.get('[data-cy=verification-signature-input]').type(sig); + cy.wait(this.reasonableInputTimeEntryDelayMs); return this; } @@ -64,6 +71,7 @@ export class SigningRobot { } checkSignatureEquals(message: string): SigningRobot { + cy.wait(this.reasonableInputTimeEntryDelayMs); cy.get('input[name="signature"]') .invoke('val') .then((val) => { diff --git a/package.json b/package.json index e918c1f8..264053ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "thebananostand", - "version": "4.3.0", + "version": "4.4.0", "scripts": { "ng": "ng", "start": "ng serve --open --host 0.0.0.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6ef67bbc..61ac54fb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -95,6 +95,9 @@ import { provideUserIdleConfig } from 'angular-user-idle'; import { MatSliderModule } from '@angular/material/slider'; import { LOAD_WASM, NgxScannerQrcodeModule } from 'ngx-scanner-qrcode'; import { CommaPipe } from './pipes/comma.pipe'; +import { AddRpcOverlayComponent } from '@app/overlays/actions/add-rpc/add-rpc.component'; +import { AddRpcDialogComponent } from '@app/overlays/dialogs/add-rpc/add-rpc-dialog.component'; +import { AddRpcBottomSheetComponent } from '@app/overlays/bottom-sheet/add-rpc/add-rpc-bottom-sheet.component'; LOAD_WASM().subscribe((res: any) => console.log('WASM ngx-scanner-qrcode loaded', res)); @@ -108,6 +111,9 @@ LOAD_WASM().subscribe((res: any) => console.log('WASM ngx-scanner-qrcode loaded' AddIndexBottomSheetComponent, AddIndexDialogComponent, AddIndexOverlayComponent, + AddRpcOverlayComponent, + AddRpcDialogComponent, + AddRpcBottomSheetComponent, AddressBookComponent, AppAccountSettingsComponent, AppComponent, diff --git a/src/app/overlays/actions/add-rpc/add-rpc.component.scss b/src/app/overlays/actions/add-rpc/add-rpc.component.scss new file mode 100644 index 00000000..f0b04791 --- /dev/null +++ b/src/app/overlays/actions/add-rpc/add-rpc.component.scss @@ -0,0 +1,11 @@ +.add-rpc-overlay { + width: 380px; +} + +.add-rpc-example { + font-family: monospace; + background: #e6e6e6; + padding: 0 4px; + border-radius: 4px; + margin-top: 8px; +} diff --git a/src/app/overlays/actions/add-rpc/add-rpc.component.ts b/src/app/overlays/actions/add-rpc/add-rpc.component.ts new file mode 100644 index 00000000..e4f3ed74 --- /dev/null +++ b/src/app/overlays/actions/add-rpc/add-rpc.component.ts @@ -0,0 +1,66 @@ +import { Component, EventEmitter, Output } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { ADD_RPC_NODE_BY_URL } from '@app/services/wallet-events.service'; + +@Component({ + selector: 'app-add-rpc-overlay', + styleUrls: ['add-rpc.component.scss'], + template: ` +
+
Add new Banano RPC Node
+
+
+ If the default Banano nodes are unavailable, you can add a custom RPC node to handle all send, + receive, change transactions. +
+
+
Use the input field below to enter the URL of your new Banano node e.g:
+
https://booster.dev-ptera.com/banano-rpc
+
+
+ + RPC Node URL + + +
+
+ +
+ `, +}) +export class AddRpcOverlayComponent { + urlFormControl = new FormControl(''); + + @Output() close: EventEmitter = new EventEmitter(); + + isDisabled(): boolean { + return !this.urlFormControl.value; + } + + addRpcNode(): void { + if (this.isDisabled()) { + return; + } + + // TODO: Add checks to see if the node is accessible and online before adding it to the list. + ADD_RPC_NODE_BY_URL.next(this.urlFormControl.value); + this.close.emit(); + } +} diff --git a/src/app/overlays/bottom-sheet/add-rpc/add-rpc-bottom-sheet.component.ts b/src/app/overlays/bottom-sheet/add-rpc/add-rpc-bottom-sheet.component.ts new file mode 100644 index 00000000..f3076449 --- /dev/null +++ b/src/app/overlays/bottom-sheet/add-rpc/add-rpc-bottom-sheet.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { MatBottomSheetRef } from '@angular/material/bottom-sheet'; + +@Component({ + selector: 'app-add-rpc-bottom-sheet', + template: ` `, +}) +export class AddRpcBottomSheetComponent { + constructor(private readonly _sheet: MatBottomSheetRef) {} + + closeDialog(): void { + this._sheet.dismiss(); + } +} diff --git a/src/app/overlays/dialogs/add-rpc/add-rpc-dialog.component.ts b/src/app/overlays/dialogs/add-rpc/add-rpc-dialog.component.ts new file mode 100644 index 00000000..b6031ff0 --- /dev/null +++ b/src/app/overlays/dialogs/add-rpc/add-rpc-dialog.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-add-rpc-dialog', + template: ` `, +}) +export class AddRpcDialogComponent { + constructor(private readonly _dialogRef: MatDialogRef) {} + + closeDialog(): void { + this._dialogRef.close(); + } +} diff --git a/src/app/pages/dashboard/components/account-table/account-table.component.ts b/src/app/pages/dashboard/components/account-table/account-table.component.ts index dd621621..59c3efde 100644 --- a/src/app/pages/dashboard/components/account-table/account-table.component.ts +++ b/src/app/pages/dashboard/components/account-table/account-table.component.ts @@ -5,8 +5,6 @@ import { MatTableDataSource } from '@angular/material/table'; import { ViewportService } from '@app/services/viewport.service'; import { Router } from '@angular/router'; import { UtilService } from '@app/services/util.service'; -import { MatDialog } from '@angular/material/dialog'; -import { MatBottomSheet } from '@angular/material/bottom-sheet'; import { ThemeService } from '@app/services/theme.service'; import { AccountService } from '@app/services/account.service'; import { MatSort } from '@angular/material/sort'; @@ -148,8 +146,6 @@ export class AccountTableComponent implements OnInit { public vp: ViewportService, private readonly _router: Router, private readonly _util: UtilService, - private readonly _dialog: MatDialog, - private readonly _sheet: MatBottomSheet, private readonly _themeService: ThemeService, private readonly _accountService: AccountService, private readonly _appStateService: AppStateService diff --git a/src/app/pages/dashboard/dashboard.component.ts b/src/app/pages/dashboard/dashboard.component.ts index bec3684c..bfd0e1fc 100644 --- a/src/app/pages/dashboard/dashboard.component.ts +++ b/src/app/pages/dashboard/dashboard.component.ts @@ -1,6 +1,6 @@ import { Component, ViewEncapsulation } from '@angular/core'; import * as Colors from '@brightlayer-ui/colors'; -import { ActivatedRoute, Params, Router } from '@angular/router'; +import { ActivatedRoute, Params } from '@angular/router'; import { UtilService } from '@app/services/util.service'; import { MatDialog } from '@angular/material/dialog'; import { ViewportService } from '@app/services/viewport.service'; @@ -54,7 +54,6 @@ export class DashboardComponent { constructor( public vp: ViewportService, private readonly _route: ActivatedRoute, - private readonly _router: Router, private readonly _dialog: MatDialog, private readonly _util: UtilService, private readonly _sheet: MatBottomSheet, diff --git a/src/app/pages/settings/settings.component.ts b/src/app/pages/settings/settings.component.ts index f330bcc5..ba4f6e3a 100644 --- a/src/app/pages/settings/settings.component.ts +++ b/src/app/pages/settings/settings.component.ts @@ -10,13 +10,17 @@ import { Router } from '@angular/router'; import { EDIT_MINIMUM_INCOMING_THRESHOLD, REMOVE_ALL_WALLET_DATA, + REMOVE_CUSTOM_RPC_NODE_BY_INDEX, SELECT_LOCALIZATION_CURRENCY, + SELECTED_RPC_DATASOURCE_CHANGE, } from '@app/services/wallet-events.service'; import { MatRadioChange } from '@angular/material/radio'; import { CurrencyConversionService } from '@app/services/currency-conversion.service'; import { AppStateService } from '@app/services/app-state.service'; import { MatSelectChange } from '@angular/material/select'; import { UntilDestroy } from '@ngneat/until-destroy'; +import { AddRpcBottomSheetComponent } from '@app/overlays/bottom-sheet/add-rpc/add-rpc-bottom-sheet.component'; +import { AddRpcDialogComponent } from '@app/overlays/dialogs/add-rpc/add-rpc-dialog.component'; @Pipe({ name: 'available' }) export class DatasourceAvailablePipe implements PipeTransform { @@ -60,7 +64,6 @@ export class DatasourceAvailablePipe implements PipeTransform { mat-stroked-button blui-inline color="primary" - class="preserve-non-mobile" (click)="openChangePasswordOverlay()" data-cy="change-password-button" > @@ -80,7 +83,6 @@ export class DatasourceAvailablePipe implements PipeTransform { mat-stroked-button blui-inline color="warn" - class="preserve-non-mobile" longPress (mouseLongPress)="clearStorage()" data-cy="clear-storage-button" @@ -114,8 +116,24 @@ export class DatasourceAvailablePipe implements PipeTransform {
Data Sources
-
Node RPC Datasource
-
The node which broadcasts send, receive and change transactions.
+ +
+ Custom Entries +
+
+ + + + +
Spyglass API Datasource
@@ -198,8 +235,8 @@ export class DatasourceAvailablePipe implements PipeTransform { styleUrls: ['./settings.component.scss'], }) export class SettingsPageComponent implements OnInit { - selectedRpcSource: any; - selectedSpyglassApi: any; + selectedRpcSource: Datasource; + selectedSpyglassApi: Datasource; selectedCurrencyCode: string; minimumThreshold: number; @@ -216,6 +253,9 @@ export class SettingsPageComponent implements OnInit { this._appStateService.store.subscribe((data) => { this.selectedCurrencyCode = data.localCurrencyCode; }); + SELECTED_RPC_DATASOURCE_CHANGE.subscribe((source) => { + this.selectedRpcSource = source; + }); } async ngOnInit(): Promise { @@ -236,6 +276,18 @@ export class SettingsPageComponent implements OnInit { } } + openAddRpcOverlay(): void { + if (this.vp.sm) { + this._sheet.open(AddRpcBottomSheetComponent); + } else { + this._dialog.open(AddRpcDialogComponent); + } + } + + removeCustomRpcNode(index: number): void { + REMOVE_CUSTOM_RPC_NODE_BY_INDEX.next(index); + } + clearStorage(): void { REMOVE_ALL_WALLET_DATA.next(); void this._router.navigate(['/']); diff --git a/src/app/services/app-state.service.ts b/src/app/services/app-state.service.ts index 5fe06fc7..66939388 100644 --- a/src/app/services/app-state.service.ts +++ b/src/app/services/app-state.service.ts @@ -39,6 +39,8 @@ export type AppStore = { walletPassword: string; /** Determines how the Dashboard page looks. Can either be table or card. */ preferredDashboardView: 'card' | 'table'; + /** Custom RPC nodes **/ + customRpcNodeURLs: string[]; }; @Injectable({ @@ -72,6 +74,7 @@ export class AppStateService { idleTimeoutMinutes: 15, isLoadingAccounts: true, preferredDashboardView: undefined, + customRpcNodeURLs: [], }); appLocalStorage = new Subject<{ @@ -82,5 +85,6 @@ export class AppStateService { localStorageWallets: LocalStorageWallet[]; preferredDashboardView: string; idleTimeoutMinutes: number; + customRpcNodeURLs: string[]; }>(); } diff --git a/src/app/services/datasource.service.ts b/src/app/services/datasource.service.ts index 8ae08a4b..09e4d197 100644 --- a/src/app/services/datasource.service.ts +++ b/src/app/services/datasource.service.ts @@ -2,12 +2,15 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Subject } from 'rxjs'; import { NanoClient } from '@dev-ptera/nano-node-rpc'; +import { AppStateService } from '@app/services/app-state.service'; +import { SELECTED_RPC_DATASOURCE_CHANGE } from '@app/services/wallet-events.service'; export type Datasource = { - alias: 'Batman' | 'Creeper' | 'Jungle Tv' | 'Booster' | 'Kalium' | 'Rain City'; + alias: 'Batman' | 'Creeper' | 'Jungle Tv' | 'Booster' | 'Kalium' | 'Rain City' | string; url: string; isAccessible: boolean; isSelected: boolean; + isAddedByUser?: boolean; }; @Injectable({ @@ -39,6 +42,9 @@ export class DatasourceService { // { alias: 'Rain City', url: 'https://rainstorm.city/api', isAccessible: false, isSelected: false } // Nano node, but can generate work (?) ]; + defaultRpcDataSource: Datasource; + customRpcDataSources: Datasource[] = []; + private rpcNode: NanoClient; private rpcSource: Datasource; private readonly rpcSourceLoadedSubject = new Subject(); @@ -46,12 +52,34 @@ export class DatasourceService { private spyglassApiSource: Datasource; private readonly spyglassSourceLoadedSubject = new Subject(); - constructor(http: HttpClient) { + constructor(http: HttpClient, state: AppStateService) { const handleError = (err, url): void => { console.error(`${url} is inaccessible as a datasource, ignoring it.`); console.error(err); }; + state.store.subscribe((data) => { + if (!data.customRpcNodeURLs || data.customRpcNodeURLs.length === this.customRpcDataSources.length) { + return; + } + + this.customRpcDataSources = []; + data.customRpcNodeURLs.map((customSourceUrl, index) => { + const newSource = { + isSelected: false, + isAccessible: true, + alias: `Custom node #${index}`, + url: customSourceUrl, + }; + this.customRpcDataSources.push(newSource); + }); + + /** When a custom datasource is added, we will set it as the selected RPC datasource. */ + this.setRpcSource( + this.customRpcDataSources[this.customRpcDataSources.length - 1] || this.defaultRpcDataSource + ); + }); + // Ping available RPC Sources this.availableRpcDataSources.map((source: Datasource) => { http.post(source.url, { action: 'block_count' }) @@ -63,6 +91,7 @@ export class DatasourceService { // eslint-disable-next-line no-console console.log(`Using ${source.alias} as RPC source.`); this.setRpcSource(source); + this.defaultRpcDataSource = source; this.rpcSourceLoadedSubject.next(source); } }) @@ -95,6 +124,7 @@ export class DatasourceService { this.rpcNode = new NanoClient({ url: source.url, }); + SELECTED_RPC_DATASOURCE_CHANGE.next(this.rpcSource); } setSpyglassApiSource(source: Datasource): void { diff --git a/src/app/services/wallet-events.service.ts b/src/app/services/wallet-events.service.ts index f21f9ce7..a012b7f9 100644 --- a/src/app/services/wallet-events.service.ts +++ b/src/app/services/wallet-events.service.ts @@ -13,6 +13,7 @@ import { SpyglassService } from '@app/services/spyglass.service'; import { CurrencyConversionService } from '@app/services/currency-conversion.service'; import { AuthGuardService } from '../guards/auth-guard'; import { Router } from '@angular/router'; +import { Datasource } from '@app/services/datasource.service'; const SNACKBAR_DURATION = 3000; const SNACKBAR_CLOSE_ACTION_TEXT = 'Dismiss'; @@ -24,6 +25,9 @@ export const ADD_NEXT_ACCOUNT_BY_INDEX = new Subject(); /** New addresses (index) has been added to the dashboard. */ export const ADD_SPECIFIC_ACCOUNTS_BY_INDEX = new Subject(); +/** New Banano Node (URL) has been added to the settings page. */ +export const ADD_RPC_NODE_BY_URL = new Subject(); + /** User has attempted to unlock an encrypted secret wallet using a password. */ export const ATTEMPT_UNLOCK_WALLET_WITH_PASSWORD = new Subject<{ password: string }>(); @@ -93,12 +97,18 @@ export const REMOVE_ACTIVE_WALLET = new Subject(); /** User has opted to delete all locally stored info. */ export const REMOVE_ALL_WALLET_DATA = new Subject(); +/** A Banano Node (URL) has been removed from the settings page. The display order on the settings page matches the order in storage. */ +export const REMOVE_CUSTOM_RPC_NODE_BY_INDEX = new Subject(); + /** User has requested a backup action */ export const REQUEST_BACKUP_SECRET = new Subject<{ useMnemonic: boolean }>(); /** An account is being added to the dashboard. Can be either true or false. */ export const SET_DASHBOARD_ACCOUNT_LOADING = new BehaviorSubject(true); +/** Datasource RPC has been updated. */ +export const SELECTED_RPC_DATASOURCE_CHANGE = new Subject(); + /** A transaction has been broadcast onto the network successfully. */ export const TRANSACTION_COMPLETED_SUCCESS = new Subject(); @@ -149,6 +159,7 @@ export class WalletEventsService { localStorageWallets: this._walletStorageService.readWalletsFromLocalStorage(), preferredDashboardView: this._walletStorageService.readPreferredDashboardViewFromLocalStorage(), idleTimeoutMinutes: this._walletStorageService.readIdleTimeoutMinutes(), + customRpcNodeURLs: this._walletStorageService.readCustomRpcNodeUrls(), }); this._appStateService.store.subscribe((store) => { @@ -180,6 +191,11 @@ export class WalletEventsService { }); }); + ADD_RPC_NODE_BY_URL.subscribe((url: string) => { + this.store.customRpcNodeURLs.push(url); + this._dispatch({ customRpcNodeURLs: this.store.customRpcNodeURLs }); + }); + ATTEMPT_UNLOCK_LEDGER_WALLET.subscribe(async () => { try { await this._signerService.checkLedgerOrError(); @@ -367,6 +383,13 @@ export class WalletEventsService { LOCK_WALLET.next(); }); + REMOVE_CUSTOM_RPC_NODE_BY_INDEX.subscribe((index) => { + this.store.customRpcNodeURLs.splice(index, 1); + this._dispatch({ + customRpcNodeURLs: this.store.customRpcNodeURLs, + }); + }); + REQUEST_BACKUP_SECRET.subscribe(async (data) => { if (data.useMnemonic) { const mnemonic = await this._secretService.getActiveWalletMnemonic(); @@ -417,9 +440,11 @@ export class WalletEventsService { newData.addressBook || newData.localCurrencyCode || newData.preferredDashboardView || + newData.customRpcNodeURLs || newData.minimumBananoThreshold !== undefined // Can be 0. ) { this._appStateService.appLocalStorage.next({ + customRpcNodeURLs: newData.customRpcNodeURLs, minimumBananoThreshold: newData.minimumBananoThreshold, preferredDashboardView: newData.preferredDashboardView, localizationCurrencyCode: newData.localCurrencyCode, diff --git a/src/app/services/wallet-storage.service.ts b/src/app/services/wallet-storage.service.ts index b8cf4cb2..92b5c812 100644 --- a/src/app/services/wallet-storage.service.ts +++ b/src/app/services/wallet-storage.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@angular/core'; -import { UtilService } from '@app/services/util.service'; import { AppStateService, AppStore } from '@app/services/app-state.service'; import { AccountOverview } from '@app/types/AccountOverview'; import { AddressBookEntry } from '@app/types/AddressBookEntry'; @@ -22,6 +21,7 @@ const LEDGER_STORED_INDEXES = 'bananostand_ledgerIndexes'; const PREFERRED_DASHBOARD_VIEW = 'bananostand_dashboardView'; const IDLE_TIMEOUT_MINUTES = 'bananostand_idleTimeoutMinutes'; const MINIMUM_INCOMING_THRESHOLD_BAN = 'bananostand_minimumIncomingBananoThreshold'; +const CUSTOM_RPC_NODE_URLS = 'bananostand_customRpcNodeURLs'; @Injectable({ providedIn: 'root', @@ -32,11 +32,7 @@ const MINIMUM_INCOMING_THRESHOLD_BAN = 'bananostand_minimumIncomingBananoThresho export class WalletStorageService { store: AppStore; - constructor( - private readonly _util: UtilService, - private readonly _vp: ViewportService, - private readonly _appStateService: AppStateService - ) { + constructor(private readonly _vp: ViewportService, private readonly _appStateService: AppStateService) { this._appStateService.store.subscribe((store) => { this.store = store; }); @@ -44,6 +40,10 @@ export class WalletStorageService { // Listen for the updated store and write to localstorage accordingly. // `store` & `localStorage` will always match. this._appStateService.appLocalStorage.subscribe((walletData) => { + if (walletData.customRpcNodeURLs !== undefined) { + window.localStorage.setItem(CUSTOM_RPC_NODE_URLS, walletData.customRpcNodeURLs.toString()); + } + if (walletData.minimumBananoThreshold !== undefined) { window.localStorage.setItem(MINIMUM_INCOMING_THRESHOLD_BAN, String(walletData.minimumBananoThreshold)); } @@ -215,6 +215,14 @@ export class WalletStorageService { return wallets[0]; } + readCustomRpcNodeUrls(): string[] { + const urls = localStorage.getItem(CUSTOM_RPC_NODE_URLS); + if (!urls) { + return []; + } + return urls.split(','); + } + /** Reads from local storage, defaults to USD. */ readLocalizationCurrencyFromLocalStorage(): string { return window.localStorage.getItem(LOCALIZATION_CURRENCY_CODE) || 'USD';