refactor: improve device interface types (#996)

This commit is contained in:
Mo
2022-04-22 13:54:34 -05:00
committed by GitHub
parent 68ad0f17ae
commit abb85b3f11
22 changed files with 296 additions and 235 deletions

View File

@@ -20,15 +20,15 @@ import { IsWebPlatform, WebAppVersion } from '@/Version'
import { Runtime, SNLog } from '@standardnotes/snjs'
import { render } from 'preact'
import { ApplicationGroupView } from './Components/ApplicationGroupView'
import { Bridge } from './Services/Bridge'
import { BrowserBridge } from './Services/BrowserBridge'
import { StartApplication } from './StartApplication'
import { WebDevice } from './Device/WebDevice'
import { StartApplication } from './Device/StartApplication'
import { ApplicationGroup } from './UIModels/ApplicationGroup'
import { isDev } from './Utils'
import { WebOrDesktopDevice } from './Device/WebOrDesktopDevice'
const startApplication: StartApplication = async function startApplication(
defaultSyncServerHost: string,
bridge: Bridge,
device: WebOrDesktopDevice,
enableUnfinishedFeatures: boolean,
webSocketUrl: string,
) {
@@ -37,7 +37,7 @@ const startApplication: StartApplication = async function startApplication(
const mainApplicationGroup = new ApplicationGroup(
defaultSyncServerHost,
bridge,
device,
enableUnfinishedFeatures ? Runtime.Dev : Runtime.Prod,
webSocketUrl,
)
@@ -70,7 +70,7 @@ const startApplication: StartApplication = async function startApplication(
if (IsWebPlatform) {
startApplication(
window.defaultSyncServer,
new BrowserBridge(WebAppVersion),
new WebDevice(WebAppVersion),
window.enabledUnfinishedFeatures,
window.websocketUrl,
).catch(console.error)

View File

@@ -59,7 +59,11 @@ export class ApplicationView extends PureComponent<Props, State> {
}
async loadApplication() {
this.application.componentManager.setDesktopManager(this.application.getDesktopService())
const desktopService = this.application.getDesktopService()
if (desktopService) {
this.application.componentManager.setDesktopManager(desktopService)
}
await this.application.prepareForLaunch({
receiveChallenge: async (challenge) => {
const challenges = this.state.challenges.slice()
@@ -67,6 +71,7 @@ export class ApplicationView extends PureComponent<Props, State> {
this.setState({ challenges: challenges })
},
})
await this.application.launch()
}

View File

@@ -168,14 +168,14 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
useEffect(() => {
const unregisterDesktopObserver = application
.getDesktopService()
.registerUpdateObserver((updatedComponent: SNComponent) => {
?.registerUpdateObserver((updatedComponent: SNComponent) => {
if (updatedComponent.uuid === component.uuid && updatedComponent.active) {
requestReload?.(componentViewer)
}
})
return () => {
unregisterDesktopObserver()
unregisterDesktopObserver?.()
}
}, [application, requestReload, componentViewer, component.uuid])

View File

@@ -28,8 +28,8 @@ export const ConfirmSignoutModal = observer(({ application, appState }: Props) =
const [localBackupsCount, setLocalBackupsCount] = useState(0)
useEffect(() => {
application.bridge.localBackupsCount().then(setLocalBackupsCount).catch(console.error)
}, [appState.accountMenu.signingOut, application.bridge])
application.desktopDevice?.localBackupsCount().then(setLocalBackupsCount).catch(console.error)
}, [appState.accountMenu.signingOut, application.desktopDevice])
return (
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef}>
@@ -44,6 +44,7 @@ export const ConfirmSignoutModal = observer(({ application, appState }: Props) =
<AlertDialogDescription className="sk-panel-row">
<p className="color-foreground">{STRING_SIGN_OUT_CONFIRMATION}</p>
</AlertDialogDescription>
{localBackupsCount > 0 && (
<div className="flex">
<div className="sk-panel-row"></div>
@@ -63,13 +64,14 @@ export const ConfirmSignoutModal = observer(({ application, appState }: Props) =
<button
className="capitalize sk-a ml-1.5 p-0 rounded cursor-pointer"
onClick={() => {
application.bridge.viewlocalBackups()
application.desktopDevice?.viewlocalBackups()
}}
>
View backup files
</button>
</div>
)}
<div className="flex my-1 mt-4">
<button className="sn-button small neutral" ref={cancelRef} onClick={closeDialog}>
Cancel

View File

@@ -147,7 +147,7 @@ export class NoteView extends PureComponent<Props, State> {
this.controller = props.controller
this.onEditorComponentLoad = () => {
this.application.getDesktopService().redoSearch()
this.application.getDesktopService()?.redoSearch()
}
this.debounceReloadEditorComponent = debounce(this.debounceReloadEditorComponent.bind(this), 25)

View File

@@ -0,0 +1,11 @@
import { DeviceInterface, Environment } from '@standardnotes/snjs'
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
import { WebCommunicationReceiver } from './DesktopWebCommunication'
export function isDesktopDevice(x: DeviceInterface): x is DesktopDeviceInterface {
return x.environment === Environment.Desktop
}
export interface DesktopDeviceInterface extends WebOrDesktopDevice, WebCommunicationReceiver {
environment: Environment.Desktop
}

View File

@@ -0,0 +1 @@
export { Environment, RawKeychainValue } from '@standardnotes/snjs'

View File

@@ -0,0 +1,44 @@
import { DecryptedTransferPayload } from '@standardnotes/snjs'
/** Receives communications emitted by Web Core. This would be the Desktop client. */
export interface WebCommunicationReceiver {
localBackupsCount(): Promise<number>
viewlocalBackups(): void
deleteLocalBackups(): Promise<void>
syncComponents(payloads: unknown[]): void
onMajorDataChange(): void
onInitialDataLoad(): void
onSignOut(): void
onSearch(text?: string): void
downloadBackup(): void | Promise<void>
get extensionsServerHost(): string
}
/** Receives communications emitted by the desktop client. This would be Web Core. */
export interface DesktopCommunicationReceiver {
updateAvailable(): void
windowGainedFocus(): void
windowLostFocus(): void
onComponentInstallationComplete(
componentData: DecryptedTransferPayload,
error: unknown,
): Promise<void>
requestBackupFile(): Promise<string | undefined>
didBeginBackup(): void
didFinishBackup(success: boolean): void
}

View File

@@ -1,8 +1,8 @@
import { Bridge } from './Services/Bridge'
import { WebOrDesktopDevice } from './WebOrDesktopDevice'
export type StartApplication = (
defaultSyncServerHost: string,
bridge: Bridge,
device: WebOrDesktopDevice,
enableUnfinishedFeatures: boolean,
webSocketUrl: string,
) => Promise<void>

View File

@@ -0,0 +1,26 @@
import { Environment, RawKeychainValue } from '@standardnotes/snjs'
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
const KEYCHAIN_STORAGE_KEY = 'keychain'
export class WebDevice extends WebOrDesktopDevice {
environment = Environment.Web
async getKeychainValue(): Promise<RawKeychainValue> {
const value = localStorage.getItem(KEYCHAIN_STORAGE_KEY)
if (value) {
return JSON.parse(value)
}
return {}
}
async setKeychainValue(value: RawKeychainValue): Promise<void> {
localStorage.setItem(KEYCHAIN_STORAGE_KEY, JSON.stringify(value))
}
async clearRawKeychainValue(): Promise<void> {
localStorage.removeItem(KEYCHAIN_STORAGE_KEY)
}
}

View File

@@ -1,38 +1,60 @@
import {
getGlobalScope,
SNApplication,
ApplicationIdentifier,
AbstractDevice,
Environment,
LegacyRawKeychainValue,
RawKeychainValue,
TransferPayload,
NamespacedRootKeyInKeychain,
} from '@standardnotes/snjs'
import { Database } from '@/Database'
import { Bridge } from './Services/Bridge'
import { Database } from '../Database'
import { WebOrDesktopDeviceInterface } from './WebOrDesktopDeviceInterface'
export abstract class WebOrDesktopDevice implements WebOrDesktopDeviceInterface {
constructor(public appVersion: string) {}
export class WebDeviceInterface extends AbstractDevice {
private databases: Database[] = []
constructor(private bridge: Bridge) {
super(setTimeout.bind(getGlobalScope()), setInterval.bind(getGlobalScope()))
}
abstract environment: Environment
setApplication(application: SNApplication) {
const database = new Database(application.identifier, application.alertService)
this.databases.push(database)
}
public async getJsonParsedRawStorageValue(key: string): Promise<unknown | undefined> {
const value = await this.getRawStorageValue(key)
if (value == undefined) {
return undefined
}
try {
return JSON.parse(value)
} catch (e) {
return value
}
}
private databaseForIdentifier(identifier: ApplicationIdentifier) {
return this.databases.find((database) => database.databaseName === identifier) as Database
}
override deinit() {
super.deinit()
deinit() {
for (const database of this.databases) {
database.deinit()
}
this.databases = []
}
async getRawStorageValue(key: string) {
return localStorage.getItem(key) as any
async getRawStorageValue(key: string): Promise<string | undefined> {
const result = localStorage.getItem(key)
if (result == undefined) {
return undefined
}
return result
}
async getAllRawStorageKeyValues() {
@@ -46,7 +68,7 @@ export class WebDeviceInterface extends AbstractDevice {
return results
}
async setRawStorageValue(key: string, value: any) {
async setRawStorageValue(key: string, value: string) {
localStorage.setItem(key, value)
}
@@ -78,11 +100,11 @@ export class WebDeviceInterface extends AbstractDevice {
return this.databaseForIdentifier(identifier).getAllPayloads()
}
async saveRawDatabasePayload(payload: any, identifier: ApplicationIdentifier) {
async saveRawDatabasePayload(payload: TransferPayload, identifier: ApplicationIdentifier) {
return this.databaseForIdentifier(identifier).savePayload(payload)
}
async saveRawDatabasePayloads(payloads: any[], identifier: ApplicationIdentifier) {
async saveRawDatabasePayloads(payloads: TransferPayload[], identifier: ApplicationIdentifier) {
return this.databaseForIdentifier(identifier).savePayloads(payloads)
}
@@ -95,43 +117,44 @@ export class WebDeviceInterface extends AbstractDevice {
}
async getNamespacedKeychainValue(identifier: ApplicationIdentifier) {
const keychain = await this.getRawKeychainValue()
const keychain = await this.getKeychainValue()
if (!keychain) {
return
}
return keychain[identifier]
}
async setNamespacedKeychainValue(value: any, identifier: ApplicationIdentifier) {
let keychain = await this.getRawKeychainValue()
async setNamespacedKeychainValue(
value: NamespacedRootKeyInKeychain,
identifier: ApplicationIdentifier,
) {
let keychain = await this.getKeychainValue()
if (!keychain) {
keychain = {}
}
return this.bridge.setKeychainValue({
return this.setKeychainValue({
...keychain,
[identifier]: value,
})
}
async clearNamespacedKeychainValue(identifier: ApplicationIdentifier) {
const keychain = await this.getRawKeychainValue()
const keychain = await this.getKeychainValue()
if (!keychain) {
return
}
delete keychain[identifier]
return this.bridge.setKeychainValue(keychain)
return this.setKeychainValue(keychain)
}
getRawKeychainValue(): Promise<any> {
return this.bridge.getKeychainValue()
}
legacy_setRawKeychainValue(value: unknown): Promise<any> {
return this.bridge.setKeychainValue(value)
}
clearRawKeychainValue() {
return this.bridge.clearKeychainValue()
setRawKeychainValue(value: unknown): Promise<void> {
return this.setKeychainValue(value)
}
openUrl(url: string) {
@@ -140,4 +163,14 @@ export class WebDeviceInterface extends AbstractDevice {
win.focus()
}
}
setLegacyRawKeychainValue(value: LegacyRawKeychainValue): Promise<void> {
return this.setKeychainValue(value)
}
abstract getKeychainValue(): Promise<RawKeychainValue>
abstract setKeychainValue(value: unknown): Promise<void>
abstract clearRawKeychainValue(): Promise<void>
}

View File

@@ -0,0 +1,9 @@
import { DeviceInterface, RawKeychainValue } from '@standardnotes/snjs'
export interface WebOrDesktopDeviceInterface extends DeviceInterface {
readonly appVersion: string
getKeychainValue(): Promise<RawKeychainValue>
setKeychainValue(value: RawKeychainValue): Promise<void>
}

View File

@@ -1,37 +0,0 @@
/**
* This file will be imported by desktop, so we make sure imports are carrying
* as little extra code as possible with them.
*/
import { Environment } from '@standardnotes/snjs'
export interface ElectronDesktopCallbacks {
desktop_updateAvailable(): void
desktop_windowGainedFocus(): void
desktop_windowLostFocus(): void
desktop_onComponentInstallationComplete(componentData: any, error: any): Promise<void>
desktop_requestBackupFile(): Promise<string | undefined>
desktop_didBeginBackup(): void
desktop_didFinishBackup(success: boolean): void
}
/** Platform-specific (i-e Electron/browser) behavior is handled by a Bridge object. */
export interface Bridge {
readonly appVersion: string
environment: Environment
getKeychainValue(): Promise<unknown>
setKeychainValue(value: unknown): Promise<void>
clearKeychainValue(): Promise<void>
localBackupsCount(): Promise<number>
viewlocalBackups(): void
deleteLocalBackups(): Promise<void>
extensionsServerHost?: string
syncComponents(payloads: unknown[]): void
onMajorDataChange(): void
onInitialDataLoad(): void
onSignOut(): void
onSearch(text?: string): void
downloadBackup(): void | Promise<void>
}

View File

@@ -1,42 +0,0 @@
import { Bridge } from './Bridge'
import { Environment } from '@standardnotes/snjs'
const KEYCHAIN_STORAGE_KEY = 'keychain'
export class BrowserBridge implements Bridge {
constructor(public appVersion: string) {}
environment = Environment.Web
async getKeychainValue(): Promise<unknown> {
const value = localStorage.getItem(KEYCHAIN_STORAGE_KEY)
if (value) {
return JSON.parse(value)
}
return undefined
}
async setKeychainValue(value: unknown): Promise<void> {
localStorage.setItem(KEYCHAIN_STORAGE_KEY, JSON.stringify(value))
}
async clearKeychainValue(): Promise<void> {
localStorage.removeItem(KEYCHAIN_STORAGE_KEY)
}
async localBackupsCount(): Promise<number> {
/** Browsers cannot save backups, only let you download one */
return 0
}
/** No-ops */
/* eslint-disable @typescript-eslint/no-empty-function */
async deleteLocalBackups(): Promise<void> {}
viewlocalBackups(): void {}
syncComponents(): void {}
onMajorDataChange(): void {}
onInitialDataLoad(): void {}
onSearch(): void {}
downloadBackup(): void {}
onSignOut(): void {}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable camelcase */
import {
SNComponent,
ComponentMutator,
@@ -8,27 +7,26 @@ import {
removeFromArray,
DesktopManagerInterface,
InternalEventBus,
DecryptedTransferPayload,
ComponentContent,
assert,
} from '@standardnotes/snjs'
import { WebAppEvent, WebApplication } from '@/UIModels/Application'
import { isDesktopApplication } from '@/Utils'
import { Bridge, ElectronDesktopCallbacks } from './Bridge'
import { DesktopDeviceInterface } from '../Device/DesktopDeviceInterface'
import { DesktopCommunicationReceiver } from '@/Device/DesktopWebCommunication'
/**
* An interface used by the Desktop application to interact with SN
*/
export class DesktopManager
extends ApplicationService
implements DesktopManagerInterface, ElectronDesktopCallbacks
implements DesktopManagerInterface, DesktopCommunicationReceiver
{
updateObservers: {
callback: (component: SNComponent) => void
}[] = []
isDesktop = isDesktopApplication()
dataLoaded = false
lastSearchedText?: string
constructor(application: WebApplication, private bridge: Bridge) {
constructor(application: WebApplication, private device: DesktopDeviceInterface) {
super(application, new InternalEventBus())
}
@@ -45,19 +43,20 @@ export class DesktopManager
super.onAppEvent(eventName).catch(console.error)
if (eventName === ApplicationEvent.LocalDataLoaded) {
this.dataLoaded = true
this.bridge.onInitialDataLoad()
this.device.onInitialDataLoad()
} else if (eventName === ApplicationEvent.MajorDataChange) {
this.bridge.onMajorDataChange()
this.device.onMajorDataChange()
}
}
saveBackup() {
this.bridge.onMajorDataChange()
this.device.onMajorDataChange()
}
getExtServerHost(): string {
console.assert(!!this.bridge.extensionsServerHost, 'extServerHost is null')
return this.bridge.extensionsServerHost as string
assert(this.device.extensionsServerHost)
return this.device.extensionsServerHost
}
/**
@@ -70,17 +69,13 @@ export class DesktopManager
// All `components` should be installed
syncComponentsInstallation(components: SNComponent[]) {
if (!this.isDesktop) {
return
}
Promise.all(
components.map((component) => {
return this.convertComponentForTransmission(component)
}),
)
.then((payloads) => {
this.bridge.syncComponents(payloads)
this.device.syncComponents(payloads)
})
.catch(console.error)
}
@@ -96,11 +91,8 @@ export class DesktopManager
}
searchText(text?: string) {
if (!this.isDesktop) {
return
}
this.lastSearchedText = text
this.bridge.onSearch(text)
this.device.onSearch(text)
}
redoSearch() {
@@ -109,23 +101,27 @@ export class DesktopManager
}
}
desktop_updateAvailable(): void {
updateAvailable(): void {
this.webApplication.notifyWebEvent(WebAppEvent.NewUpdateAvailable)
}
desktop_windowGainedFocus(): void {
windowGainedFocus(): void {
this.webApplication.notifyWebEvent(WebAppEvent.DesktopWindowGainedFocus)
}
desktop_windowLostFocus(): void {
windowLostFocus(): void {
this.webApplication.notifyWebEvent(WebAppEvent.DesktopWindowLostFocus)
}
async desktop_onComponentInstallationComplete(componentData: any, error: any) {
async onComponentInstallationComplete(
componentData: DecryptedTransferPayload<ComponentContent>,
error: unknown,
) {
const component = this.application.items.findItem(componentData.uuid)
if (!component) {
return
}
const updatedComponent = await this.application.mutator.changeAndSaveItem(
component,
(m) => {
@@ -133,7 +129,9 @@ export class DesktopManager
if (error) {
mutator.setAppDataItem(AppDataField.ComponentInstallError, error)
} else {
mutator.local_url = componentData.content.local_url
// eslint-disable-next-line camelcase
mutator.local_url = componentData.content.local_url as string
// eslint-disable-next-line camelcase
mutator.package_info = componentData.content.package_info
mutator.setAppDataItem(AppDataField.ComponentInstallError, undefined)
}
@@ -146,7 +144,7 @@ export class DesktopManager
}
}
async desktop_requestBackupFile(): Promise<string | undefined> {
async requestBackupFile(): Promise<string | undefined> {
const encrypted = this.application.hasProtectionSources()
const data = encrypted
? await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
@@ -159,11 +157,11 @@ export class DesktopManager
return undefined
}
desktop_didBeginBackup() {
didBeginBackup() {
this.webApplication.getAppState().beganBackupDownload()
}
desktop_didFinishBackup(success: boolean) {
didFinishBackup(success: boolean) {
this.webApplication.getAppState().endedBackupDownload(success)
}
}

View File

@@ -22,7 +22,7 @@ const DefaultThemeIdentifier = 'Default'
export class ThemeManager extends ApplicationService {
private activeThemes: UuidString[] = []
private unregisterDesktop!: () => void
private unregisterDesktop?: () => void
private unregisterStream!: () => void
private lastUseDeviceThemeSettings = false
@@ -91,10 +91,12 @@ export class ThemeManager extends ApplicationService {
override deinit() {
this.activeThemes.length = 0
this.unregisterDesktop()
this.unregisterDesktop?.()
this.unregisterStream()
;(this.unregisterDesktop as unknown) = undefined
;(this.unregisterStream as unknown) = undefined
window
.matchMedia('(prefers-color-scheme: dark)')
.removeEventListener('change', this.colorSchemeEventHandler)
@@ -212,7 +214,7 @@ export class ThemeManager extends ApplicationService {
private registerObservers() {
this.unregisterDesktop = this.webApplication
.getDesktopService()
.registerUpdateObserver((component) => {
?.registerUpdateObserver((component) => {
if (component.active && component.isTheme()) {
this.deactivateTheme(component.uuid)
setTimeout(() => {

View File

@@ -1,4 +1,3 @@
import { Bridge } from '@/Services/Bridge'
import { storage, StorageKey } from '@/Services/LocalStorage'
import { WebApplication, WebAppEvent } from '@/UIModels/Application'
import { AccountMenuState } from '@/UIModels/AppState/AccountMenuState'
@@ -32,6 +31,7 @@ import { SearchOptionsState } from './SearchOptionsState'
import { SubscriptionState } from './SubscriptionState'
import { SyncState } from './SyncState'
import { TagsState } from './TagsState'
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
export enum AppStateEvent {
TagChanged,
@@ -91,7 +91,7 @@ export class AppState {
private readonly tagChangedDisposer: IReactionDisposer
constructor(application: WebApplication, private bridge: Bridge) {
constructor(application: WebApplication, private device: WebOrDesktopDevice) {
this.application = application
this.notes = new NotesState(
application,
@@ -120,7 +120,7 @@ export class AppState {
}
this.registerVisibilityObservers()
if (this.bridge.appVersion.includes('-beta')) {
if (this.device.appVersion.includes('-beta')) {
this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true
} else {
this.showBetaWarning = false
@@ -198,7 +198,7 @@ export class AppState {
}
public get version(): string {
return this.bridge.appVersion
return this.device.appVersion
}
async openNewNote(title?: string) {

View File

@@ -379,8 +379,9 @@ export class NotesViewState {
paginate = () => {
this.notesToDisplay += this.pageSize
this.reloadNotes()
if (this.searchSubmitted) {
this.application.getDesktopService().searchText(this.noteFilterText)
this.application.getDesktopService()?.searchText(this.noteFilterText)
}
}
@@ -482,8 +483,9 @@ export class NotesViewState {
})
.catch(console.error)
}
if (this.isFiltering) {
this.application.getDesktopService().searchText(this.noteFilterText)
this.application.getDesktopService()?.searchText(this.noteFilterText)
}
}
@@ -496,9 +498,13 @@ export class NotesViewState {
handleTagChange = () => {
this.resetScrollPosition()
this.setShowDisplayOptionsMenu(false)
this.setNoteFilterText('')
this.application.getDesktopService().searchText()
this.application.getDesktopService()?.searchText()
this.resetPagination()
/* Capture db load state before beginning reloadNotes,
@@ -525,7 +531,8 @@ export class NotesViewState {
* enter before highlighting desktop search results.
*/
this.searchSubmitted = true
this.application.getDesktopService().searchText(this.noteFilterText)
this.application.getDesktopService()?.searchText(this.noteFilterText)
}
handleFilterTextChanged = () => {

View File

@@ -2,13 +2,13 @@ import { WebCrypto } from '@/Crypto'
import { AlertService } from '@/Services/AlertService'
import { ArchiveManager } from '@/Services/ArchiveManager'
import { AutolockService } from '@/Services/AutolockService'
import { Bridge } from '@/Services/Bridge'
import { DesktopDeviceInterface, isDesktopDevice } from '@/Device/DesktopDeviceInterface'
import { DesktopManager } from '@/Services/DesktopManager'
import { IOService } from '@/Services/IOService'
import { StatusManager } from '@/Services/StatusManager'
import { ThemeManager } from '@/Services/ThemeManager'
import { AppState } from '@/UIModels/AppState'
import { WebDeviceInterface } from '@/WebDeviceInterface'
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
import {
DeinitSource,
Platform,
@@ -21,7 +21,7 @@ import {
type WebServices = {
appState: AppState
desktopService: DesktopManager
desktopService?: DesktopManager
autolockService: AutolockService
archiveService: ArchiveManager
statusManager: StatusManager
@@ -44,26 +44,26 @@ export class WebApplication extends SNApplication {
public iconsController: IconsController
constructor(
deviceInterface: WebDeviceInterface,
deviceInterface: WebOrDesktopDevice,
platform: Platform,
identifier: string,
defaultSyncServerHost: string,
public bridge: Bridge,
webSocketUrl: string,
runtime: Runtime,
) {
super({
environment: bridge.environment,
environment: deviceInterface.environment,
platform: platform,
deviceInterface: deviceInterface,
crypto: WebCrypto,
alertService: new AlertService(),
identifier,
defaultHost: defaultSyncServerHost,
appVersion: bridge.appVersion,
appVersion: deviceInterface.appVersion,
webSocketUrl: webSocketUrl,
runtime,
})
deviceInterface.setApplication(this)
this.noteControllerGroup = new NoteGroupController(this)
this.iconsController = new IconsController()
@@ -80,7 +80,7 @@ export class WebApplication extends SNApplication {
if ('deinit' in service) {
service.deinit?.(source)
}
;(service as any).application = undefined
;(service as { application?: WebApplication }).application = undefined
}
this.webServices = {} as WebServices
@@ -88,7 +88,7 @@ export class WebApplication extends SNApplication {
this.webEventObservers.length = 0
if (source === DeinitSource.SignOut) {
this.bridge.onSignOut()
isDesktopDevice(this.deviceInterface) && this.deviceInterface.onSignOut()
}
} catch (error) {
console.error('Error while deiniting application', error)
@@ -116,7 +116,7 @@ export class WebApplication extends SNApplication {
return this.webServices.appState
}
public getDesktopService(): DesktopManager {
public getDesktopService(): DesktopManager | undefined {
return this.webServices.desktopService
}
@@ -128,6 +128,14 @@ export class WebApplication extends SNApplication {
return this.webServices.archiveService
}
public get desktopDevice(): DesktopDeviceInterface | undefined {
if (isDesktopDevice(this.deviceInterface)) {
return this.deviceInterface
}
return undefined
}
getStatusManager() {
return this.webServices.statusManager
}
@@ -145,11 +153,14 @@ export class WebApplication extends SNApplication {
}
downloadBackup(): void | Promise<void> {
return this.bridge.downloadBackup()
if (isDesktopDevice(this.deviceInterface)) {
return this.deviceInterface.downloadBackup()
}
}
async signOutAndDeleteLocalBackups(): Promise<void> {
await this.bridge.deleteLocalBackups()
isDesktopDevice(this.deviceInterface) && (await this.deviceInterface.deleteLocalBackups())
return this.user.signOut()
}
}

View File

@@ -1,15 +1,12 @@
import { WebDeviceInterface } from '@/WebDeviceInterface'
import { WebApplication } from './Application'
import {
ApplicationDescriptor,
SNApplicationGroup,
DeviceInterface,
Platform,
Runtime,
InternalEventBus,
} from '@standardnotes/snjs'
import { AppState } from '@/UIModels/AppState'
import { Bridge } from '@/Services/Bridge'
import { getPlatform, isDesktopApplication } from '@/Utils'
import { ArchiveManager } from '@/Services/ArchiveManager'
import { DesktopManager } from '@/Services/DesktopManager'
@@ -17,15 +14,17 @@ import { IOService } from '@/Services/IOService'
import { AutolockService } from '@/Services/AutolockService'
import { StatusManager } from '@/Services/StatusManager'
import { ThemeManager } from '@/Services/ThemeManager'
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
import { isDesktopDevice } from '@/Device/DesktopDeviceInterface'
export class ApplicationGroup extends SNApplicationGroup {
export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
constructor(
private defaultSyncServerHost: string,
private bridge: Bridge,
private device: WebOrDesktopDevice,
private runtime: Runtime,
private webSocketUrl: string,
) {
super(new WebDeviceInterface(bridge))
super(device)
}
override async initialize(): Promise<void> {
@@ -34,7 +33,7 @@ export class ApplicationGroup extends SNApplicationGroup {
})
if (isDesktopApplication()) {
Object.defineProperty(window, 'desktopManager', {
Object.defineProperty(window, 'desktopCommunicationReceiver', {
get: () => (this.primaryApplication as WebApplication).getDesktopService(),
})
}
@@ -42,34 +41,36 @@ export class ApplicationGroup extends SNApplicationGroup {
private createApplication = (
descriptor: ApplicationDescriptor,
deviceInterface: DeviceInterface,
deviceInterface: WebOrDesktopDevice,
) => {
const platform = getPlatform()
const application = new WebApplication(
deviceInterface as WebDeviceInterface,
deviceInterface,
platform,
descriptor.identifier,
this.defaultSyncServerHost,
this.bridge,
this.webSocketUrl,
this.runtime,
)
const appState = new AppState(application, this.bridge)
const appState = new AppState(application, this.device)
const archiveService = new ArchiveManager(application)
const desktopService = new DesktopManager(application, this.bridge)
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
const autolockService = new AutolockService(application, new InternalEventBus())
const statusManager = new StatusManager()
const themeService = new ThemeManager(application)
application.setWebServices({
appState,
archiveService,
desktopService,
desktopService: isDesktopDevice(this.device)
? new DesktopManager(application, this.device)
: undefined,
io,
autolockService,
statusManager,
themeService,
})
return application
}
}