diff --git a/.circleci/config.yml b/.circleci/config.yml index 84fc56f4..ae0c56cb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,13 +14,11 @@ jobs: sudo systemsetup -settimezone Europe/Warsaw echo "Today is $(date +"%Y-%m-%d %T")" - - restore_cache: - name: Restore Yarn Package Cache - keys: - - yarn-packages-{{ .Branch }}-{{ checksum "yarn.lock" }} - - yarn-packages-{{ .Branch }} - - yarn-packages-master - - yarn-packages- +# - restore_cache: +# name: Restore Yarn Package Cache +# keys: +# - dependency-cache-{{ checksum "yarn.lock" }} +# - dependency-cache- - run: name: Install project @@ -30,11 +28,11 @@ jobs: yarn || true yarn || true - - save_cache: - name: Save Yarn Package Cache - key: yarn-packages-{{ .Branch }}-{{ checksum "yarn.lock" }} - paths: - - node_modules/ +# - save_cache: +# name: Save Yarn Package Cache +# key: dependency-cache-{{ checksum "yarn.lock" }} +# paths: +# - ./node_modules - run: name: Building WebUI for distribution diff --git a/main.js b/main.js index f7158332..3edd904e 100644 --- a/main.js +++ b/main.js @@ -1,22 +1,39 @@ const electron = require('electron'); -var fs = require('fs'); -// Module to control application life. +const fs = require('fs'); const app = electron.app; -// Module to create native browser window. const BrowserWindow = electron.BrowserWindow; - const path = require('path'); const url = require('url'); +const yargs = require('yargs'); + +require('./sentry'); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWindow; - let serverProc = null; - let isWin = /^win/.test(process.platform); +let isDev = false; + +const argv = yargs + .describe('m', 'Maximizes window on startup.') + .boolean('m') + .describe('e', 'Environment, `dev` for developer mode and when not specified then production mode. ') + .choices('e', ['dev', null]) + .argv; + +if (argv.e == 'dev') { + isDev = true; +} + const createServerProc = () => { + const directory = path.join(__dirname, 'dist'); + + if (!fs.existsSync(directory)) { + return; + } + fs.readdir(path.join(__dirname, 'dist'), (err, files) => { var serverPath = null; @@ -43,8 +60,10 @@ const createServerProc = () => { } const exitServerProc = () => { - serverProc.kill(); - serverProc = null; + if(serverProc) { + serverProc.kill(); + serverProc = null; + } } @@ -53,15 +72,26 @@ function createWindow () { mainWindow = new BrowserWindow({width: 800, height: 600}); // and load the index.html of the app. - mainWindow.loadURL(url.format({ - pathname: path.join(__dirname, 'dist/index.html'), - protocol: 'file:', - slashes: true - })); + + if(isDev) { + mainWindow.loadURL('http://localhost:4200/'); + mainWindow.webContents.openDevTools(); + } + else { + mainWindow.loadURL(url.format({ + pathname: path.join(__dirname, 'dist/index.html'), + protocol: 'file:', + slashes: true + })); + } // Open the DevTools. // mainWindow.webContents.openDevTools(); + if(argv.m) { + mainWindow.maximize(); + } + // Emitted when the window is closed. mainWindow.on('closed', function () { // Dereference the window object, usually you would store windows @@ -98,3 +128,5 @@ app.on('activate', function () { // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. + + diff --git a/package.json b/package.json index 1da08b8e..f0ab17e2 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", - "electrondev": "concurrently -k \"yarn startforelectron\" \"electron .\"", + "electron": "electron .", + "electrondev": "concurrently -k \"yarn startforelectron\" \"electron . -e dev\"", "distlinux": "yarn buildforelectron && electron-builder --linux --x64", "distwin": "yarn buildforelectron && electron-builder --win --x64", "distmac": "yarn buildforelectron && electron-builder --mac --x64", @@ -36,20 +37,25 @@ "@angular/platform-browser-dynamic": "^5.2.1", "@angular/router": "^5.2.1", "@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.9", + "angular-persistence": "^1.0.1", "angular2-hotkeys": "^2.0.4", "angular2-indexeddb": "^1.2.2", "bootstrap": "4.0.0", "core-js": "^2.4.1", "d3-ng2-service": "^1.23.3", + "electron-settings": "^3.1.4", + "ngx-electron": "^1.0.4", "npm-check-updates": "^2.13.0", "raven-js": "^3.24.0", "rxjs": "^5.4.1", + "yargs": "^11.0.0", "zone.js": "^0.8.20" }, "devDependencies": { "@angular/cli": "^6.0.0-beta.5", "@angular/compiler-cli": "^5.2.1", "@angular/language-service": "^5.2.1", + "@sentry/electron": "^0.4.2", "@types/jasmine": "~2.8.5", "@types/jasminewd2": "~2.0.2", "@types/node": "~9.6.0", diff --git a/sentry.js b/sentry.js new file mode 100644 index 00000000..8a02bf08 --- /dev/null +++ b/sentry.js @@ -0,0 +1,26 @@ +const { SentryClient } = require('@sentry/electron'); +const fs = require('fs'); +const { ipcMain } = require('electron'); + +let crashReportsEnabled = true; +const DSN = + 'https://cb7b474b2e874afb8e400c47d1452ecc:7876224cbff543d992cb0ac4021962f8@sentry.io/1040940'; + +const isDev = () => { + return fs.existsSync('.git'); +}; + +const shouldSendCallback = () => { + return !isDev() && crashReportsEnabled; +}; + + +ipcMain.on('settings.changed', function (event, settings) { + crashReportsEnabled = settings.crash_reports; +}); + + +SentryClient.create({ + dsn: DSN, + shouldSendCallback: shouldSendCallback +}); diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index efbda56f..3691417b 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -7,6 +7,7 @@ import { ProjectMapComponent } from './project-map/project-map.component'; import { ServersComponent } from "./servers/servers.component"; import { ProjectsComponent } from "./projects/projects.component"; import { DefaultLayoutComponent } from "./default-layout/default-layout.component"; +import { SettingsComponent } from "./settings/settings.component"; const routes: Routes = [ @@ -14,7 +15,8 @@ const routes: Routes = [ children: [ { path: '', redirectTo: 'servers', pathMatch: 'full'}, { path: 'servers', component: ServersComponent }, - { path: 'server/:server_id/projects', component: ProjectsComponent } + { path: 'server/:server_id/projects', component: ProjectsComponent }, + { path: 'settings', component: SettingsComponent }, ] }, { path: 'server/:server_id/project/:project_id', component: ProjectMapComponent }, diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index fa26fe63..82bc43b4 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,30 +1,74 @@ -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; +import { MatIconModule } from "@angular/material"; +import { SettingsService } from "./shared/services/settings.service"; +import { PersistenceService } from "angular-persistence"; +import { ElectronService, NgxElectronModule } from "ngx-electron"; +import createSpyObj = jasmine.createSpyObj; + describe('AppComponent', () => { + let component: AppComponent; + let fixture: ComponentFixture; + let electronService: ElectronService; + let settingsService: SettingsService; + beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ - AppComponent + AppComponent ], imports: [ - RouterTestingModule + RouterTestingModule, + MatIconModule, + NgxElectronModule + ], + providers: [ + SettingsService, + PersistenceService, ] }).compileComponents(); + + electronService = TestBed.get(ElectronService); + settingsService = TestBed.get(SettingsService); })); - // it('should create the app', async(() => { - // const fixture = TestBed.createComponent(AppComponent); - // const app = fixture.debugElement.componentInstance; - // expect(app).toBeTruthy(); - // })); + beforeEach(() => { + fixture = TestBed.createComponent(AppComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); - // it('should have footer', async(() => { - // const fixture = TestBed.createComponent(AppComponent); - // fixture.detectChanges(); - // const compiled = fixture.debugElement.nativeElement; - // expect(compiled.querySelector('.text-muted').textContent).toContain('GNS3 Web UI demo'); - // })); + it('should create the app', async(() => { + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + + it('should have footer', async(() => { + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('router-outlet').textContent).toEqual(''); + })); + + it('should receive changed settings and forward to electron', async(() => { + const spy = createSpyObj('Electron.IpcRenderer', ['send']); + spyOnProperty(electronService, 'isElectronApp').and.returnValue(true); + spyOnProperty(electronService, 'ipcRenderer').and.returnValue(spy); + settingsService.set('crash_reports', true); + component.ngOnInit(); + settingsService.set('crash_reports', false); + expect(spy.send).toHaveBeenCalled(); + expect(spy.send.calls.mostRecent().args[0]).toEqual('settings.changed'); + expect(spy.send.calls.mostRecent().args[1].crash_reports).toEqual(false); + })); + + it('should receive changed settings and do not forward to electron', async(() => { + const spy = createSpyObj('Electron.IpcRenderer', ['send']); + spyOnProperty(electronService, 'isElectronApp').and.returnValue(false); + settingsService.set('crash_reports', true); + component.ngOnInit(); + settingsService.set('crash_reports', false); + expect(spy.send).not.toHaveBeenCalled(); + })); }); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 35a74157..123baa5f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,7 +1,9 @@ import { Component, OnInit } from '@angular/core'; -import {Http} from "@angular/http"; -import {MatIconRegistry} from "@angular/material"; -import {DomSanitizer} from "@angular/platform-browser"; +import { MatIconRegistry } from "@angular/material"; +import { DomSanitizer } from "@angular/platform-browser"; +import { ElectronService } from "ngx-electron"; +import { SettingsService } from "./shared/services/settings.service"; + @Component({ selector: 'app-root', @@ -11,10 +13,20 @@ import {DomSanitizer} from "@angular/platform-browser"; ] }) export class AppComponent implements OnInit { - constructor(http: Http, iconReg: MatIconRegistry, sanitizer: DomSanitizer) { + constructor( + iconReg: MatIconRegistry, + sanitizer: DomSanitizer, + private settingsService: SettingsService, + private electronService: ElectronService) { + iconReg.addSvgIcon('gns3', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon.svg')); } ngOnInit(): void { + if (this.electronService.isElectronApp) { + this.settingsService.subscribe((settings) => { + this.electronService.ipcRenderer.send('settings.changed', settings); + }); + } } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 00880b78..37734db1 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,7 +1,6 @@ import * as Raven from 'raven-js'; import { BrowserModule } from '@angular/platform-browser'; import { NgModule, ErrorHandler } from '@angular/core'; -import { HttpModule } from '@angular/http'; import { FormsModule } from '@angular/forms'; import { CdkTableModule } from "@angular/cdk/table"; import { HttpClientModule } from '@angular/common/http'; @@ -19,13 +18,17 @@ import { MatDialogModule, MatProgressBarModule, MatProgressSpinnerModule, - MatSnackBarModule + MatSnackBarModule, + MatCheckboxModule, + MatListModule, + MatExpansionModule, } from '@angular/material'; import { D3Service } from 'd3-ng2-service'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; - import { HotkeyModule } from 'angular2-hotkeys'; +import { PersistenceModule } from 'angular-persistence'; +import { NgxElectronModule } from 'ngx-electron'; import { AppRoutingModule } from './app-routing.module'; @@ -66,21 +69,15 @@ import { DrawingsDataSource } from "./cartography/shared/datasources/drawings-da import { MoveLayerDownActionComponent } from './shared/node-context-menu/actions/move-layer-down-action/move-layer-down-action.component'; import { MoveLayerUpActionComponent } from './shared/node-context-menu/actions/move-layer-up-action/move-layer-up-action.component'; import { ProjectMapShortcutsComponent } from './project-map/project-map-shortcuts/project-map-shortcuts.component'; +import { SettingsComponent } from './settings/settings.component'; +import { SettingsService } from "./shared/services/settings.service"; -import { environment } from "../environments/environment"; +import { RavenErrorHandler } from "./raven-error-handler"; Raven .config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726') .install(); -export class RavenErrorHandler implements ErrorHandler { - handleError(err:any) : void { - console.error(err.originalError || err); - if (environment.production) { - Raven.captureException(err.originalError || err); - } - } -} @NgModule({ @@ -102,11 +99,11 @@ export class RavenErrorHandler implements ErrorHandler { MoveLayerDownActionComponent, MoveLayerUpActionComponent, ProjectMapShortcutsComponent, + SettingsComponent, ], imports: [ NgbModule.forRoot(), BrowserModule, - HttpModule, HttpClientModule, AppRoutingModule, FormsModule, @@ -123,11 +120,17 @@ export class RavenErrorHandler implements ErrorHandler { MatProgressBarModule, MatProgressSpinnerModule, MatSnackBarModule, + MatCheckboxModule, + MatListModule, + MatExpansionModule, CdkTableModule, CartographyModule, - HotkeyModule.forRoot() + HotkeyModule.forRoot(), + PersistenceModule, + NgxElectronModule ], providers: [ + SettingsService, { provide: ErrorHandler, useClass: RavenErrorHandler }, D3Service, VersionService, diff --git a/src/app/default-layout/default-layout.component.css b/src/app/default-layout/default-layout.component.css index 5f4933e8..f08c30e4 100644 --- a/src/app/default-layout/default-layout.component.css +++ b/src/app/default-layout/default-layout.component.css @@ -58,3 +58,10 @@ main { text-align: right; } +.fill-space { + flex: 1 1 auto; +} + +.example-container > * { + width: 100%; +} diff --git a/src/app/default-layout/default-layout.component.html b/src/app/default-layout/default-layout.component.html index f6648b16..cfe2f4e8 100644 --- a/src/app/default-layout/default-layout.component.html +++ b/src/app/default-layout/default-layout.component.html @@ -5,6 +5,12 @@ + + + + @@ -13,6 +19,6 @@ diff --git a/src/app/raven-error-handler.spec.ts b/src/app/raven-error-handler.spec.ts new file mode 100644 index 00000000..9c295851 --- /dev/null +++ b/src/app/raven-error-handler.spec.ts @@ -0,0 +1,48 @@ +import { TestBed } from '@angular/core/testing'; +import { PersistenceService } from "angular-persistence"; + +import * as Raven from 'raven-js'; + +import { SettingsService } from "./shared/services/settings.service"; +import { RavenErrorHandler } from "./raven-error-handler"; +import { environment } from "../environments/environment"; + + +describe('RavenErrorHandler', () => { + let handler: RavenErrorHandler; + let settingsService: SettingsService; + const inProductionOriginal = environment.production; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SettingsService, PersistenceService, RavenErrorHandler] + }); + + settingsService = TestBed.get(SettingsService); + handler = TestBed.get(RavenErrorHandler); + }); + + afterEach(() => { + environment.production = inProductionOriginal; + }); + + it('should create', () => { + expect(handler).toBeTruthy(); + }); + + it('should handle error', () => { + settingsService.set('crash_reports', true); + const error = new Error("My error"); + const captureException = spyOn(Raven, 'captureException'); + environment.production = true; + handler.handleError(error); + expect(captureException).toHaveBeenCalledWith(error); + }); + + it('should not handle when not in production', () => { + const captureException = spyOn(Raven, 'captureException'); + environment.production = false; + handler.handleError(new Error("My error")); + expect(captureException).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/raven-error-handler.ts b/src/app/raven-error-handler.ts new file mode 100644 index 00000000..6e85b8de --- /dev/null +++ b/src/app/raven-error-handler.ts @@ -0,0 +1,20 @@ +import * as Raven from 'raven-js'; + +import { ErrorHandler, Inject, Injector } from "@angular/core"; + +import { SettingsService } from "./shared/services/settings.service"; +import { environment } from "../environments/environment"; + + +export class RavenErrorHandler implements ErrorHandler { + constructor(@Inject(Injector) private injector: Injector) {} + + handleError(err: any): void { + const settingsService: SettingsService = this.injector.get(SettingsService); + console.error(err.originalError || err); + + if (environment.production && settingsService.get('crash_reports')) { + Raven.captureException(err.originalError || err); + } + } +} diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html new file mode 100644 index 00000000..bf1ad8aa --- /dev/null +++ b/src/app/settings/settings.component.html @@ -0,0 +1,28 @@ +
+
+

Settings

+
+
+
+ + + + + Local settings + + + Customize your local settings + + + + Send anonymous crash reports + + + +
+ +
+ +
+
+
diff --git a/src/app/settings/settings.component.scss b/src/app/settings/settings.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/settings/settings.component.spec.ts b/src/app/settings/settings.component.spec.ts new file mode 100644 index 00000000..c5a151e6 --- /dev/null +++ b/src/app/settings/settings.component.spec.ts @@ -0,0 +1,53 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SettingsComponent } from './settings.component'; +import { MatCheckboxModule, MatExpansionModule } from "@angular/material"; +import { FormsModule } from "@angular/forms"; +import { SettingsService } from "../shared/services/settings.service"; +import { PersistenceModule } from "angular-persistence"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; + +describe('SettingsComponent', () => { + let component: SettingsComponent; + let fixture: ComponentFixture; + let settingsService: SettingsService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + MatExpansionModule, MatCheckboxModule, FormsModule, + PersistenceModule, BrowserAnimationsModule ], + providers: [ SettingsService ], + declarations: [ SettingsComponent ] + }) + .compileComponents(); + + settingsService = TestBed.get(SettingsService); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should get and save new settings', () => { + const settings = { + 'crash_reports': true + }; + const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings); + const setAll = spyOn(settingsService, 'setAll'); + component.ngOnInit(); + expect(getAll).toHaveBeenCalled(); + expect(component.settings).toEqual(settings); + component.settings.crash_reports = false; + component.save(); + expect(setAll).toHaveBeenCalledWith(settings); + }); + + +}); diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts new file mode 100644 index 00000000..960cf27e --- /dev/null +++ b/src/app/settings/settings.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { SettingsService } from "../shared/services/settings.service"; + +@Component({ + selector: 'app-settings', + templateUrl: './settings.component.html', + styleUrls: ['./settings.component.scss'] +}) +export class SettingsComponent implements OnInit { + settings = { ...SettingsService.DEFAULTS }; + + constructor(private settingsService: SettingsService) { } + + ngOnInit() { + this.settings = this.settingsService.getAll(); + } + + save() { + this.settingsService.setAll(this.settings); + } +} diff --git a/src/app/shared/services/settings.service.spec.ts b/src/app/shared/services/settings.service.spec.ts new file mode 100644 index 00000000..d7b473a7 --- /dev/null +++ b/src/app/shared/services/settings.service.spec.ts @@ -0,0 +1,73 @@ +import { TestBed, inject } from '@angular/core/testing'; +import { PersistenceService, StorageType } from "angular-persistence"; + +import { Settings, SettingsService } from './settings.service'; +import createSpyObj = jasmine.createSpyObj; + + +describe('SettingsService', () => { + let persistenceService: PersistenceService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SettingsService, PersistenceService] + }); + + persistenceService = TestBed.get(PersistenceService); + }); + + afterEach(() => { + persistenceService.removeAll(StorageType.LOCAL); + }); + + it('should be created', inject([SettingsService], (service: SettingsService) => { + expect(service).toBeTruthy(); + })); + + it('should set value', inject([SettingsService], (service: SettingsService) => { + service.set('crash_reports', false); + expect(service.get('crash_reports')).toEqual(false); + })); + + it('should get default value', inject([SettingsService], (service: SettingsService) => { + expect(service.get('crash_reports')).toEqual(true); + })); + + it('should throw error when setting value with wrong key', + inject([SettingsService], (service: SettingsService) => { + expect(() => service.set('test', false)).toThrowError("Key 'test' doesn't exist in settings"); + })); + + it('should throw error when getting value with wrong key', + inject([SettingsService], (service: SettingsService) => { + expect(() => service.get('test')).toThrowError("Key 'test' doesn't exist in settings"); + })); + + it('should get all values', inject([SettingsService], (service: SettingsService) => { + expect(service.getAll()).toEqual({ + 'crash_reports': true + }); + })); + + it('should set all values', inject([SettingsService], (service: SettingsService) => { + const settings = { + 'crash_reports': false + }; + service.setAll(settings) + + expect(service.getAll()).toEqual({ + 'crash_reports': false + }); + })); + + it('should execute subscriber', inject([SettingsService], (service: SettingsService) => { + let changedSettings: Settings; + + service.set('crash_reports', true); + service.subscribe(settings => changedSettings = settings); + service.set('crash_reports', false); + + expect(changedSettings.crash_reports).toEqual(false); + })); + +}); diff --git a/src/app/shared/services/settings.service.ts b/src/app/shared/services/settings.service.ts new file mode 100644 index 00000000..cda14bfd --- /dev/null +++ b/src/app/shared/services/settings.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { PersistenceService, StorageType } from "angular-persistence"; +import { Subject } from "rxjs/Subject"; +import { BehaviorSubject } from "rxjs/BehaviorSubject"; + + +export interface Settings { + crash_reports: boolean; +} + + +@Injectable() +export class SettingsService { + static DEFAULTS: Settings = { + 'crash_reports': true + }; + + private settingsSubject: BehaviorSubject; + + constructor(private persistenceService: PersistenceService) { + this.settingsSubject = new BehaviorSubject(this.getAll()); + } + + get(key: string) { + if (!(key in SettingsService.DEFAULTS)) { + throw Error(`Key '${key}' doesn't exist in settings`); + } + const value = this.persistenceService.get(key, StorageType.LOCAL) as T; + if (typeof value === 'undefined') { + return SettingsService.DEFAULTS[key]; + } + return value; + } + + set(key: string, value: T): void { + if (!(key in SettingsService.DEFAULTS)) { + throw Error(`Key '${key}' doesn't exist in settings`); + } + this.persistenceService.set(key, value, { type: StorageType.LOCAL }); + this.settingsSubject.next(this.getAll()); + } + + getAll() { + const settings = { ...SettingsService.DEFAULTS }; + Object.keys(SettingsService.DEFAULTS).forEach((key) => { + settings[key] = this.get(key); + }); + return settings; + } + + setAll(settings) { + Object.keys(settings).forEach((key) => { + this.set(key, settings[key]); + }); + } + + subscribe(subscriber: ((settings: Settings) => void)) { + return this.settingsSubject.subscribe(subscriber); + } +} diff --git a/yarn.lock b/yarn.lock index eed64965..416e24ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -227,6 +227,42 @@ semver "^5.3.0" semver-intersect "^1.1.2" +"@sentry/browser@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-0.4.2.tgz#bd91752558d2e92adbd1f4b1dcd78efd92c7d2ed" + dependencies: + "@sentry/core" "0.4.2" + "@sentry/utils" "0.4.2" + raven-js "^3.23.1" + +"@sentry/core@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-0.4.2.tgz#3922c61b42519d9b67cd49da39f214a1d92b7f84" + +"@sentry/electron@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@sentry/electron/-/electron-0.4.2.tgz#47c15672470d3fd850096d8786b6738e250eea61" + dependencies: + "@sentry/browser" "0.4.2" + "@sentry/core" "0.4.2" + "@sentry/node" "0.4.2" + "@sentry/utils" "0.4.2" + form-data "^2.3.2" + node-fetch "^2.1.1" + util.promisify "^1.0.0" + +"@sentry/node@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-0.4.2.tgz#3fcc890cdff157fbb488a130ba83b7f68b57aacd" + dependencies: + "@sentry/core" "0.4.2" + "@sentry/utils" "0.4.2" + raven "^2.4.2" + +"@sentry/utils@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-0.4.2.tgz#1a951ceee448f0fd2dc146798ee9b93f39c058cf" + "@types/d3-array@1.2": version "1.2.1" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.1.tgz#e489605208d46a1c9d980d2e5772fa9c75d9ec65" @@ -535,6 +571,10 @@ amqplib@^0.5.2: readable-stream "1.x >=1.1.9" safe-buffer "^5.0.1" +angular-persistence@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/angular-persistence/-/angular-persistence-1.0.1.tgz#79ffe7317f1f7aed88e69f07705f0ac32ccdb9da" + angular2-hotkeys@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/angular2-hotkeys/-/angular2-hotkeys-2.0.4.tgz#83355f0a65fe484bfdd3d238b6ee96d63526eb91" @@ -1538,6 +1578,10 @@ chalk@~2.2.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + chokidar@^1.4.1, chokidar@^1.4.2, chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -1791,6 +1835,12 @@ combine-source-map@~0.8.0: lodash.memoize "~3.0.3" source-map "~0.5.3" +combined-stream@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -2071,6 +2121,10 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -2862,6 +2916,13 @@ electron-publish@20.0.2: fs-extra-p "^4.5.0" mime "^2.2.0" +electron-settings@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/electron-settings/-/electron-settings-3.1.4.tgz#1b670837fd00626395714210291fb4af8ff52880" + dependencies: + clone "^2.1.1" + jsonfile "^4.0.0" + electron-to-chromium@^1.3.30: version "1.3.31" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz#00d832cba9fe2358652b0c48a8816c8e3a037e9f" @@ -2979,6 +3040,16 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.5.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + es-abstract@^1.7.0: version "1.10.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" @@ -3437,6 +3508,14 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + form-data@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.0.0.tgz#6f0aebadcc5da16c13e1ecc11137d85f9b883b25" @@ -4342,7 +4421,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.0, is-buffer@^1.1.5: +is-buffer@^1.0.2, is-buffer@^1.1.0, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -5308,6 +5387,14 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5650,6 +5737,10 @@ netmask@~1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" +ngx-electron@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/ngx-electron/-/ngx-electron-1.0.4.tgz#2d80b25d74ae4d6226ad8b3bc4ecfa611e48fdca" + no-case@^2.2.0: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" @@ -5663,6 +5754,10 @@ node-alias@^1.0.4: chalk "^1.1.1" lodash "^4.2.0" +node-fetch@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + node-forge@0.6.33: version "0.6.33" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" @@ -6132,6 +6227,13 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -6851,10 +6953,20 @@ range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -raven-js@^3.24.0: +raven-js@^3.23.1, raven-js@^3.24.0: version "3.24.0" resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.24.0.tgz#59464d8bc4b3812ae87a282e9bb98ecad5b4b047" +raven@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/raven/-/raven-2.4.2.tgz#0129e2adc30788646fd530b67d08a8ce25d4f6dc" + dependencies: + cookie "0.3.1" + md5 "^2.2.1" + stack-trace "0.0.9" + timed-out "4.0.1" + uuid "3.0.0" + raw-body@2, raw-body@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" @@ -8148,6 +8260,10 @@ ssri@^5.0.0: dependencies: safe-buffer "^5.1.0" +stack-trace@0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" + stat-mode@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" @@ -8465,6 +8581,10 @@ thunky@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" +timed-out@4.0.1, timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + timed-out@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" @@ -8473,10 +8593,6 @@ timed-out@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" -timed-out@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - timers-browserify@^1.0.1: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" @@ -8919,6 +9035,13 @@ util-extend@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + util@0.10.3, util@^0.10.3, util@~0.10.1: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" @@ -8937,6 +9060,10 @@ utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" +uuid@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" + uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"