From f22f11eb586d4884b6d8e638315eff841a5417dc Mon Sep 17 00:00:00 2001 From: Lucy C <12953208+elvece@users.noreply.github.com> Date: Mon, 27 Jun 2022 15:25:42 -0600 Subject: [PATCH] Fix/sideload icon type (#1577) * add content type to icon dataURL * better handling of blob reading; remove verifying loader and reorganize html * clean up PR feedback and create validation fn instead of boolean * grpup upload state into one type * better organize validation * add server id to eos check for updates req * fix patchdb to latest Co-authored-by: Matt Hill --- .../src/types/marketplace-manifest.ts | 2 +- .../server-routes/sideload/sideload.page.html | 103 +++++++++-------- .../server-routes/sideload/sideload.page.scss | 14 ++- .../server-routes/sideload/sideload.page.ts | 109 +++++++++++------- .../ui/src/app/services/api/api.fixures.ts | 24 ++++ .../ui/src/app/services/api/api.types.ts | 1 + .../ui/src/app/services/api/mock-patch.ts | 16 +++ .../ui/src/app/services/eos.service.ts | 10 +- .../src/app/services/patch-db/data-model.ts | 9 ++ 9 files changed, 186 insertions(+), 102 deletions(-) diff --git a/frontend/projects/marketplace/src/types/marketplace-manifest.ts b/frontend/projects/marketplace/src/types/marketplace-manifest.ts index 05ca26cca..a8df045f3 100644 --- a/frontend/projects/marketplace/src/types/marketplace-manifest.ts +++ b/frontend/projects/marketplace/src/types/marketplace-manifest.ts @@ -10,7 +10,7 @@ export interface MarketplaceManifest { long: string } 'release-notes': string - license: string // name + license: string // type of license 'wrapper-repo': Url 'upstream-repo': Url 'support-site': Url diff --git a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html index d6d99b5de..f3dc66464 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html @@ -10,7 +10,7 @@
-
-
- -

{{ message }}

-
- -
-
- -

{{ message }}

-
- - Try again - -
-
-
-
-
-
- - - -
-
- -

{{ toUpload.manifest.title }}

-

{{ toUpload.manifest.version | displayEmver }}

+ +
+

+ + + {{ uploadState?.message }} +

+
+
+
+ + + +
+
+ +

{{ toUpload.manifest.title }}

+

{{ toUpload.manifest.version | displayEmver }}

+
- - Upload & Install + + Try again + + + Upload & Install + +
-
+ diff --git a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss index 4d96f15a7..482572370 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss +++ b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.scss @@ -1,8 +1,5 @@ .inline { - * { - vertical-align: initial; - padding-right: 5px; - } + vertical-align: initial; } .area { @@ -39,7 +36,7 @@ margin: 60px; padding: 30px; min-height: 600px; - + min-width: 400px; } &_mobile { @@ -51,10 +48,15 @@ } } +.box { + display: flex; + justify-content: space-evenly +} + .service-card { background: radial-gradient(var(--ion-color-step-100), transparent); min-width: 200px; - max-width: 300px; + max-width: 200px; height: auto; padding: 0; display: flex; diff --git a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts index 7a904a419..9fa243d11 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts @@ -5,12 +5,14 @@ import { Manifest } from 'src/app/services/patch-db/data-model' import { ConfigService } from 'src/app/services/config.service' import cbor from 'cbor' import { ErrorToastService } from '@start9labs/shared' + interface Positions { [key: string]: [bigint, bigint] // [position, length] } const MAGIC = new Uint8Array([59, 59]) const VERSION = new Uint8Array([1]) + @Component({ selector: 'sideload', templateUrl: './sideload.page.html', @@ -28,8 +30,10 @@ export class SideloadPage { file: null, } onTor = this.config.isTor() - valid: boolean - message: string + uploadState: { + invalid: boolean + message: string + } constructor( private readonly loadingCtrl: LoadingController, @@ -49,29 +53,27 @@ export class SideloadPage { this.setFile(files) } async setFile(files?: File[]) { - const loader = await this.loadingCtrl.create({ - message: 'Verifying package', - cssClass: 'loader', - }) - await loader.present() if (!files || !files.length) return - this.toUpload.file = files[0] - // verify valid s9pk - const magic = new Uint8Array( - await readBlobToArrayBuffer(this.toUpload.file.slice(0, 2)), - ) - const version = new Uint8Array( - await readBlobToArrayBuffer(this.toUpload.file.slice(2, 3)), - ) + const file = files[0] + if (!file) return + this.toUpload.file = file + this.uploadState = await this.validateS9pk(file) + } + + async validateS9pk(file: File) { + const magic = new Uint8Array(await blobToBuffer(file.slice(0, 2))) + const version = new Uint8Array(await blobToBuffer(file.slice(2, 3))) if (compare(magic, MAGIC) && compare(version, VERSION)) { - loader.dismiss() - this.valid = true - this.message = 'A valid package file has been detected!' - await this.parseS9pk(this.toUpload.file) + await this.parseS9pk(file) + return { + invalid: false, + message: 'A valid package file has been detected!', + } } else { - loader.dismiss() - this.valid = false - this.message = 'Invalid package file' + return { + invalid: true, + message: 'Invalid package file', + } } } @@ -93,7 +95,7 @@ export class SideloadPage { icon: this.toUpload.icon!, }) this.api - .uploadPackage(guid, await readBlobToArrayBuffer(this.toUpload.file!)) + .uploadPackage(guid, await blobToBuffer(this.toUpload.file!)) .catch(e => { this.errToast.present(e) }) @@ -108,15 +110,13 @@ export class SideloadPage { } } - async parseS9pk(file: Blob) { + async parseS9pk(file: File) { const positions: Positions = {} // magic=2bytes, version=1bytes, pubkey=32bytes, signature=64bytes, toc_length=4bytes = 103byte is starting point let start = 103 let end = start + 1 // 104 const tocLength = new DataView( - await readBlobToArrayBuffer( - this.toUpload.file?.slice(99, 103) ?? new Blob(), - ), + await blobToBuffer(file.slice(99, 103) ?? new Blob()), ).getUint32(0, false) await getPositions(start, end, file, positions, tocLength as any) @@ -125,7 +125,7 @@ export class SideloadPage { } async getManifest(positions: Positions, file: Blob) { - const data = await readBlobToArrayBuffer( + const data = await blobToBuffer( file.slice( Number(positions['manifest'][0]), Number(positions['manifest'][0]) + Number(positions['manifest'][1]), @@ -135,11 +135,15 @@ export class SideloadPage { } async getIcon(positions: Positions, file: Blob) { + const contentType = `image/${this.toUpload.manifest?.assets.icon + .split('.') + .pop()}` const data = file.slice( Number(positions['icon'][0]), Number(positions['icon'][0]) + Number(positions['icon'][1]), + contentType, ) - this.toUpload.icon = await readBlobAsDataURL(data) + this.toUpload.icon = await blobToDataURL(data) } } @@ -153,18 +157,18 @@ async function getPositions( let start = initialStart let end = initialEnd const titleLength = new Uint8Array( - await readBlobToArrayBuffer(file.slice(start, end)), + await blobToBuffer(file.slice(start, end)), )[0] const tocTitle = await file.slice(end, end + titleLength).text() start = end + titleLength end = start + 8 const chapterPosition = new DataView( - await readBlobToArrayBuffer(file.slice(start, end)), + await blobToBuffer(file.slice(start, end)), ).getBigUint64(0, false) start = end end = start + 8 const chapterLength = new DataView( - await readBlobToArrayBuffer(file.slice(start, end)), + await blobToBuffer(file.slice(start, end)), ).getBigUint64(0, false) positions[tocTitle] = [chapterPosition, chapterLength] @@ -175,23 +179,48 @@ async function getPositions( } } -async function readBlobAsDataURL(f: Blob): Promise { +async function readBlobAsDataURL( + f: Blob | File, +): Promise { const reader = new FileReader() - reader.readAsDataURL(f) - return new Promise(resolve => { + return new Promise((resolve, reject) => { reader.onloadend = () => { - resolve(reader.result as string) + resolve(reader.result) } + reader.readAsDataURL(f) + reader.onerror = _ => reject(new Error('error reading blob')) }) } +async function blobToDataURL(data: Blob | File): Promise { + const res = await readBlobAsDataURL(data) + if (res instanceof ArrayBuffer) + throw new Error('readBlobAsDataURL response should not be an array buffer') + if (res == null) + throw new Error('readBlobAsDataURL response should not be null') + if (typeof res === 'string') return res + throw new Error('no possible blob to data url resolution found') +} -async function readBlobToArrayBuffer(f: Blob): Promise { +async function blobToBuffer(data: Blob | File): Promise { + const res = await readBlobToArrayBuffer(data) + if (res instanceof String) + throw new Error('readBlobToArrayBuffer response should not be a string') + if (res == null) + throw new Error('readBlobToArrayBuffer response should not be null') + if (res instanceof ArrayBuffer) return res + throw new Error('no possible blob to array buffer resolution found') +} + +async function readBlobToArrayBuffer( + f: Blob | File, +): Promise { const reader = new FileReader() - reader.readAsArrayBuffer(f) - return new Promise(resolve => { + return new Promise((resolve, reject) => { reader.onloadend = () => { - resolve(reader.result as ArrayBuffer) + resolve(reader.result) } + reader.readAsArrayBuffer(f) + reader.onerror = _ => reject(new Error('error reading blob')) }) } diff --git a/frontend/projects/ui/src/app/services/api/api.fixures.ts b/frontend/projects/ui/src/app/services/api/api.fixures.ts index b5d927c64..6dba316b7 100644 --- a/frontend/projects/ui/src/app/services/api/api.fixures.ts +++ b/frontend/projects/ui/src/app/services/api/api.fixures.ts @@ -50,6 +50,14 @@ export module Mock { long: 'Bitcoin is a decentralized consensus protocol and settlement network.', }, 'release-notes': 'Taproot, Schnorr, and more.', + assets: { + icon: 'icon.png', + license: 'LICENSE.md', + instructions: 'INSTRUCTIONS.md', + docker_images: 'image.tar', + assets: './assets', + scripts: './scripts', + }, license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/bitcoind-wrapper', 'upstream-repo': 'https://github.com/bitcoin/bitcoin', @@ -348,6 +356,14 @@ export module Mock { long: 'More info about LND. More info about LND. More info about LND.', }, 'release-notes': 'Dual funded channels!', + assets: { + icon: 'icon.png', + license: 'LICENSE.md', + instructions: 'INSTRUCTIONS.md', + docker_images: 'image.tar', + assets: './assets', + scripts: './scripts', + }, license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper', 'upstream-repo': 'https://github.com/lightningnetwork/lnd', @@ -494,6 +510,14 @@ export module Mock { long: 'More info about Bitcoin Proxy. More info about Bitcoin Proxy. More info about Bitcoin Proxy.', }, 'release-notes': 'Even better support for Bitcoin and wallets!', + assets: { + icon: 'icon.png', + license: 'LICENSE.md', + instructions: 'INSTRUCTIONS.md', + docker_images: 'image.tar', + assets: './assets', + scripts: './scripts', + }, license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/btc-rpc-proxy-wrapper', 'upstream-repo': 'https://github.com/Kixunil/btc-rpc-proxy', diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index 4f23af82d..b1f8a45e1 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -249,6 +249,7 @@ export module RR { export type GetMarketplaceDataRes = MarketplaceData export type GetMarketplaceEOSReq = { + 'server-id': string 'eos-version': string } export type GetMarketplaceEOSRes = MarketplaceEOS diff --git a/frontend/projects/ui/src/app/services/api/mock-patch.ts b/frontend/projects/ui/src/app/services/api/mock-patch.ts index 48d18b084..26d3a0763 100644 --- a/frontend/projects/ui/src/app/services/api/mock-patch.ts +++ b/frontend/projects/ui/src/app/services/api/mock-patch.ts @@ -56,6 +56,14 @@ export const mockPatchData: DataModel = { long: 'Bitcoin is a decentralized consensus protocol and settlement network.', }, 'release-notes': 'Taproot, Schnorr, and more.', + assets: { + icon: 'icon.png', + license: 'LICENSE.md', + instructions: 'INSTRUCTIONS.md', + docker_images: 'image.tar', + assets: './assets', + scripts: './scripts', + }, license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/bitcoind-wrapper', 'upstream-repo': 'https://github.com/bitcoin/bitcoin', @@ -437,6 +445,14 @@ export const mockPatchData: DataModel = { long: 'More info about LND. More info about LND. More info about LND.', }, 'release-notes': 'Dual funded channels!', + assets: { + icon: 'icon.png', + license: 'LICENSE.md', + instructions: 'INSTRUCTIONS.md', + docker_images: 'image.tar', + assets: './assets', + scripts: './scripts', + }, license: 'MIT', 'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper', 'upstream-repo': 'https://github.com/lightningnetwork/lnd', diff --git a/frontend/projects/ui/src/app/services/eos.service.ts b/frontend/projects/ui/src/app/services/eos.service.ts index 2369512c3..b429145f6 100644 --- a/frontend/projects/ui/src/app/services/eos.service.ts +++ b/frontend/projects/ui/src/app/services/eos.service.ts @@ -50,15 +50,13 @@ export class EOSService { ) {} async getEOS(): Promise { - const version = this.patch.getData()['server-info'].version + const server = this.patch.getData()['server-info'] + const version = server.version this.eos = await this.api.getEos({ + 'server-id': server.id, 'eos-version': version, }) - const updateAvailable = - this.emver.compare( - this.eos.version, - this.patch.getData()['server-info'].version, - ) === 1 + const updateAvailable = this.emver.compare(this.eos.version, version) === 1 this.updateAvailable$.next(updateAvailable) return updateAvailable } diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index 2079a4392..367b9c40f 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -2,6 +2,7 @@ import { ConfigSpec } from 'src/app/pkg-config/config-types' import { Url } from '@start9labs/shared' import { MarketplaceManifest } from '@start9labs/marketplace' import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info' +import { string } from 'ts-matches' export interface DataModel { 'server-info': ServerInfo @@ -120,6 +121,14 @@ export interface CurrentDependencyInfo { } export interface Manifest extends MarketplaceManifest { + assets: { + license: string // filename + instructions: string // filename + icon: string // filename + docker_images: string // filename + assets: string // path to assets folder + scripts: string // path to scripts folder + } main: ActionImpl 'health-checks': Record< string,