refactor: improve device interface types (#996)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
11
app/assets/javascripts/Device/DesktopDeviceInterface.ts
Normal file
11
app/assets/javascripts/Device/DesktopDeviceInterface.ts
Normal 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
|
||||
}
|
||||
1
app/assets/javascripts/Device/DesktopSnjsExports.ts
Normal file
1
app/assets/javascripts/Device/DesktopSnjsExports.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Environment, RawKeychainValue } from '@standardnotes/snjs'
|
||||
44
app/assets/javascripts/Device/DesktopWebCommunication.ts
Normal file
44
app/assets/javascripts/Device/DesktopWebCommunication.ts
Normal 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
|
||||
}
|
||||
@@ -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>
|
||||
26
app/assets/javascripts/Device/WebDevice.ts
Normal file
26
app/assets/javascripts/Device/WebDevice.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user