feat: handle basic routes (#1784)

This commit is contained in:
Mo
2022-10-13 09:08:03 -05:00
committed by GitHub
parent 794ed7f7d4
commit 3cb016ab1f
27 changed files with 391 additions and 140 deletions

View File

@@ -0,0 +1,14 @@
const PREFERENCE_IDS = [
'general',
'account',
'security',
'appearance',
'backups',
'listed',
'shortcuts',
'accessibility',
'get-free-month',
'help-feedback',
] as const
export type PreferenceId = typeof PREFERENCE_IDS[number]

View File

@@ -0,0 +1,18 @@
import { PreferenceId } from '../Preferences/PreferenceId'
export type OnboardingParams = {
fromHomepage: boolean
}
export type SettingsParams = {
panel: PreferenceId
}
export type DemoParams = {
token: string
}
export type PurchaseParams = {
plan: string
period: string
}

View File

@@ -0,0 +1,51 @@
import { RouteParser } from './RouteParser'
import { RouteType } from './RouteType'
describe('route parser', () => {
it('routes to onboarding', () => {
const url = 'https://app.standardnotes.com/onboard?from_homepage=true'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.Onboarding)
expect(parser.onboardingParams.fromHomepage).toEqual(true)
})
it('routes to demo', () => {
const url = 'https://app-demo.standardnotes.com/?demo-token=eyJhY2Nlc3NUb2tl'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.Demo)
expect(parser.demoParams.token).toEqual('eyJhY2Nlc3NUb2tl')
})
it('routes to settings', () => {
const url = 'https://app.standardnotes.com/?settings=account'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.Settings)
expect(parser.settingsParams.panel).toEqual('account')
})
it('routes to purchase', () => {
const url = 'https://app.standardnotes.com/?purchase=true&plan=PLUS_PLAN&period=year'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.Purchase)
expect(parser.purchaseParams.period).toEqual('year')
expect(parser.purchaseParams.plan).toEqual('PLUS_PLAN')
})
it('routes to none', () => {
const url = 'https://app.standardnotes.com/unknown?foo=bar'
const parser = new RouteParser(url)
expect(parser.type).toEqual(RouteType.None)
})
it('accessing wrong params should throw', () => {
const url = 'https://app.standardnotes.com/item?uuid=123'
const parser = new RouteParser(url)
expect(() => parser.onboardingParams).toThrowError('Accessing invalid params')
})
})

View File

@@ -0,0 +1,88 @@
import { PreferenceId } from './../Preferences/PreferenceId'
import { DemoParams, OnboardingParams, PurchaseParams, SettingsParams } from './RouteParams'
import { RouteType } from './RouteType'
enum RootRoutes {
Onboarding = '/onboard',
None = '/',
}
enum RootQueryParam {
Purchase = 'purchase',
Settings = 'settings',
DemoToken = 'demo-token',
}
export class RouteParser {
private url: URL
private readonly path: string
public readonly type: RouteType
private readonly searchParams: URLSearchParams
constructor(url: string) {
this.url = new URL(url)
this.path = this.url.pathname
this.searchParams = this.url.searchParams
const pathUsesRootQueryParams = this.path === RootRoutes.None
if (pathUsesRootQueryParams) {
if (this.searchParams.has(RootQueryParam.Purchase)) {
this.type = RouteType.Purchase
} else if (this.searchParams.has(RootQueryParam.Settings)) {
this.type = RouteType.Settings
} else if (this.searchParams.has(RootQueryParam.DemoToken)) {
this.type = RouteType.Demo
} else {
this.type = RouteType.None
}
} else {
if (this.path === RootRoutes.Onboarding) {
this.type = RouteType.Onboarding
} else {
this.type = RouteType.None
}
}
}
get demoParams(): DemoParams {
if (this.type !== RouteType.Demo) {
throw new Error('Accessing invalid params')
}
return {
token: this.searchParams.get(RootQueryParam.DemoToken) as string,
}
}
get settingsParams(): SettingsParams {
if (this.type !== RouteType.Settings) {
throw new Error('Accessing invalid params')
}
return {
panel: this.searchParams.get(RootQueryParam.Settings) as PreferenceId,
}
}
get purchaseParams(): PurchaseParams {
if (this.type !== RouteType.Purchase) {
throw new Error('Accessing invalid params')
}
return {
plan: this.searchParams.get('plan') as string,
period: this.searchParams.get('period') as string,
}
}
get onboardingParams(): OnboardingParams {
if (this.type !== RouteType.Onboarding) {
throw new Error('Accessing invalid params')
}
return {
fromHomepage: !!this.searchParams.get('from_homepage'),
}
}
}

View File

@@ -0,0 +1,50 @@
import {
AbstractService,
ApplicationEvent,
ApplicationInterface,
InternalEventBusInterface,
} from '@standardnotes/services'
import { RouteParser } from './RouteParser'
import { RouteServiceEvent } from './RouteServiceEvent'
export class RouteService extends AbstractService<RouteServiceEvent, RouteParser> {
private unsubApp!: () => void
constructor(
private application: ApplicationInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
this.addAppEventObserver()
}
override deinit() {
super.deinit()
;(this.application as unknown) = undefined
this.unsubApp()
}
public getRoute(): RouteParser {
return new RouteParser(window.location.href)
}
public removeSettingsFromURLQueryParameters() {
const urlSearchParams = new URLSearchParams(window.location.search)
urlSearchParams.delete('settings')
const newUrl = `${window.location.origin}${window.location.pathname}${urlSearchParams.toString()}`
window.history.replaceState(null, document.title, newUrl)
}
private addAppEventObserver() {
this.unsubApp = this.application.addEventObserver(async (event: ApplicationEvent) => {
if (event === ApplicationEvent.LocalDataLoaded) {
void this.notifyRouteChange()
}
})
}
private notifyRouteChange() {
void this.notifyEvent(RouteServiceEvent.RouteChanged, this.getRoute())
}
}

View File

@@ -0,0 +1,3 @@
export enum RouteServiceEvent {
RouteChanged = 'route-changed',
}

View File

@@ -0,0 +1,7 @@
export enum RouteType {
Onboarding = 'onboarding',
Settings = 'settings',
Purchase = 'purchase',
Demo = 'demo',
None = 'none',
}

View File

@@ -2,6 +2,12 @@ export * from './Alert/Functions'
export * from './Alert/WebAlertService'
export * from './Archive/ArchiveManager'
export * from './IO/IOService'
export * from './Preferences/PreferenceId'
export * from './Route/RouteParams'
export * from './Route/RouteParser'
export * from './Route/RouteType'
export * from './Route/RouteService'
export * from './Route/RouteServiceEvent'
export * from './Security/AutolockService'
export * from './Storage/LocalStorage'
export * from './Theme/ThemeManager'