Update formatting of existing files to be in line with new prettier formatting rules

This commit is contained in:
Martin Kleinschrodt 2018-06-04 18:27:17 +02:00
parent cf4e8e7c83
commit 967fce9746
10 changed files with 294 additions and 296 deletions

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
app/src/core/*.js

View File

@ -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;
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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> {

View File

@ -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, "");
}
}

View File

@ -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) {

View File

@ -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
View File

@ -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();