Desktop manager TS
This commit is contained in:
@@ -4,10 +4,10 @@ import { SKAlert } from 'sn-stylekit';
|
||||
|
||||
export class AlertService extends SNAlertService {
|
||||
async alert(
|
||||
title,
|
||||
text,
|
||||
closeButtonText = "OK",
|
||||
onClose
|
||||
title: string,
|
||||
text: string,
|
||||
closeButtonText = 'OK',
|
||||
onClose: () => void
|
||||
) {
|
||||
return new Promise((resolve) => {
|
||||
const buttons = [
|
||||
@@ -16,7 +16,7 @@ export class AlertService extends SNAlertService {
|
||||
style: "neutral",
|
||||
action: async () => {
|
||||
if (onClose) {
|
||||
this.deviceInterface.timeout(onClose);
|
||||
this.deviceInterface!.timeout(onClose);
|
||||
}
|
||||
resolve(true);
|
||||
}
|
||||
@@ -28,12 +28,12 @@ export class AlertService extends SNAlertService {
|
||||
}
|
||||
|
||||
async confirm(
|
||||
title,
|
||||
text,
|
||||
confirmButtonText = "Confirm",
|
||||
cancelButtonText = "Cancel",
|
||||
onConfirm,
|
||||
onCancel,
|
||||
title: string,
|
||||
text: string,
|
||||
confirmButtonText = 'Confirm',
|
||||
cancelButtonText = 'Cancel',
|
||||
onConfirm: () => void,
|
||||
onCancel: () => void,
|
||||
destructive = false
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -43,7 +43,7 @@ export class AlertService extends SNAlertService {
|
||||
style: "neutral",
|
||||
action: async () => {
|
||||
if (onCancel) {
|
||||
this.deviceInterface.timeout(onCancel);
|
||||
this.deviceInterface!.timeout(onCancel);
|
||||
}
|
||||
reject(false);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export class AlertService extends SNAlertService {
|
||||
style: destructive ? "danger" : "info",
|
||||
action: async () => {
|
||||
if (onConfirm) {
|
||||
this.deviceInterface.timeout(onConfirm);
|
||||
this.deviceInterface!.timeout(onConfirm);
|
||||
}
|
||||
resolve(true);
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
import { EncryptionIntents, ProtectedAction } from 'snjs';
|
||||
|
||||
export class ArchiveManager {
|
||||
constructor(application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
async downloadBackup(encrypted) {
|
||||
return this.downloadBackupOfItems(this.application.modelManager.allItems, encrypted);
|
||||
}
|
||||
|
||||
/** @public */
|
||||
async downloadBackupOfItems(items, encrypted) {
|
||||
const run = async () => {
|
||||
// download in Standard Notes format
|
||||
const intent = encrypted
|
||||
? EncryptionIntents.FileEncrypted
|
||||
: EncryptionIntents.FileDecrypted;
|
||||
this.itemsData(items, intent).then((data) => {
|
||||
const modifier = encrypted ? "Encrypted" : "Decrypted";
|
||||
this.downloadData(
|
||||
data,
|
||||
`Standard Notes ${modifier} Backup - ${this.formattedDate()}.txt`
|
||||
);
|
||||
// download as zipped plain text files
|
||||
if (!encrypted) {
|
||||
this.downloadZippedItems(items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (await this.application.privilegesService.actionRequiresPrivilege(ProtectedAction.ManageBackups)) {
|
||||
this.application.presentPrivilegesModal(ProtectedAction.ManageBackups, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
/** @private */
|
||||
formattedDate() {
|
||||
var string = `${new Date()}`;
|
||||
// Match up to the first parenthesis, i.e do not include '(Central Standard Time)'
|
||||
var matches = string.match(/^(.*?) \(/);
|
||||
if (matches.length >= 2) {
|
||||
return matches[1];
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
async itemsData(items, intent) {
|
||||
const data = await this.application.createBackupFile({
|
||||
subItems: items,
|
||||
intent: intent
|
||||
});
|
||||
const blobData = new Blob([data], { type: 'text/json' });
|
||||
return blobData;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
async loadZip() {
|
||||
if (window.zip) {
|
||||
return;
|
||||
}
|
||||
|
||||
var scriptTag = document.createElement('script');
|
||||
scriptTag.src = "/assets/zip/zip.js";
|
||||
scriptTag.async = false;
|
||||
var headTag = document.getElementsByTagName('head')[0];
|
||||
headTag.appendChild(scriptTag);
|
||||
return new Promise((resolve, reject) => {
|
||||
scriptTag.onload = function () {
|
||||
zip.workerScriptsPath = "assets/zip/";
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** @private */
|
||||
async downloadZippedItems(items) {
|
||||
await this.loadZip();
|
||||
zip.createWriter(new zip.BlobWriter("application/zip"), (zipWriter) => {
|
||||
var index = 0;
|
||||
|
||||
const nextFile = () => {
|
||||
var item = items[index];
|
||||
var name, contents;
|
||||
|
||||
if (item.content_type === "Note") {
|
||||
name = item.content.title;
|
||||
contents = item.content.text;
|
||||
} else {
|
||||
name = item.content_type;
|
||||
contents = JSON.stringify(item.content, null, 2);
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = "";
|
||||
}
|
||||
|
||||
const blob = new Blob([contents], { type: 'text/plain' });
|
||||
let filePrefix = name.replace(/\//g, "").replace(/\\+/g, "");
|
||||
const fileSuffix = `-${item.uuid.split("-")[0]}.txt`;
|
||||
// Standard max filename length is 255. Slice the note name down to allow filenameEnd
|
||||
filePrefix = filePrefix.slice(0, (255 - fileSuffix.length));
|
||||
const fileName = `${item.content_type}/${filePrefix}${fileSuffix}`;
|
||||
zipWriter.add(fileName, new zip.BlobReader(blob), () => {
|
||||
index++;
|
||||
if (index < items.length) {
|
||||
nextFile();
|
||||
} else {
|
||||
zipWriter.close((blob) => {
|
||||
this.downloadData(blob, `Standard Notes Backup - ${this.formattedDate()}.zip`);
|
||||
zipWriter = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
nextFile();
|
||||
}, onerror);
|
||||
}
|
||||
|
||||
|
||||
/** @private */
|
||||
hrefForData(data) {
|
||||
// If we are replacing a previously generated file we need to
|
||||
// manually revoke the object URL to avoid memory leaks.
|
||||
if (this.textFile !== null) {
|
||||
window.URL.revokeObjectURL(this.textFile);
|
||||
}
|
||||
this.textFile = window.URL.createObjectURL(data);
|
||||
// returns a URL you can use as a href
|
||||
return this.textFile;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
downloadData(data, fileName) {
|
||||
var link = document.createElement('a');
|
||||
link.setAttribute('download', fileName);
|
||||
link.href = this.hrefForData(data);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
}
|
||||
154
app/assets/javascripts/services/archiveManager.ts
Normal file
154
app/assets/javascripts/services/archiveManager.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { EncryptionIntent, ProtectedAction, SNItem, ContentType, SNNote } from 'snjs';
|
||||
|
||||
export class ArchiveManager {
|
||||
|
||||
private readonly application: WebApplication
|
||||
private textFile?: string
|
||||
|
||||
constructor(application: WebApplication) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
public async downloadBackup(encrypted: boolean) {
|
||||
return this.downloadBackupOfItems(this.application.allItems(), encrypted);
|
||||
}
|
||||
|
||||
public async downloadBackupOfItems(items: SNItem[], encrypted: boolean) {
|
||||
const run = async () => {
|
||||
// download in Standard Notes format
|
||||
const intent = encrypted
|
||||
? EncryptionIntent.FileEncrypted
|
||||
: EncryptionIntent.FileDecrypted;
|
||||
this.itemsData(items, intent).then((data) => {
|
||||
const modifier = encrypted ? 'Encrypted' : 'Decrypted';
|
||||
this.downloadData(
|
||||
data!,
|
||||
`Standard Notes ${modifier} Backup - ${this.formattedDate()}.txt`
|
||||
);
|
||||
// download as zipped plain text files
|
||||
if (!encrypted) {
|
||||
this.downloadZippedItems(items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
await this.application.privilegesService!
|
||||
.actionRequiresPrivilege(ProtectedAction.ManageBackups)
|
||||
) {
|
||||
this.application.presentPrivilegesModal(
|
||||
ProtectedAction.ManageBackups,
|
||||
() => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
private formattedDate() {
|
||||
const string = `${new Date()}`;
|
||||
// Match up to the first parenthesis, i.e do not include '(Central Standard Time)'
|
||||
const matches = string.match(/^(.*?) \(/);
|
||||
if (matches && matches.length >= 2) {
|
||||
return matches[1];
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
private async itemsData(items: SNItem[], intent: EncryptionIntent) {
|
||||
const data = await this.application.createBackupFile(items, intent);
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
const blobData = new Blob([data], { type: 'text/json' });
|
||||
return blobData;
|
||||
}
|
||||
|
||||
private get zip() {
|
||||
return (window as any).zip;
|
||||
}
|
||||
|
||||
private async loadZip() {
|
||||
if (this.zip) {
|
||||
return;
|
||||
}
|
||||
const scriptTag = document.createElement('script');
|
||||
scriptTag.src = '/assets/zip/zip.js';
|
||||
scriptTag.async = false;
|
||||
const headTag = document.getElementsByTagName('head')[0];
|
||||
headTag.appendChild(scriptTag);
|
||||
return new Promise((resolve) => {
|
||||
scriptTag.onload = () => {
|
||||
this.zip.workerScriptsPath = 'assets/zip/';
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async downloadZippedItems(items: SNItem[]) {
|
||||
await this.loadZip();
|
||||
this.zip.createWriter(
|
||||
new this.zip.BlobWriter('application/zip'),
|
||||
(zipWriter: any) => {
|
||||
let index = 0;
|
||||
const nextFile = () => {
|
||||
const item = items[index];
|
||||
let name, contents;
|
||||
if (item.content_type === ContentType.Note) {
|
||||
const note = item as SNNote;
|
||||
name = note.title;
|
||||
contents = note.text;
|
||||
} else {
|
||||
name = item.content_type;
|
||||
contents = JSON.stringify(item.content, null, 2);
|
||||
}
|
||||
if (!name) {
|
||||
name = '';
|
||||
}
|
||||
const blob = new Blob([contents], { type: 'text/plain' });
|
||||
let filePrefix = name.replace(/\//g, '').replace(/\\+/g, '');
|
||||
const fileSuffix = `-${item.uuid.split('-')[0]}.txt`;
|
||||
// Standard max filename length is 255. Slice the note name down to allow filenameEnd
|
||||
filePrefix = filePrefix.slice(0, (255 - fileSuffix.length));
|
||||
const fileName = `${item.content_type}/${filePrefix}${fileSuffix}`;
|
||||
zipWriter.add(fileName, new this.zip.BlobReader(blob), () => {
|
||||
index++;
|
||||
if (index < items.length) {
|
||||
nextFile();
|
||||
} else {
|
||||
zipWriter.close((blob: any) => {
|
||||
this.downloadData(
|
||||
blob,
|
||||
`Standard Notes Backup - ${this.formattedDate()}.zip`
|
||||
);
|
||||
zipWriter = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
nextFile();
|
||||
}, onerror);
|
||||
}
|
||||
|
||||
private hrefForData(data: Blob) {
|
||||
// If we are replacing a previously generated file we need to
|
||||
// manually revoke the object URL to avoid memory leaks.
|
||||
if (this.textFile) {
|
||||
window.URL.revokeObjectURL(this.textFile);
|
||||
}
|
||||
this.textFile = window.URL.createObjectURL(data);
|
||||
// returns a URL you can use as a href
|
||||
return this.textFile;
|
||||
}
|
||||
|
||||
private downloadData(data: Blob, fileName: string) {
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('download', fileName);
|
||||
link.href = this.hrefForData(data);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/* eslint-disable camelcase */
|
||||
// An interface used by the Desktop app to interact with SN
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import { EncryptionIntents, ApplicationService, ApplicationEvent, removeFromArray } from 'snjs';
|
||||
|
||||
const COMPONENT_DATA_KEY_INSTALL_ERROR = 'installError';
|
||||
const COMPONENT_CONTENT_KEY_PACKAGE_INFO = 'package_info';
|
||||
const COMPONENT_CONTENT_KEY_LOCAL_URL = 'local_url';
|
||||
|
||||
export class DesktopManager extends ApplicationService {
|
||||
constructor(
|
||||
$rootScope,
|
||||
$timeout,
|
||||
application
|
||||
) {
|
||||
super(application);
|
||||
this.$rootScope = $rootScope;
|
||||
this.$timeout = $timeout;
|
||||
this.componentActivationObservers = [];
|
||||
this.updateObservers = [];
|
||||
this.isDesktop = isDesktopApplication();
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.componentActivationObservers.length = 0;
|
||||
this.updateObservers.length = 0;
|
||||
super.deinit();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
onAppEvent(eventName) {
|
||||
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) {
|
||||
return this.application.protocolService.payloadByEncryptingPayload({
|
||||
payload: component.payloadRepresentation(),
|
||||
intent: EncryptionIntents.FileDecrypted
|
||||
});
|
||||
}
|
||||
|
||||
// All `components` should be installed
|
||||
syncComponentsInstallation(components) {
|
||||
if (!this.isDesktop) {
|
||||
return;
|
||||
}
|
||||
Promise.all(components.map((component) => {
|
||||
return this.convertComponentForTransmission(component);
|
||||
})).then((data) => {
|
||||
this.installationSyncHandler(data);
|
||||
});
|
||||
}
|
||||
|
||||
async installComponent(component) {
|
||||
this.installComponentHandler(
|
||||
await this.convertComponentForTransmission(component)
|
||||
);
|
||||
}
|
||||
|
||||
registerUpdateObserver(callback) {
|
||||
const observer = {
|
||||
callback: callback
|
||||
};
|
||||
this.updateObservers.push(observer);
|
||||
return () => {
|
||||
removeFromArray(this.updateObservers, observer);
|
||||
};
|
||||
}
|
||||
|
||||
searchText(text) {
|
||||
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) {
|
||||
this.searchHandler = handler;
|
||||
}
|
||||
|
||||
desktop_windowGainedFocus() {
|
||||
this.$rootScope.$broadcast('window-gained-focus');
|
||||
}
|
||||
|
||||
desktop_windowLostFocus() {
|
||||
this.$rootScope.$broadcast('window-lost-focus');
|
||||
}
|
||||
|
||||
async desktop_onComponentInstallationComplete(componentData, error) {
|
||||
const component = await this.application.findItem({ uuid: componentData.uuid });
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
component.setAppDataItem(
|
||||
COMPONENT_DATA_KEY_INSTALL_ERROR,
|
||||
error
|
||||
);
|
||||
} else {
|
||||
const permissableKeys = [
|
||||
COMPONENT_CONTENT_KEY_PACKAGE_INFO,
|
||||
COMPONENT_CONTENT_KEY_LOCAL_URL
|
||||
];
|
||||
for (const key of permissableKeys) {
|
||||
component[key] = componentData.content[key];
|
||||
}
|
||||
component.setAppDataItem(
|
||||
COMPONENT_DATA_KEY_INSTALL_ERROR,
|
||||
null
|
||||
);
|
||||
}
|
||||
this.application.saveItem({ item: component });
|
||||
this.$timeout(() => {
|
||||
for (const observer of this.updateObservers) {
|
||||
observer.callback(component);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
desktop_registerComponentActivationObserver(callback) {
|
||||
const observer = { id: Math.random, callback: callback };
|
||||
this.componentActivationObservers.push(observer);
|
||||
return observer;
|
||||
}
|
||||
|
||||
desktop_deregisterComponentActivationObserver(observer) {
|
||||
removeFromArray(this.componentActivationObservers, observer);
|
||||
}
|
||||
|
||||
/* Notify observers that a component has been registered/activated */
|
||||
async notifyComponentActivation(component) {
|
||||
const serializedComponent = await this.convertComponentForTransmission(
|
||||
component
|
||||
);
|
||||
this.$timeout(() => {
|
||||
for (const observer of this.componentActivationObservers) {
|
||||
observer.callback(serializedComponent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Used to resolve 'sn://' */
|
||||
desktop_setExtServerHost(host) {
|
||||
this.extServerHost = host;
|
||||
this.application.getAppState().desktopExtensionsReady();
|
||||
}
|
||||
|
||||
desktop_setComponentInstallationSyncHandler(handler) {
|
||||
this.installationSyncHandler = handler;
|
||||
}
|
||||
|
||||
desktop_setInstallComponentHandler(handler) {
|
||||
this.installComponentHandler = handler;
|
||||
}
|
||||
|
||||
desktop_setInitialDataLoadHandler(handler) {
|
||||
this.dataLoadHandler = handler;
|
||||
if (this.dataLoaded) {
|
||||
this.dataLoadHandler();
|
||||
}
|
||||
}
|
||||
|
||||
async desktop_requestBackupFile(callback) {
|
||||
const data = await this.application.createBackupFile({
|
||||
returnIfEmpty: true
|
||||
});
|
||||
callback(data);
|
||||
}
|
||||
|
||||
desktop_setMajorDataChangeHandler(handler) {
|
||||
this.majorDataChangeHandler = handler;
|
||||
}
|
||||
|
||||
desktop_didBeginBackup() {
|
||||
this.application.getAppState().beganBackupDownload();
|
||||
}
|
||||
|
||||
desktop_didFinishBackup(success) {
|
||||
this.application.getAppState().endedBackupDownload(success);
|
||||
}
|
||||
}
|
||||
245
app/assets/javascripts/services/desktopManager.ts
Normal file
245
app/assets/javascripts/services/desktopManager.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { SNComponent, PurePayload } from 'snjs';
|
||||
/* eslint-disable camelcase */
|
||||
import { WebApplication } from '@/application';
|
||||
// An interface used by the Desktop app to interact with SN
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from 'snjs';
|
||||
import { ComponentMutator } from '@/../../../../snjs/dist/@types/models';
|
||||
import { AppDataField } from '@/../../../../snjs/dist/@types/models/core/item';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import _ from 'lodash';
|
||||
import {
|
||||
ApplicationEvent,
|
||||
StorageValueModes,
|
||||
EncryptionIntents,
|
||||
EncryptionIntent,
|
||||
ApplicationService,
|
||||
} from 'snjs';
|
||||
import { AppStateEvent } from '@/services/state';
|
||||
@@ -123,7 +123,7 @@ export class ThemeManager extends ApplicationService {
|
||||
const payload = theme.payloadRepresentation();
|
||||
const processedPayload = await this.application.protocolService.payloadByEncryptingPayload({
|
||||
payload: payload,
|
||||
intent: EncryptionIntents.LocalStorageDecrypted
|
||||
intent: EncryptionIntent.LocalStorageDecrypted
|
||||
});
|
||||
return processedPayload;
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user