Update formatting of existing files to be in line with new prettier formatting rules
This commit is contained in:
parent
cf4e8e7c83
commit
967fce9746
|
@ -0,0 +1 @@
|
|||
app/src/core/*.js
|
|
@ -1,41 +1,43 @@
|
|||
export class AjaxError {
|
||||
|
||||
public code: string;
|
||||
|
||||
public message: string;
|
||||
|
||||
constructor(
|
||||
public request: XMLHttpRequest
|
||||
) {
|
||||
constructor(public request: XMLHttpRequest) {
|
||||
try {
|
||||
const err = JSON.parse(request.responseText);
|
||||
this.code = err.error;
|
||||
this.message = err.message;
|
||||
} catch (e) {
|
||||
switch (request.status.toString()[0]) {
|
||||
case "0":
|
||||
this.code = "failed_connection";
|
||||
this.message = "Failed Connection";
|
||||
break;
|
||||
case "3":
|
||||
this.code = "unexpected_redirect";
|
||||
this.message = "Unexpected Redirect";
|
||||
break;
|
||||
case "4":
|
||||
this.code = "client_error";
|
||||
this.message = "Unknown Client Error";
|
||||
break;
|
||||
default:
|
||||
this.code = "server_error";
|
||||
this.message = "Server Error";
|
||||
case "0":
|
||||
this.code = "failed_connection";
|
||||
this.message = "Failed Connection";
|
||||
break;
|
||||
case "3":
|
||||
this.code = "unexpected_redirect";
|
||||
this.message = "Unexpected Redirect";
|
||||
break;
|
||||
case "4":
|
||||
this.code = "client_error";
|
||||
this.message = "Unknown Client Error";
|
||||
break;
|
||||
default:
|
||||
this.code = "server_error";
|
||||
this.message = "Server Error";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type Method = "GET" | "POST" | "PUT" | "DELETE";
|
||||
|
||||
export function request(method: Method, url: string, body?: string, headers?: Map<string, string>): Promise<XMLHttpRequest> {
|
||||
export function request(
|
||||
method: Method,
|
||||
url: string,
|
||||
body?: string,
|
||||
headers?: Map<string, string>
|
||||
): Promise<XMLHttpRequest> {
|
||||
let req = new XMLHttpRequest();
|
||||
|
||||
return new Promise<XMLHttpRequest>((resolve, reject) => {
|
||||
|
@ -47,7 +49,7 @@ export function request(method: Method, url: string, body?: string, headers?: Ma
|
|||
resolve(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
req.open(method, url, true);
|
||||
|
@ -55,13 +57,13 @@ export function request(method: Method, url: string, body?: string, headers?: Ma
|
|||
headers.forEach((value, key) => req.setRequestHeader(key, value));
|
||||
}
|
||||
req.send(body);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
reject(new AjaxError(req));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
request(method: Method, url: string, body?: string, headers?: Map<string, string>): Promise<XMLHttpRequest>
|
||||
urlForPath(path: string): string
|
||||
request(method: Method, url: string, body?: string, headers?: Map<string, string>): Promise<XMLHttpRequest>;
|
||||
urlForPath(path: string): string;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import { sjcl } from "../../vendor/sjcl";
|
||||
import { isCordova, hasNode } from "./platform";
|
||||
|
||||
declare var pbkdf2: undefined |
|
||||
((pass: string, salt: string, opts: { iterations: number, keySize: number }) => Promise<string>);
|
||||
declare var pbkdf2:
|
||||
| undefined
|
||||
| ((pass: string, salt: string, opts: { iterations: number; keySize: number }) => Promise<string>);
|
||||
const nodeCrypto = hasNode() && window.require("crypto");
|
||||
|
||||
export class CryptoError {
|
||||
constructor(
|
||||
public code:
|
||||
"invalid_container_data" |
|
||||
"unsupported_container_version" |
|
||||
"invalid_key_params" |
|
||||
"decryption_failed" |
|
||||
"encryption_failed"
|
||||
) {};
|
||||
| "invalid_container_data"
|
||||
| "unsupported_container_version"
|
||||
| "invalid_key_params"
|
||||
| "decryption_failed"
|
||||
| "encryption_failed"
|
||||
) {}
|
||||
}
|
||||
|
||||
// Available cipher algorithms
|
||||
|
@ -67,30 +68,24 @@ export interface CipherParams {
|
|||
}
|
||||
|
||||
function validateKeyParams(params: any) {
|
||||
return [128, 192, 256].includes(params.keySize) &&
|
||||
return (
|
||||
[128, 192, 256].includes(params.keySize) &&
|
||||
typeof params.iter == "number" && // valid PBKDF2 iteration count
|
||||
params.iter <= PBKDF2_ITER_MAX && // sane pbkdf2 iteration count
|
||||
typeof params.salt == "string";//valid salt
|
||||
typeof params.salt == "string"
|
||||
); //valid salt
|
||||
}
|
||||
|
||||
function genKey(params: KeyParams): Promise<string> {
|
||||
if (
|
||||
!params.password ||
|
||||
typeof params.password !== "string" ||
|
||||
!validateKeyParams(params)
|
||||
) {
|
||||
if (!params.password || typeof params.password !== "string" || !validateKeyParams(params)) {
|
||||
throw new CryptoError("invalid_key_params");
|
||||
}
|
||||
|
||||
if (isCordova() && typeof pbkdf2 === "function") {
|
||||
return pbkdf2(
|
||||
params.password,
|
||||
params.salt,
|
||||
{
|
||||
iterations: params.iter,
|
||||
keySize: params.keySize
|
||||
}
|
||||
);
|
||||
return pbkdf2(params.password, params.salt, {
|
||||
iterations: params.iter,
|
||||
keySize: params.keySize
|
||||
});
|
||||
} else if (nodeCrypto) {
|
||||
return new Promise((resolve, reject) => {
|
||||
nodeCrypto.pbkdf2(
|
||||
|
@ -109,12 +104,7 @@ function genKey(params: KeyParams): Promise<string> {
|
|||
);
|
||||
});
|
||||
} else {
|
||||
let k = sjcl.misc.pbkdf2(
|
||||
utf8ToBits(params.password),
|
||||
base64ToBits(params.salt),
|
||||
params.iter,
|
||||
params.keySize
|
||||
);
|
||||
let k = sjcl.misc.pbkdf2(utf8ToBits(params.password), base64ToBits(params.salt), params.iter, params.keySize);
|
||||
|
||||
return Promise.resolve(bitsToBase64(k));
|
||||
}
|
||||
|
@ -124,11 +114,14 @@ function decrypt(key: string, ct: string, params: CipherParams): string {
|
|||
try {
|
||||
const cipher = new sjcl.cipher[params.cipher](base64ToBits(key));
|
||||
const pt = sjcl.mode[params.mode].decrypt(
|
||||
cipher, base64ToBits(ct), base64ToBits(params.iv),
|
||||
base64ToBits(params.adata), params.ts
|
||||
cipher,
|
||||
base64ToBits(ct),
|
||||
base64ToBits(params.iv),
|
||||
base64ToBits(params.adata),
|
||||
params.ts
|
||||
);
|
||||
return bitsToUtf8(pt);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
throw new CryptoError("decryption_failed");
|
||||
}
|
||||
}
|
||||
|
@ -137,10 +130,9 @@ function encrypt(key: string, pt: string, params: CipherParams): string {
|
|||
try {
|
||||
const cipher = new sjcl.cipher[params.cipher](base64ToBits(key));
|
||||
const mode = sjcl.mode[params.mode];
|
||||
var ct = mode.encrypt(cipher, utf8ToBits(pt), base64ToBits(params.iv),
|
||||
base64ToBits(params.adata), params.ts);
|
||||
var ct = mode.encrypt(cipher, utf8ToBits(pt), base64ToBits(params.iv), base64ToBits(params.adata), params.ts);
|
||||
return bitsToBase64(ct);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
throw new CryptoError("encryption_failed");
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +142,7 @@ export interface RawContainerV0 extends Pbkdf2Params, CipherParams {
|
|||
ct: string;
|
||||
}
|
||||
|
||||
export interface RawContainerV1 extends Pbkdf2Params, CipherParams {
|
||||
export interface RawContainerV1 extends Pbkdf2Params, CipherParams {
|
||||
version: 1;
|
||||
ct: string;
|
||||
}
|
||||
|
@ -158,7 +150,6 @@ export interface RawContainerV1 extends Pbkdf2Params, CipherParams {
|
|||
export type RawContainer = RawContainerV0 | RawContainerV1;
|
||||
|
||||
export class Container implements KeyParams, CipherParams {
|
||||
|
||||
password: string;
|
||||
salt: string;
|
||||
iv: string;
|
||||
|
@ -235,8 +226,8 @@ export class Container implements KeyParams, CipherParams {
|
|||
throw new CryptoError("invalid_container_data");
|
||||
}
|
||||
|
||||
switch(raw.version) {
|
||||
case undefined:
|
||||
switch (raw.version) {
|
||||
case undefined:
|
||||
// Legacy versions of Padlock had a bug where the base64-encoded
|
||||
// `adata` value was not converted to a BitArray before being
|
||||
// passed to `sjcl.mode.ccm.encrypt/decrypt` and the raw string was
|
||||
|
@ -272,7 +263,8 @@ export class Container implements KeyParams, CipherParams {
|
|||
}
|
||||
|
||||
static validateRaw(obj: RawContainer) {
|
||||
return typeof obj == "object" &&
|
||||
return (
|
||||
typeof obj == "object" &&
|
||||
(obj.version === undefined || typeof obj.version === "number") && // has a valid version
|
||||
validateKeyParams(obj) &&
|
||||
["aes"].includes(obj.cipher) && // valid cipher
|
||||
|
@ -280,7 +272,7 @@ export class Container implements KeyParams, CipherParams {
|
|||
typeof obj.iv == "string" && // valid initialisation vector
|
||||
typeof obj.ct == "string" && // valid cipher text
|
||||
typeof obj.adata == "string" && // valid authorisation data
|
||||
[64, 96, 128].includes(obj.ts); // valid authorisation tag length
|
||||
[64, 96, 128].includes(obj.ts)
|
||||
); // valid authorisation tag length
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { uuid } from "./util";
|
|||
import { getAppVersion } from "./platform";
|
||||
import { Source } from "./source";
|
||||
|
||||
|
||||
function compareProperty(p: string): (a: Object, b: Object) => number {
|
||||
return (a, b) => {
|
||||
const x = typeof a[p] === "string" ? a[p].toLowerCase() : a[p];
|
||||
|
@ -23,7 +22,6 @@ function normalizeTag(tag: string): string {
|
|||
}
|
||||
|
||||
export class Record {
|
||||
|
||||
private _tags: Set<string>;
|
||||
name: string;
|
||||
fields: Array<Field>;
|
||||
|
@ -32,8 +30,15 @@ export class Record {
|
|||
removed: boolean;
|
||||
lastUsed?: Date;
|
||||
|
||||
constructor(name = "", fields?: Array<Field>, tags?: string[],
|
||||
id?: string, updated?: Date, removed = false, lastUsed?: Date) {
|
||||
constructor(
|
||||
name = "",
|
||||
fields?: Array<Field>,
|
||||
tags?: string[],
|
||||
id?: string,
|
||||
updated?: Date,
|
||||
removed = false,
|
||||
lastUsed?: Date
|
||||
) {
|
||||
this.name = name;
|
||||
this.fields = fields || new Array<Field>();
|
||||
if (!Array.isArray(tags)) {
|
||||
|
@ -50,7 +55,7 @@ export class Record {
|
|||
const fields = obj.fields && <Array<Field>>obj.fields;
|
||||
const updated = obj.updated && (obj.updated instanceof Date ? obj.updated : new Date(obj.updated));
|
||||
const lastUsed = obj.lastUsed && (obj.lastUsed instanceof Date ? obj.lastUsed : new Date(obj.lastUsed));
|
||||
const tags = obj.tags || obj.category && [obj.category];
|
||||
const tags = obj.tags || (obj.category && [obj.category]);
|
||||
return new Record(obj.name, fields, tags, obj.uuid, updated, obj.removed, lastUsed);
|
||||
}
|
||||
|
||||
|
@ -93,14 +98,12 @@ export class Record {
|
|||
lastUsed: this.lastUsed
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Collection {
|
||||
|
||||
private _records: Map<string, Record>;
|
||||
|
||||
constructor(records?:Record[]) {
|
||||
constructor(records?: Record[]) {
|
||||
this._records = new Map<string, Record>();
|
||||
|
||||
if (records) {
|
||||
|
@ -151,7 +154,6 @@ export class Collection {
|
|||
clear(): void {
|
||||
this._records.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface Device {
|
||||
|
@ -165,7 +167,6 @@ export interface Account {
|
|||
}
|
||||
|
||||
export class Settings {
|
||||
|
||||
static defaults = {
|
||||
autoLock: true,
|
||||
// Auto lock delay in minutes
|
||||
|
@ -272,5 +273,4 @@ export class Settings {
|
|||
Object.assign(this, Settings.defaults);
|
||||
this.loaded = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage;
|
||||
const persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage;
|
||||
|
||||
function requestFileSystem(bytes: number): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
|
@ -18,16 +18,14 @@ async function requestQuota(requestedBytes: number): Promise<number> {
|
|||
}
|
||||
|
||||
export interface FileManager {
|
||||
read(path: string): Promise<string>
|
||||
write(path: string, content: string): Promise<void>
|
||||
read(path: string): Promise<string>;
|
||||
write(path: string, content: string): Promise<void>;
|
||||
}
|
||||
|
||||
export class HTML5FileManager implements FileManager {
|
||||
|
||||
protected get fs(): Promise<any> {
|
||||
return requestQuota(1024 * 1024 * 10)
|
||||
.then((grantedBytes) => requestFileSystem(grantedBytes));
|
||||
};
|
||||
return requestQuota(1024 * 1024 * 10).then(grantedBytes => requestFileSystem(grantedBytes));
|
||||
}
|
||||
|
||||
async getFile(name: string): Promise<any> {
|
||||
const fs = await this.fs;
|
||||
|
@ -35,7 +33,6 @@ export class HTML5FileManager implements FileManager {
|
|||
return new Promise<any>((resolve, reject) => {
|
||||
fs.root.getFile(name, { create: true, exclusive: false }, resolve, reject);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async read(name: string): Promise<string> {
|
||||
|
@ -62,24 +59,22 @@ export class HTML5FileManager implements FileManager {
|
|||
return new Promise<void>((resolve, reject) => {
|
||||
// Create a FileWriter object for our FileEntry (log.txt).
|
||||
fileEntry.createWriter(function(fileWriter: any) {
|
||||
|
||||
fileWriter.onerror = reject;
|
||||
|
||||
fileWriter.onwrite = () => {
|
||||
const blob = new Blob([content], {type: "text/plain"});
|
||||
const blob = new Blob([content], { type: "text/plain" });
|
||||
fileWriter.onwrite = resolve;
|
||||
fileWriter.write(blob);
|
||||
};
|
||||
|
||||
fileWriter.seek(0);
|
||||
fileWriter.truncate(0);
|
||||
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const cordovaReady = new Promise<void>((resolve) => {
|
||||
const cordovaReady = new Promise<void>(resolve => {
|
||||
document.addEventListener("deviceready", () => resolve());
|
||||
});
|
||||
|
||||
|
@ -94,7 +89,6 @@ const nodePath = window.require && window.require("path");
|
|||
const electron = window.require && window.require("electron");
|
||||
|
||||
export class NodeFileManager implements FileManager {
|
||||
|
||||
public basePath = electron.remote.app.getPath("userData");
|
||||
|
||||
constructor() {
|
||||
|
@ -109,7 +103,7 @@ export class NodeFileManager implements FileManager {
|
|||
|
||||
read(path: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
nodeFs.readFile(this.resolvePath(path), "utf8", (err: {code: string}, content: string) => {
|
||||
nodeFs.readFile(this.resolvePath(path), "utf8", (err: { code: string }, content: string) => {
|
||||
if (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
resolve("");
|
||||
|
@ -125,7 +119,7 @@ export class NodeFileManager implements FileManager {
|
|||
|
||||
write(path: string, content: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
nodeFs.writeFile(this.resolvePath(path), content, "utf8", (err: {code: string}) => {
|
||||
nodeFs.writeFile(this.resolvePath(path), content, "utf8", (err: { code: string }) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,7 @@ declare var device: any | undefined;
|
|||
|
||||
const nodeRequire = window.require;
|
||||
const electron = nodeRequire && nodeRequire("electron");
|
||||
const cordovaReady = new Promise<void>((r) => document.addEventListener("deviceready", () => r()));
|
||||
const cordovaReady = new Promise<void>(r => document.addEventListener("deviceready", () => r()));
|
||||
|
||||
// Textarea used for copying/pasting using the dom
|
||||
let clipboardTextArea: HTMLTextAreaElement;
|
||||
|
@ -38,7 +38,7 @@ export function isCordova(): Boolean {
|
|||
|
||||
//* Checks if the app is running as a packaged Chrome app
|
||||
export function isChromeApp(): boolean {
|
||||
return (typeof chrome !== "undefined") && chrome.app && !!chrome.app.runtime;
|
||||
return typeof chrome !== "undefined" && chrome.app && !!chrome.app.runtime;
|
||||
}
|
||||
|
||||
export async function isIOS(): Promise<boolean> {
|
||||
|
@ -105,7 +105,7 @@ export async function getAppStoreLink(): Promise<string> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getReviewLink(rating:number): Promise<string> {
|
||||
export async function getReviewLink(rating: number): Promise<string> {
|
||||
if (await isIOS()) {
|
||||
return "https://itunes.apple.com/app/id871710139?action=write-review";
|
||||
} else if (await isAndroid()) {
|
||||
|
@ -145,24 +145,28 @@ export async function getAppVersion(): Promise<string> {
|
|||
export async function getPlatformName(): Promise<string> {
|
||||
if (isElectron()) {
|
||||
const platform = nodeRequire("os").platform();
|
||||
return {
|
||||
darwin: "MacOS",
|
||||
win32: "Windows",
|
||||
linux: "Linux"
|
||||
}[platform] || platform;
|
||||
return (
|
||||
{
|
||||
darwin: "MacOS",
|
||||
win32: "Windows",
|
||||
linux: "Linux"
|
||||
}[platform] || platform
|
||||
);
|
||||
} else if (isCordova()) {
|
||||
await cordovaReady;
|
||||
return device.platform;
|
||||
} else if (isChromeApp()) {
|
||||
const info = await new Promise<{os: string}>((r) => chrome.runtime.getPlatformInfo(r));
|
||||
return {
|
||||
cros: "ChromeOS",
|
||||
win: "Windows (Chrome)",
|
||||
linux: "Linux (Chrome)",
|
||||
android: "Android (Chrome)",
|
||||
mac: "MacOS (Chrome)",
|
||||
openbsd: "OpenBSD (Chrome)"
|
||||
}[info.os] || info.os;
|
||||
const info = await new Promise<{ os: string }>(r => chrome.runtime.getPlatformInfo(r));
|
||||
return (
|
||||
{
|
||||
cros: "ChromeOS",
|
||||
win: "Windows (Chrome)",
|
||||
linux: "Linux (Chrome)",
|
||||
android: "Android (Chrome)",
|
||||
mac: "MacOS (Chrome)",
|
||||
openbsd: "OpenBSD (Chrome)"
|
||||
}[info.os] || info.os
|
||||
);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
@ -221,13 +225,13 @@ export function getLocale(): string {
|
|||
}
|
||||
|
||||
export interface DeviceInfo {
|
||||
platform: string,
|
||||
osVersion: string,
|
||||
uuid: string,
|
||||
appVersion: string,
|
||||
manufacturer?: string,
|
||||
model?: string,
|
||||
hostName?: string
|
||||
platform: string;
|
||||
osVersion: string;
|
||||
uuid: string;
|
||||
appVersion: string;
|
||||
manufacturer?: string;
|
||||
model?: string;
|
||||
hostName?: string;
|
||||
}
|
||||
|
||||
export async function getDeviceInfo(): Promise<DeviceInfo> {
|
||||
|
|
|
@ -14,7 +14,6 @@ export interface Source {
|
|||
}
|
||||
|
||||
export class MemorySource implements Source {
|
||||
|
||||
constructor(private data = "") {}
|
||||
|
||||
async get(): Promise<string> {
|
||||
|
@ -28,33 +27,29 @@ export class MemorySource implements Source {
|
|||
async clear(): Promise<void> {
|
||||
this.data = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class HTML5LocalStorageSource implements Source {
|
||||
|
||||
constructor(public key: string) {}
|
||||
|
||||
async get(): Promise<string> {
|
||||
return localStorage.getItem(this.key) || "";
|
||||
};
|
||||
}
|
||||
|
||||
async set(data: string): Promise<void> {
|
||||
localStorage.setItem(this.key, data);
|
||||
};
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
localStorage.removeItem(this.key);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class ChromeLocalStorageSource implements Source {
|
||||
|
||||
constructor(public key: string) {}
|
||||
|
||||
get(): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise(resolve => {
|
||||
chromeLocalStorage.get(this.key, (obj: any) => {
|
||||
let data = obj[this.key];
|
||||
if (typeof data === "object") {
|
||||
|
@ -63,26 +58,24 @@ export class ChromeLocalStorageSource implements Source {
|
|||
resolve(data || "");
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
set(data: string): Promise<void> {
|
||||
const obj = {
|
||||
[this.key]: data
|
||||
};
|
||||
|
||||
return new Promise<void>((resolve) => chromeLocalStorage.set(obj, resolve));
|
||||
return new Promise<void>(resolve => chromeLocalStorage.set(obj, resolve));
|
||||
}
|
||||
|
||||
clear(): Promise<void> {
|
||||
return new Promise<void>((resolve) => chromeLocalStorage.remove(this.key, resolve));
|
||||
return new Promise<void>(resolve => chromeLocalStorage.remove(this.key, resolve));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const LocalStorageSource = isChromeApp() ? ChromeLocalStorageSource : HTML5LocalStorageSource;
|
||||
|
||||
export class AjaxSource implements Source {
|
||||
|
||||
constructor(private _url: string) {}
|
||||
|
||||
get url() {
|
||||
|
@ -104,7 +97,6 @@ export class AjaxSource implements Source {
|
|||
clear(): Promise<void> {
|
||||
return this.set("").then(() => {});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface CloudAuthToken {
|
||||
|
@ -116,12 +108,11 @@ export interface CloudAuthToken {
|
|||
}
|
||||
|
||||
export class CloudSource extends AjaxSource implements Client {
|
||||
|
||||
urlForPath(path: string): string {
|
||||
// Remove trailing slashes
|
||||
const host = this.settings.syncCustomHost ?
|
||||
this.settings.syncHostUrl.replace(/\/+$/, "") :
|
||||
"https://cloud.padlock.io"
|
||||
const host = this.settings.syncCustomHost
|
||||
? this.settings.syncHostUrl.replace(/\/+$/, "")
|
||||
: "https://cloud.padlock.io";
|
||||
return `${host}/${path}/`;
|
||||
}
|
||||
|
||||
|
@ -134,13 +125,11 @@ export class CloudSource extends AjaxSource implements Client {
|
|||
}
|
||||
|
||||
async request(method: Method, url: string, data?: string, headers?: Map<string, string>): Promise<XMLHttpRequest> {
|
||||
|
||||
headers = headers || new Map<string, string>();
|
||||
|
||||
headers.set("Accept", "application/vnd.padlock;version=1");
|
||||
if (this.settings.syncEmail && this.settings.syncToken) {
|
||||
headers.set("Authorization",
|
||||
"AuthToken " + this.settings.syncEmail + ":" + this.settings.syncToken);
|
||||
headers.set("Authorization", "AuthToken " + this.settings.syncEmail + ":" + this.settings.syncToken);
|
||||
}
|
||||
|
||||
const { uuid, platform, osVersion, appVersion, manufacturer, model, hostName } = await getDeviceInfo();
|
||||
|
@ -174,7 +163,13 @@ export class CloudSource extends AjaxSource implements Client {
|
|||
return req;
|
||||
}
|
||||
|
||||
async authenticate(email: string, create = false, authType = "api", redirect = "", actType = ""): Promise<CloudAuthToken> {
|
||||
async authenticate(
|
||||
email: string,
|
||||
create = false,
|
||||
authType = "api",
|
||||
redirect = "",
|
||||
actType = ""
|
||||
): Promise<CloudAuthToken> {
|
||||
const params = new URLSearchParams();
|
||||
params.set("email", email);
|
||||
params.set("type", authType);
|
||||
|
@ -239,7 +234,7 @@ export class CloudSource extends AjaxSource implements Client {
|
|||
new Map<string, string>().set("Content-Type", "application/x-www-form-urlencoded")
|
||||
)
|
||||
.then(() => true)
|
||||
.catch((e) => {
|
||||
.catch(e => {
|
||||
if (e.code === "bad_request") {
|
||||
return false;
|
||||
} else {
|
||||
|
@ -249,14 +244,11 @@ export class CloudSource extends AjaxSource implements Client {
|
|||
}
|
||||
|
||||
logout(): Promise<XMLHttpRequest> {
|
||||
return this.request(
|
||||
"GET",
|
||||
this.urlForPath("logout")
|
||||
);
|
||||
return this.request("GET", this.urlForPath("logout"));
|
||||
}
|
||||
|
||||
async getAccountInfo(): Promise<Account> {
|
||||
const res = await this.request("GET", this.urlForPath("account"))
|
||||
const res = await this.request("GET", this.urlForPath("account"));
|
||||
const account = JSON.parse(res.responseText);
|
||||
this.settings.account = account;
|
||||
return account;
|
||||
|
@ -291,14 +283,11 @@ export class CloudSource extends AjaxSource implements Client {
|
|||
}
|
||||
|
||||
getPlans(): Promise<any[]> {
|
||||
return this.request("GET", this.urlForPath("plans"))
|
||||
.then((res) => <any[]>JSON.parse(res.responseText));
|
||||
return this.request("GET", this.urlForPath("plans")).then(res => <any[]>JSON.parse(res.responseText));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class EncryptedSource implements Source {
|
||||
|
||||
private container?: Container;
|
||||
|
||||
public password: string;
|
||||
|
@ -311,7 +300,7 @@ export class EncryptedSource implements Source {
|
|||
return "";
|
||||
}
|
||||
|
||||
let cont = this.container = Container.fromJSON(data);
|
||||
let cont = (this.container = Container.fromJSON(data));
|
||||
cont.password = this.password;
|
||||
|
||||
return await cont.get();
|
||||
|
@ -319,7 +308,7 @@ export class EncryptedSource implements Source {
|
|||
|
||||
async set(data: string): Promise<void> {
|
||||
// Reuse container if possible
|
||||
let cont = this.container = this.container || new Container();
|
||||
let cont = (this.container = this.container || new Container());
|
||||
cont.password = this.password;
|
||||
await cont.set(data);
|
||||
|
||||
|
@ -339,16 +328,17 @@ export class EncryptedSource implements Source {
|
|||
delete this.container;
|
||||
return this.source.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FileSource implements Source {
|
||||
|
||||
private fileManager: FileManager;
|
||||
|
||||
constructor(private filePath: string) {
|
||||
this.fileManager = isElectron() ? new NodeFileManager() :
|
||||
isCordova() ? new CordovaFileManager() : new HTML5FileManager();
|
||||
this.fileManager = isElectron()
|
||||
? new NodeFileManager()
|
||||
: isCordova()
|
||||
? new CordovaFileManager()
|
||||
: new HTML5FileManager();
|
||||
}
|
||||
|
||||
get(): Promise<string> {
|
||||
|
@ -362,5 +352,4 @@ export class FileSource implements Source {
|
|||
clear(): Promise<void> {
|
||||
return this.fileManager.write(this.filePath, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { FileSource } from "./source"
|
||||
import { FileSource } from "./source";
|
||||
|
||||
const statsSource = new FileSource("stats.json");
|
||||
|
||||
export type Stats = { [prop: string]: number|string };
|
||||
export type Stats = { [prop: string]: number | string };
|
||||
let stats: Stats;
|
||||
|
||||
const statsLoaded = statsSource.get().then((data) => {
|
||||
const statsLoaded = statsSource.get().then(data => {
|
||||
try {
|
||||
stats = JSON.parse(data);
|
||||
} catch (e) {
|
||||
|
|
|
@ -4,15 +4,19 @@ import { getAppVersion } from "./platform";
|
|||
let initCb: () => void;
|
||||
let client: Client;
|
||||
let trackingID = "";
|
||||
let statsApi:{ get(): Promise<any>, set(data: any): Promise<void> } ;
|
||||
let statsApi: { get(): Promise<any>; set(data: any): Promise<void> };
|
||||
|
||||
const initialized = new Promise((resolve) => initCb = resolve);
|
||||
const initialized = new Promise(resolve => (initCb = resolve));
|
||||
|
||||
export function init(cl: Client, st?: { get(): Promise<any>, set(data: any): Promise<void> }) {
|
||||
export function init(cl: Client, st?: { get(): Promise<any>; set(data: any): Promise<void> }) {
|
||||
client = cl;
|
||||
statsApi = st || {
|
||||
get() { return Promise.resolve({}) },
|
||||
set() { return Promise.resolve() }
|
||||
get() {
|
||||
return Promise.resolve({});
|
||||
},
|
||||
set() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
initCb();
|
||||
}
|
||||
|
@ -22,40 +26,36 @@ export function setTrackingID(id: string) {
|
|||
return statsApi.set({ trackingID: id });
|
||||
}
|
||||
|
||||
let ready = initialized
|
||||
.then(() => Promise.all([
|
||||
statsApi.get(),
|
||||
getAppVersion()
|
||||
]))
|
||||
.then(([stats, version]) => {
|
||||
trackingID = stats.trackingID as string;
|
||||
const launchCount = typeof stats.launchCount === "number" ? (stats.launchCount as number)+ 1 : 1;
|
||||
const isFirstLaunch = !stats.firstLaunch;
|
||||
const firstLaunch = stats.firstLaunch || new Date().getTime();
|
||||
let ready = initialized.then(() => Promise.all([statsApi.get(), getAppVersion()])).then(([stats, version]) => {
|
||||
trackingID = stats.trackingID as string;
|
||||
const launchCount = typeof stats.launchCount === "number" ? (stats.launchCount as number) + 1 : 1;
|
||||
const isFirstLaunch = !stats.firstLaunch;
|
||||
const firstLaunch = stats.firstLaunch || new Date().getTime();
|
||||
|
||||
if (isFirstLaunch) {
|
||||
track("Install");
|
||||
} else if (stats.lastVersion !== version) {
|
||||
track("Update", { "From Version": stats.lastVersion });
|
||||
}
|
||||
if (isFirstLaunch) {
|
||||
track("Install");
|
||||
} else if (stats.lastVersion !== version) {
|
||||
track("Update", { "From Version": stats.lastVersion });
|
||||
}
|
||||
|
||||
return statsApi.set({
|
||||
firstLaunch: firstLaunch,
|
||||
lastLaunch: new Date().getTime(),
|
||||
launchCount: launchCount,
|
||||
lastVersion: version
|
||||
});
|
||||
return statsApi.set({
|
||||
firstLaunch: firstLaunch,
|
||||
lastLaunch: new Date().getTime(),
|
||||
launchCount: launchCount,
|
||||
lastVersion: version
|
||||
});
|
||||
});
|
||||
|
||||
export function track(event: string, props?: { [prop: string]: number|string }) {
|
||||
export function track(event: string, props?: { [prop: string]: number | string }) {
|
||||
const data = {
|
||||
event: event,
|
||||
props: props || {},
|
||||
trackingID: trackingID
|
||||
};
|
||||
|
||||
ready = ready.then(() => statsApi.get())
|
||||
.then((stats) => {
|
||||
ready = ready
|
||||
.then(() => statsApi.get())
|
||||
.then(stats => {
|
||||
Object.assign(data.props, {
|
||||
"First Launch": stats.firstLaunch && new Date(stats.firstLaunch as number).toISOString(),
|
||||
"Launch Count": stats.launchCount,
|
||||
|
@ -70,7 +70,7 @@ export function track(event: string, props?: { [prop: string]: number|string })
|
|||
Object.assign(data.props, {
|
||||
"Last Rated": new Date(stats.lastAskedFeedback as number).toISOString(),
|
||||
"Rated Version": stats.lastRatedVersion,
|
||||
"Rating": stats.lastRating
|
||||
Rating: stats.lastRating
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -78,12 +78,8 @@ export function track(event: string, props?: { [prop: string]: number|string })
|
|||
data.props["Last Reviewed"] = new Date(stats.lastReviewed as number).toISOString();
|
||||
}
|
||||
})
|
||||
.then(() => client.request(
|
||||
"POST",
|
||||
client.urlForPath("track"),
|
||||
JSON.stringify(data)
|
||||
))
|
||||
.then((r) => {
|
||||
.then(() => client.request("POST", client.urlForPath("track"), JSON.stringify(data)))
|
||||
.then(r => {
|
||||
const res = JSON.parse(r.responseText);
|
||||
return setTrackingID(res.trackingID);
|
||||
})
|
||||
|
|
220
main.js
220
main.js
|
@ -15,7 +15,7 @@ if (debug || test) {
|
|||
}
|
||||
|
||||
const defaultDBPath = path.join(app.getPath("userData"), "data.pls");
|
||||
const settings = global.settings = new ElectronStore({
|
||||
const settings = (global.settings = new ElectronStore({
|
||||
name: "settings",
|
||||
defaults: {
|
||||
autoDownloadUpdates: false,
|
||||
|
@ -27,7 +27,7 @@ const settings = global.settings = new ElectronStore({
|
|||
fullscreen: false,
|
||||
dbPath: defaultDBPath
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
if (!settings.get("uuid")) {
|
||||
settings.set("uuid", uuid());
|
||||
|
@ -37,19 +37,23 @@ let win;
|
|||
let updateOnQuit = false;
|
||||
|
||||
function updateReady(updateInfo) {
|
||||
dialog.showMessageBox({
|
||||
message: "Install Update",
|
||||
detail: `Padlock version ${updateInfo.version} has been downloaded. The update will be installed ` +
|
||||
`the next time the app is launched.`,
|
||||
buttons: ["Install Later", "Install And Restart"],
|
||||
defaultId: 1,
|
||||
}, (buttonIndex) => {
|
||||
if (buttonIndex === 1) {
|
||||
autoUpdater.quitAndInstall();
|
||||
} else {
|
||||
updateOnQuit = true;
|
||||
dialog.showMessageBox(
|
||||
{
|
||||
message: "Install Update",
|
||||
detail:
|
||||
`Padlock version ${updateInfo.version} has been downloaded. The update will be installed ` +
|
||||
`the next time the app is launched.`,
|
||||
buttons: ["Install Later", "Install And Restart"],
|
||||
defaultId: 1
|
||||
},
|
||||
buttonIndex => {
|
||||
if (buttonIndex === 1) {
|
||||
autoUpdater.quitAndInstall();
|
||||
} else {
|
||||
updateOnQuit = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
autoUpdater.on("update-downloaded", updateReady);
|
||||
|
@ -66,25 +70,28 @@ function updateAvailable(versionInfo) {
|
|||
return;
|
||||
}
|
||||
|
||||
dialog.showMessageBox({
|
||||
type: "info",
|
||||
message: `A new version of Padlock is available! (v${versionInfo.version})`,
|
||||
detail: htmlToText(versionInfo.releaseNotes),
|
||||
checkboxLabel: "Automatically download and install updates in the future (recommended)",
|
||||
buttons: ["Remind Me Later", "Download And Install"],
|
||||
defaultId: 1
|
||||
}, (buttonIndex, checkboxChecked) => {
|
||||
settings.set("autoDownloadUpdates", checkboxChecked);
|
||||
dialog.showMessageBox(
|
||||
{
|
||||
type: "info",
|
||||
message: `A new version of Padlock is available! (v${versionInfo.version})`,
|
||||
detail: htmlToText(versionInfo.releaseNotes),
|
||||
checkboxLabel: "Automatically download and install updates in the future (recommended)",
|
||||
buttons: ["Remind Me Later", "Download And Install"],
|
||||
defaultId: 1
|
||||
},
|
||||
(buttonIndex, checkboxChecked) => {
|
||||
settings.set("autoDownloadUpdates", checkboxChecked);
|
||||
|
||||
if (buttonIndex === 1) {
|
||||
autoUpdater.downloadUpdate();
|
||||
if (buttonIndex === 1) {
|
||||
autoUpdater.downloadUpdate();
|
||||
|
||||
dialog.showMessageBox({
|
||||
message: "Downloading Update...",
|
||||
detail: "The new version is being downloaded. You'll be notified when it is ready to be installed!"
|
||||
});
|
||||
dialog.showMessageBox({
|
||||
message: "Downloading Update...",
|
||||
detail: "The new version is being downloaded. You'll be notified when it is ready to be installed!"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
function checkForUpdates(manual) {
|
||||
|
@ -92,46 +99,50 @@ function checkForUpdates(manual) {
|
|||
autoUpdater.allowPrerelease = settings.get("allowPrerelease");
|
||||
|
||||
const check = autoUpdater.checkForUpdates();
|
||||
check && check.then((result) => {
|
||||
if (autoUpdater.updateAvailable) {
|
||||
updateAvailable(result.versionInfo);
|
||||
} else if (manual) {
|
||||
dialog.showMessageBox({
|
||||
type: "info",
|
||||
message: "No Updates Available",
|
||||
detail: "Your version of Padlock is up to date.",
|
||||
checkboxLabel: "Automatically download and install updates in the future (recommended)",
|
||||
checkboxChecked: settings.get("autoDownloadUpdates")
|
||||
}, (buttonIndex, checkboxChecked) => {
|
||||
settings.set("autoDownloadUpdates", checkboxChecked);
|
||||
});
|
||||
}
|
||||
});
|
||||
check &&
|
||||
check.then(result => {
|
||||
if (autoUpdater.updateAvailable) {
|
||||
updateAvailable(result.versionInfo);
|
||||
} else if (manual) {
|
||||
dialog.showMessageBox(
|
||||
{
|
||||
type: "info",
|
||||
message: "No Updates Available",
|
||||
detail: "Your version of Padlock is up to date.",
|
||||
checkboxLabel: "Automatically download and install updates in the future (recommended)",
|
||||
checkboxChecked: settings.get("autoDownloadUpdates")
|
||||
},
|
||||
(buttonIndex, checkboxChecked) => {
|
||||
settings.set("autoDownloadUpdates", checkboxChecked);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveDBAs() {
|
||||
const oldPath = settings.get("dbPath");
|
||||
const newPath = dialog.showSaveDialog({
|
||||
defaultPath: oldPath === defaultDBPath ? path.join(app.getPath("home"), "padlock.pls") : oldPath,
|
||||
filters: [
|
||||
{name: "Padlock Store", extensions: ["pls"]},
|
||||
]
|
||||
filters: [{ name: "Padlock Store", extensions: ["pls"] }]
|
||||
});
|
||||
if (newPath) {
|
||||
if (dialog.showMessageBox({
|
||||
type: "question",
|
||||
message: "Confirm Changing Database Location",
|
||||
detail: `Are you sure you want to change your database location to ${newPath}?`,
|
||||
buttons: ["Save And Restart", "Cancel"],
|
||||
defaultId: 0
|
||||
}) === 1) {
|
||||
if (
|
||||
dialog.showMessageBox({
|
||||
type: "question",
|
||||
message: "Confirm Changing Database Location",
|
||||
detail: `Are you sure you want to change your database location to ${newPath}?`,
|
||||
buttons: ["Save And Restart", "Cancel"],
|
||||
defaultId: 0
|
||||
}) === 1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings.set("dbPath", newPath);
|
||||
|
||||
if (fs.pathExistsSync(oldPath)) {
|
||||
fs.moveSync(oldPath, newPath, {overwrite: true});
|
||||
fs.moveSync(oldPath, newPath, { overwrite: true });
|
||||
}
|
||||
|
||||
app.relaunch();
|
||||
|
@ -144,21 +155,22 @@ function loadDB() {
|
|||
const paths = dialog.showOpenDialog({
|
||||
defaultPath: oldPath === defaultDBPath ? path.join(app.getPath("home"), "padlock.pls") : oldPath,
|
||||
properties: ["openFile", "createDirectory", "promptToCreate"],
|
||||
filters: [
|
||||
{name: "Padlock Store", extensions: ["pls"]},
|
||||
]
|
||||
filters: [{ name: "Padlock Store", extensions: ["pls"] }]
|
||||
});
|
||||
const newPath = paths && paths[0];
|
||||
|
||||
if (newPath && newPath !== oldPath) {
|
||||
if (dialog.showMessageBox({
|
||||
type: "question",
|
||||
message: "Confirm Loading Database",
|
||||
detail: `Are you sure you want to load the database file at ${newPath}?` +
|
||||
` Your existing datbase will remain at ${oldPath}.`,
|
||||
buttons: ["Load And Restart", "Cancel"],
|
||||
defaultId: 0
|
||||
}) === 1) {
|
||||
if (
|
||||
dialog.showMessageBox({
|
||||
type: "question",
|
||||
message: "Confirm Loading Database",
|
||||
detail:
|
||||
`Are you sure you want to load the database file at ${newPath}?` +
|
||||
` Your existing datbase will remain at ${oldPath}.`,
|
||||
buttons: ["Load And Restart", "Cancel"],
|
||||
defaultId: 0
|
||||
}) === 1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -189,11 +201,13 @@ function createWindow() {
|
|||
});
|
||||
|
||||
// and load the index.html of the app.
|
||||
win.loadURL(url.format({
|
||||
pathname: path.resolve(__dirname, test ? "test/index.html" : "app/index.html"),
|
||||
protocol: "file:",
|
||||
slashes: true
|
||||
}));
|
||||
win.loadURL(
|
||||
url.format({
|
||||
pathname: path.resolve(__dirname, test ? "test/index.html" : "app/build/dev/index.html"),
|
||||
protocol: "file:",
|
||||
slashes: true
|
||||
})
|
||||
);
|
||||
|
||||
win.once("ready-to-show", () => {
|
||||
win.show();
|
||||
|
@ -218,46 +232,42 @@ function createWindow() {
|
|||
e.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function createApplicationMenu() {
|
||||
const checkForUpdatesItem = {
|
||||
label: "Check for Updates...",
|
||||
click() { checkForUpdates(true); }
|
||||
click() {
|
||||
checkForUpdates(true);
|
||||
}
|
||||
};
|
||||
|
||||
const appSubMenu = os.platform() === "darwin" ? [ { role: "about" } ] :
|
||||
[ { label: `Padlock v${app.getVersion()}`, enabled: false} ];
|
||||
const appSubMenu =
|
||||
os.platform() === "darwin" ? [{ role: "about" }] : [{ label: `Padlock v${app.getVersion()}`, enabled: false }];
|
||||
|
||||
appSubMenu.push(
|
||||
checkForUpdatesItem
|
||||
);
|
||||
appSubMenu.push(checkForUpdatesItem);
|
||||
|
||||
if (os.platform() == "darwin") {
|
||||
appSubMenu.push(
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{ role: "hideothers" },
|
||||
{ role: "unhide" }
|
||||
);
|
||||
appSubMenu.push({ type: "separator" }, { role: "hide" }, { role: "hideothers" }, { role: "unhide" });
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
appSubMenu.push(
|
||||
{ type: "separator" },
|
||||
{ label: "Debug", submenu: [{
|
||||
label: "Open Dev Tools",
|
||||
accelerator: "CmdOrCtrl+Shift+I",
|
||||
click: () => win.webContents.toggleDevTools()
|
||||
}] }
|
||||
{
|
||||
label: "Debug",
|
||||
submenu: [
|
||||
{
|
||||
label: "Open Dev Tools",
|
||||
accelerator: "CmdOrCtrl+Shift+I",
|
||||
click: () => win.webContents.toggleDevTools()
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
appSubMenu.push(
|
||||
{ type: "separator" },
|
||||
{ role: "quit" }
|
||||
);
|
||||
appSubMenu.push({ type: "separator" }, { role: "quit" });
|
||||
|
||||
// Set up menu
|
||||
const template = [
|
||||
|
@ -277,20 +287,26 @@ function createApplicationMenu() {
|
|||
type: "checkbox",
|
||||
label: "Automatically Download and Install Updates",
|
||||
checked: settings.get("autoDownloadUpdates"),
|
||||
click(item) { settings.set("autoDownloadUpdates", item.checked); }
|
||||
click(item) {
|
||||
settings.set("autoDownloadUpdates", item.checked);
|
||||
}
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
type: "radio",
|
||||
label: "Only Download Stable Releases (recommended)",
|
||||
checked: !settings.get("allowPrerelease"),
|
||||
click(item) { settings.set("allowPrerelease", !item.checked); }
|
||||
click(item) {
|
||||
settings.set("allowPrerelease", !item.checked);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "radio",
|
||||
label: "Download Stable and Beta Releases",
|
||||
checked: settings.get("allowPrerelease"),
|
||||
click(item) { settings.set("allowPrerelease", item.checked); }
|
||||
click(item) {
|
||||
settings.set("allowPrerelease", item.checked);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -300,11 +316,15 @@ function createApplicationMenu() {
|
|||
submenu: [
|
||||
{
|
||||
label: "Load...",
|
||||
click() { loadDB(); }
|
||||
click() {
|
||||
loadDB();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Save As...",
|
||||
click() { saveDBAs(); }
|
||||
click() {
|
||||
saveDBAs();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -350,7 +370,7 @@ app.on("activate", () => {
|
|||
}
|
||||
});
|
||||
|
||||
app.on("before-quit", (e) => {
|
||||
app.on("before-quit", e => {
|
||||
if (updateOnQuit) {
|
||||
updateOnQuit = false;
|
||||
e.preventDefault();
|
||||
|
|
Loading…
Reference in New Issue