mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
don't be so fragile when comparing marketplace URLs (#2040)
* don't be so fragile when comparing marketplace URLs * handle more edges * minor * clean up a little
This commit is contained in:
@@ -10,14 +10,6 @@ export function pauseFor(ms: number): Promise<void> {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function capitalizeFirstLetter(string: string): string {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function exists<T>(t: T | undefined): t is T {
|
|
||||||
return t !== undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function debounce(delay: number = 300): MethodDecorator {
|
export function debounce(delay: number = 300): MethodDecorator {
|
||||||
return function (
|
return function (
|
||||||
target: any,
|
target: any,
|
||||||
@@ -37,13 +29,16 @@ export function debounce(delay: number = 300): MethodDecorator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeTrailingSlash(word: string): string {
|
export function sameUrl(
|
||||||
return word.replace(/\/+$/, '')
|
u1: string | null | undefined,
|
||||||
|
u2: string | null | undefined,
|
||||||
|
): boolean {
|
||||||
|
return toUrl(u1) === toUrl(u2)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidHttpUrl(string: string): boolean {
|
export function isValidHttpUrl(url: string): boolean {
|
||||||
try {
|
try {
|
||||||
const _ = new URL(string)
|
const _ = new URL(url)
|
||||||
return true
|
return true
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false
|
return false
|
||||||
@@ -53,3 +48,12 @@ export function isValidHttpUrl(string: string): boolean {
|
|||||||
export function getUrlHostname(url: string): string {
|
export function getUrlHostname(url: string): string {
|
||||||
return new URL(url).hostname
|
return new URL(url).hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toUrl(text: string | null | undefined): string {
|
||||||
|
try {
|
||||||
|
const url = new URL(text as string)
|
||||||
|
return url.toString()
|
||||||
|
} catch {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
PipeTransform,
|
PipeTransform,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
|
import { sameUrl } from '@start9labs/shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'store-icon',
|
selector: 'store-icon',
|
||||||
@@ -26,9 +27,9 @@ export class GetIconPipe implements PipeTransform {
|
|||||||
transform(url: string): string | null {
|
transform(url: string): string | null {
|
||||||
const { start9, community } = this.config.marketplace
|
const { start9, community } = this.config.marketplace
|
||||||
|
|
||||||
if (url === start9) {
|
if (sameUrl(url, start9)) {
|
||||||
return 'assets/img/icon.png'
|
return 'assets/img/icon.png'
|
||||||
} else if (url === community) {
|
} else if (sameUrl(url, community)) {
|
||||||
return 'assets/img/community-store.png'
|
return 'assets/img/community-store.png'
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { endWith, Observable } from 'rxjs'
|
import { endWith, Observable } from 'rxjs'
|
||||||
import { filter, map, pairwise } from 'rxjs/operators'
|
import { map, pairwise } from 'rxjs/operators'
|
||||||
import { exists } from '@start9labs/shared'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@@ -10,7 +9,6 @@ export class NotificationsToastService extends Observable<boolean> {
|
|||||||
private readonly stream$ = this.patch
|
private readonly stream$ = this.patch
|
||||||
.watch$('server-info', 'unread-notification-count')
|
.watch$('server-info', 'unread-notification-count')
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(exists),
|
|
||||||
pairwise(),
|
pairwise(),
|
||||||
map(([prev, cur]) => cur > prev),
|
map(([prev, cur]) => cur > prev),
|
||||||
endWith(false),
|
endWith(false),
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import {
|
|||||||
ModalController,
|
ModalController,
|
||||||
} from '@ionic/angular'
|
} from '@ionic/angular'
|
||||||
import { ActionSheetButton } from '@ionic/core'
|
import { ActionSheetButton } from '@ionic/core'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import {
|
||||||
|
ErrorToastService,
|
||||||
|
isValidHttpUrl,
|
||||||
|
sameUrl,
|
||||||
|
toUrl,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
||||||
@@ -31,7 +36,7 @@ export class MarketplaceSettingsPage {
|
|||||||
map(([stores, selected]) => {
|
map(([stores, selected]) => {
|
||||||
const toSlice = stores.map(s => ({
|
const toSlice = stores.map(s => ({
|
||||||
...s,
|
...s,
|
||||||
selected: s.url === selected.url,
|
selected: sameUrl(s.url, selected.url),
|
||||||
}))
|
}))
|
||||||
// 0 and 1 are prod and community
|
// 0 and 1 are prod and community
|
||||||
const standard = toSlice.slice(0, 1)
|
const standard = toSlice.slice(0, 1)
|
||||||
@@ -69,13 +74,13 @@ export class MarketplaceSettingsPage {
|
|||||||
{
|
{
|
||||||
text: 'Save for Later',
|
text: 'Save for Later',
|
||||||
handler: (value: { url: string }) => {
|
handler: (value: { url: string }) => {
|
||||||
this.saveOnly(new URL(value.url))
|
this.saveOnly(value.url)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Save and Connect',
|
text: 'Save and Connect',
|
||||||
handler: (value: { url: string }) => {
|
handler: (value: { url: string }) => {
|
||||||
this.saveAndConnect(new URL(value.url))
|
this.saveAndConnect(value.url)
|
||||||
},
|
},
|
||||||
isSubmit: true,
|
isSubmit: true,
|
||||||
},
|
},
|
||||||
@@ -160,10 +165,11 @@ export class MarketplaceSettingsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveOnly(url: URL): Promise<void> {
|
private async saveOnly(rawUrl: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create()
|
const loader = await this.loadingCtrl.create()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const url = new URL(rawUrl).toString()
|
||||||
await this.validateAndSave(url, loader)
|
await this.validateAndSave(url, loader)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
@@ -172,12 +178,13 @@ export class MarketplaceSettingsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveAndConnect(url: URL): Promise<void> {
|
private async saveAndConnect(rawUrl: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create()
|
const loader = await this.loadingCtrl.create()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const url = new URL(rawUrl).toString()
|
||||||
await this.validateAndSave(url, loader)
|
await this.validateAndSave(url, loader)
|
||||||
await this.connect(url.toString(), loader)
|
await this.connect(url, loader)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -186,15 +193,14 @@ export class MarketplaceSettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async validateAndSave(
|
private async validateAndSave(
|
||||||
urlObj: URL,
|
url: string,
|
||||||
loader: HTMLIonLoadingElement,
|
loader: HTMLIonLoadingElement,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const url = urlObj.toString()
|
|
||||||
// Error on duplicates
|
// Error on duplicates
|
||||||
const hosts = await firstValueFrom(
|
const hosts = await firstValueFrom(
|
||||||
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
||||||
)
|
)
|
||||||
const currentUrls = Object.keys(hosts)
|
const currentUrls = Object.keys(hosts).map(toUrl)
|
||||||
if (currentUrls.includes(url)) throw new Error('marketplace already added')
|
if (currentUrls.includes(url)) throw new Error('marketplace already added')
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
@@ -225,7 +231,7 @@ export class MarketplaceSettingsPage {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const filtered: { [url: string]: UIStore } = Object.keys(hosts)
|
const filtered: { [url: string]: UIStore } = Object.keys(hosts)
|
||||||
.filter(key => key !== url)
|
.filter(key => !sameUrl(key, url))
|
||||||
.reduce((prev, curr) => {
|
.reduce((prev, curr) => {
|
||||||
const name = hosts[curr]
|
const name = hosts[curr]
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import {
|
|||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { Emver, ErrorToastService, isEmptyObject } from '@start9labs/shared'
|
import {
|
||||||
|
Emver,
|
||||||
|
ErrorToastService,
|
||||||
|
isEmptyObject,
|
||||||
|
sameUrl,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
@@ -71,7 +76,7 @@ export class MarketplaceShowControlsComponent {
|
|||||||
} else {
|
} else {
|
||||||
const originalUrl = this.localPkg.installed?.['marketplace-url']
|
const originalUrl = this.localPkg.installed?.['marketplace-url']
|
||||||
|
|
||||||
if (url !== originalUrl) {
|
if (!sameUrl(url, originalUrl)) {
|
||||||
const proceed = await this.presentAlertDifferentMarketplace(
|
const proceed = await this.presentAlertDifferentMarketplace(
|
||||||
url,
|
url,
|
||||||
originalUrl,
|
originalUrl,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
StoreIdentity,
|
StoreIdentity,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { Emver, isEmptyObject } from '@start9labs/shared'
|
import { Emver, isEmptyObject, sameUrl } from '@start9labs/shared'
|
||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { combineLatest, Observable } from 'rxjs'
|
import { combineLatest, Observable } from 'rxjs'
|
||||||
import { PrimaryRendering } from '../../services/pkg-status-rendering.service'
|
import { PrimaryRendering } from '../../services/pkg-status-rendering.service'
|
||||||
@@ -194,7 +194,8 @@ export function marketplaceSame(
|
|||||||
local: Record<string, PackageDataEntry>,
|
local: Record<string, PackageDataEntry>,
|
||||||
url: string,
|
url: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
return local[id]?.installed?.['marketplace-url'] === url
|
const localUrl = local[id]?.installed?.['marketplace-url']
|
||||||
|
return sameUrl(localUrl, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function versionLower(
|
export function versionLower(
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
tap,
|
tap,
|
||||||
} from 'rxjs/operators'
|
} from 'rxjs/operators'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
|
import { sameUrl } from '@start9labs/shared'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MarketplaceService implements AbstractMarketplaceService {
|
export class MarketplaceService implements AbstractMarketplaceService {
|
||||||
@@ -68,7 +69,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
|
|||||||
startWith<StoreIdentity[]>([]),
|
startWith<StoreIdentity[]>([]),
|
||||||
pairwise(),
|
pairwise(),
|
||||||
mergeMap(([prev, curr]) =>
|
mergeMap(([prev, curr]) =>
|
||||||
curr.filter(c => !prev.find(p => c.url === p.url)),
|
curr.filter(c => !prev.find(p => sameUrl(c.url, p.url))),
|
||||||
),
|
),
|
||||||
mergeMap(({ url, name }) =>
|
mergeMap(({ url, name }) =>
|
||||||
this.fetchStore$(url).pipe(
|
this.fetchStore$(url).pipe(
|
||||||
|
|||||||
Reference in New Issue
Block a user