import { SNComponent, PurePayload, ComponentMutator, AppDataField } from 'snjs'; /* eslint-disable camelcase */ import { WebApplication } from '@/ui_models/application'; // An interface used by the Desktop app to interact with SN import { isDesktopApplication } from '@/utils'; import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from 'snjs'; type UpdateObserverCallback = (component: SNComponent) => void type ComponentActivationCallback = (payload: PurePayload) => void type ComponentActivationObserver = { id: string, callback: ComponentActivationCallback } export class DesktopManager extends ApplicationService { $rootScope: ng.IRootScopeService $timeout: ng.ITimeoutService componentActivationObservers: ComponentActivationObserver[] = [] updateObservers: { callback: UpdateObserverCallback }[] = [] isDesktop = isDesktopApplication(); dataLoaded = false dataLoadHandler?: () => void majorDataChangeHandler?: () => void extServerHost?: string installationSyncHandler?: (payloads: PurePayload[]) => void installComponentHandler?: (payload: PurePayload) => void lastSearchedText?: string searchHandler?: (text?: string) => void constructor( $rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, application: WebApplication ) { super(application); this.$rootScope = $rootScope; this.$timeout = $timeout; } get webApplication() { return this.application as WebApplication; } deinit() { this.componentActivationObservers.length = 0; this.updateObservers.length = 0; super.deinit(); } async onAppEvent(eventName: ApplicationEvent) { super.onAppEvent(eventName); if (eventName === ApplicationEvent.LocalDataLoaded) { this.dataLoaded = true; if (this.dataLoadHandler) { this.dataLoadHandler(); } } else if (eventName === ApplicationEvent.MajorDataChange) { if (this.majorDataChangeHandler) { this.majorDataChangeHandler(); } } } saveBackup() { this.majorDataChangeHandler && this.majorDataChangeHandler(); } getExtServerHost() { console.assert( this.extServerHost, 'extServerHost is null' ); return this.extServerHost; } /** * Sending a component in its raw state is really slow for the desktop app * Keys are not passed into ItemParams, so the result is not encrypted */ async convertComponentForTransmission(component: SNComponent) { return this.application!.protocolService!.payloadByEncryptingPayload( component.payloadRepresentation(), EncryptionIntent.FileDecrypted ); } // All `components` should be installed syncComponentsInstallation(components: SNComponent[]) { if (!this.isDesktop) { return; } Promise.all(components.map((component) => { return this.convertComponentForTransmission(component); })).then((payloads) => { this.installationSyncHandler!(payloads); }); } async installComponent(component: SNComponent) { this.installComponentHandler!( await this.convertComponentForTransmission(component) ); } registerUpdateObserver(callback: UpdateObserverCallback) { const observer = { callback: callback }; this.updateObservers.push(observer); return () => { removeFromArray(this.updateObservers, observer); }; } searchText(text?: string) { if (!this.isDesktop) { return; } this.lastSearchedText = text; this.searchHandler && this.searchHandler(text); } redoSearch() { if (this.lastSearchedText) { this.searchText(this.lastSearchedText); } } // Pass null to cancel search desktop_setSearchHandler(handler: (text?: string) => void) { this.searchHandler = handler; } desktop_windowGainedFocus() { this.$rootScope.$broadcast('window-gained-focus'); } desktop_windowLostFocus() { this.$rootScope.$broadcast('window-lost-focus'); } async desktop_onComponentInstallationComplete( componentData: any, error: any ) { const component = this.application!.findItem(componentData.uuid); if (!component) { return; } const updatedComponent = await this.application!.changeAndSaveItem( component.uuid, (m) => { const mutator = m as ComponentMutator; if (error) { mutator.setAppDataItem( AppDataField.ComponentInstallError, error ); } else { mutator.local_url = componentData.content.local_url; mutator.package_info = componentData.content.package_info; mutator.setAppDataItem( AppDataField.ComponentInstallError, undefined ); } }) this.$timeout(() => { for (const observer of this.updateObservers) { observer.callback(updatedComponent as SNComponent); } }); } desktop_registerComponentActivationObserver(callback: ComponentActivationCallback) { const observer = { id: `${Math.random}`, callback: callback }; this.componentActivationObservers.push(observer); return observer; } desktop_deregisterComponentActivationObserver(observer: ComponentActivationObserver) { removeFromArray(this.componentActivationObservers, observer); } /* Notify observers that a component has been registered/activated */ async notifyComponentActivation(component: SNComponent) { const serializedComponent = await this.convertComponentForTransmission( component ); this.$timeout(() => { for (const observer of this.componentActivationObservers) { observer.callback(serializedComponent); } }); } /* Used to resolve 'sn://' */ desktop_setExtServerHost(host: string) { this.extServerHost = host; this.webApplication.getAppState().desktopExtensionsReady(); } desktop_setComponentInstallationSyncHandler(handler: (payloads: PurePayload[]) => void) { this.installationSyncHandler = handler; } desktop_setInstallComponentHandler(handler: (payload: PurePayload) => void) { this.installComponentHandler = handler; } desktop_setInitialDataLoadHandler(handler: () => void) { this.dataLoadHandler = handler; if (this.dataLoaded) { this.dataLoadHandler(); } } async desktop_requestBackupFile(callback: (data: any) => void) { const data = await this.application!.createBackupFile( undefined, undefined, true ); callback(data); } desktop_setMajorDataChangeHandler(handler: () => void) { this.majorDataChangeHandler = handler; } desktop_didBeginBackup() { this.webApplication.getAppState().beganBackupDownload(); } desktop_didFinishBackup(success: boolean) { this.webApplication.getAppState().endedBackupDownload(success); } }