Merge branch 'feature/chrome-options' into develop
This commit is contained in:
@@ -18,5 +18,9 @@ module.exports = {
|
|||||||
import: 'src/prompt.ts',
|
import: 'src/prompt.ts',
|
||||||
runtime: false,
|
runtime: false,
|
||||||
},
|
},
|
||||||
|
options: {
|
||||||
|
import: 'src/options.ts',
|
||||||
|
runtime: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as Configuration;
|
} as Configuration;
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Gooti",
|
"name": "Gooti",
|
||||||
"description": "Nostr Identity Manager & Signer",
|
"description": "Nostr Identity Manager & Signer",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"homepage_url": "https://getgooti.com",
|
"homepage_url": "https://getgooti.com",
|
||||||
|
"options_page": "options.html",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"windows",
|
"windows",
|
||||||
"storage"
|
"storage"
|
||||||
|
|||||||
173
projects/chrome/public/options.html
Normal file
173
projects/chrome/public/options.html
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<title>Gooti - Options</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||||
|
<script src="scripts.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 60px;
|
||||||
|
width: 60px;
|
||||||
|
border-radius: 100%;
|
||||||
|
border: 2px solid var(--primary);
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
color: #b9d6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-header {
|
||||||
|
padding-top: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-header {
|
||||||
|
padding-top: 28px;
|
||||||
|
font-size: 20px;
|
||||||
|
max-width: 460px;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
.accent {
|
||||||
|
color: #d63384;
|
||||||
|
border: 1px solid #d63384;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.middle {
|
||||||
|
margin-top: 68px;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background-light);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-label {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
color: gray;
|
||||||
|
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshots-list {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
<div class="container sam-flex-row gap" style="margin-top: 16px">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="gooti.svg" alt="" />
|
||||||
|
</div>
|
||||||
|
<span class="brand-name">Gooti</span>
|
||||||
|
<span>OPTIONS</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container sam-flex-column center">
|
||||||
|
<span class="main-header"> Nostr Identity Manager & Signer </span>
|
||||||
|
<span class="sub-header">
|
||||||
|
Manage and switch between
|
||||||
|
<span class="accent">multiple identities</span>
|
||||||
|
while interacting with Nostr apps
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="middle">
|
||||||
|
<div class="container sam-flex-column">
|
||||||
|
<!-- VAULT SNAPSHOTS -->
|
||||||
|
<span class="option-label">Vault Snapshots</span>
|
||||||
|
<span class="option-text">
|
||||||
|
Importing a previously exported vault snapshot is not
|
||||||
|
<b>directly</b>
|
||||||
|
possible in the extension's popup window. This is due to the
|
||||||
|
browser's limitation of automatically closing the popup when it
|
||||||
|
looses focus, making it impossible to drop or select a file there.
|
||||||
|
</span>
|
||||||
|
<span class="option-text">
|
||||||
|
To circumvent this limitation, you need to upload your snapshot here
|
||||||
|
and make it available for the extension to import in the popup.
|
||||||
|
</span>
|
||||||
|
<span class="option-text">
|
||||||
|
<b>
|
||||||
|
Uploading a snapshot here does NOT automatically start an import!
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
<span class="option-text">
|
||||||
|
<b>
|
||||||
|
The data remains inside this browser and is NOT uploaded to
|
||||||
|
any server!
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="sam-mt-h sam-flex-row gap">
|
||||||
|
<button id="uploadSnapshotsButton" class="btn btn-primary">
|
||||||
|
Upload Snapshots
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button id="deleteSnapshotsButton" class="btn btn-danger">
|
||||||
|
Delete Snapshots
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul id="snapshotsList" class="snapshots-list">
|
||||||
|
<!-- will be filled by JS -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="uploadSnapshotInput"
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
style="position: absolute; top: 0; display: none"
|
||||||
|
accept=".json"
|
||||||
|
/>
|
||||||
|
<script src="options.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -15,6 +15,7 @@ import { HomeComponent as EditIdentityHomeComponent } from './components/edit-id
|
|||||||
import { KeysComponent as EditIdentityKeysComponent } from './components/edit-identity/keys/keys.component';
|
import { KeysComponent as EditIdentityKeysComponent } from './components/edit-identity/keys/keys.component';
|
||||||
import { PermissionsComponent as EditIdentityPermissionsComponent } from './components/edit-identity/permissions/permissions.component';
|
import { PermissionsComponent as EditIdentityPermissionsComponent } from './components/edit-identity/permissions/permissions.component';
|
||||||
import { RelaysComponent as EditIdentityRelaysComponent } from './components/edit-identity/relays/relays.component';
|
import { RelaysComponent as EditIdentityRelaysComponent } from './components/edit-identity/relays/relays.component';
|
||||||
|
import { VaultImportComponent } from './components/vault-import/vault-import.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -39,6 +40,10 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'vault-import',
|
||||||
|
component: VaultImportComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'home',
|
path: 'home',
|
||||||
component: HomeComponent,
|
component: HomeComponent,
|
||||||
|
|||||||
@@ -23,7 +23,15 @@ export class ChromeMetaHandler extends GootiMetaHandler {
|
|||||||
await chrome.storage.local.set(data);
|
await chrome.storage.local.set(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearData(): Promise<void> {
|
async clearData(keep: string[]): Promise<void> {
|
||||||
await chrome.storage.local.remove(this.metaProperties);
|
const toBeRemovedProperties: string[] = [];
|
||||||
|
|
||||||
|
for (const property of this.metaProperties) {
|
||||||
|
if (!keep.includes(property)) {
|
||||||
|
toBeRemovedProperties.push(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await chrome.storage.local.remove(toBeRemovedProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,7 @@
|
|||||||
Export Vault
|
Export Vault
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button class="btn btn-primary" (click)="navigate('/vault-import')">
|
||||||
class="btn btn-primary"
|
|
||||||
(click)="
|
|
||||||
confirm.show(
|
|
||||||
'Do you really want to import a vault? All existing data will be overwritten.',
|
|
||||||
onImportVault.bind(fileInput)
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Import Vault
|
Import Vault
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -26,12 +18,12 @@
|
|||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
(click)="
|
(click)="
|
||||||
confirm.show(
|
confirm.show(
|
||||||
'Do you really want to delete your vault with all identities?',
|
'Do you really want to reset your extension? Every data will be lost.',
|
||||||
onDeleteVault.bind(this)
|
onResetExtension.bind(this)
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Delete Vault
|
Reset Extension
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<lib-confirm #confirm> </lib-confirm>
|
<lib-confirm #confirm> </lib-confirm>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
BrowserSyncFlow,
|
BrowserSyncFlow,
|
||||||
ConfirmComponent,
|
ConfirmComponent,
|
||||||
DateHelper,
|
DateHelper,
|
||||||
|
NavComponent,
|
||||||
StartupService,
|
StartupService,
|
||||||
StorageService,
|
StorageService,
|
||||||
} from '@common';
|
} from '@common';
|
||||||
@@ -15,7 +16,7 @@ import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage
|
|||||||
templateUrl: './settings.component.html',
|
templateUrl: './settings.component.html',
|
||||||
styleUrl: './settings.component.scss',
|
styleUrl: './settings.component.scss',
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent extends NavComponent implements OnInit {
|
||||||
syncFlow: string | undefined;
|
syncFlow: string | undefined;
|
||||||
|
|
||||||
readonly #storage = inject(StorageService);
|
readonly #storage = inject(StorageService);
|
||||||
@@ -41,9 +42,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onDeleteVault() {
|
async onResetExtension() {
|
||||||
try {
|
try {
|
||||||
await this.#storage.deleteVault();
|
await this.#storage.resetExtension();
|
||||||
this.#startup.startOver(getNewStorageServiceConfig());
|
this.#startup.startOver(getNewStorageServiceConfig());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="sam-flex-column center">
|
<div class="sam-flex-column center">
|
||||||
<div class="sam-flex-column gap" style="align-items: center">
|
<div class="sam-flex-column gap" style="align-items: center">
|
||||||
<div class="logo-frame">
|
<div class="logo-frame">
|
||||||
<img src="gooti.svg" height="120" width="120" alt=""/>
|
<img src="gooti.svg" height="120" width="120" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -25,18 +25,10 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
(click)="fileInput.click()"
|
(click)="router.navigateByUrl('/vault-import')"
|
||||||
>
|
>
|
||||||
<span>Import a vault</span>
|
<span>Import a vault</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
|
||||||
#fileInput
|
|
||||||
class="file-input"
|
|
||||||
type="file"
|
|
||||||
(change)="onImportFileChange($event)"
|
|
||||||
accept=".json"
|
|
||||||
/>
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { BrowserSyncData, StartupService, StorageService } from '@common';
|
import { NavComponent } from '@common';
|
||||||
import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
@@ -9,28 +8,6 @@ import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage
|
|||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
styleUrl: './home.component.scss',
|
styleUrl: './home.component.scss',
|
||||||
})
|
})
|
||||||
export class HomeComponent {
|
export class HomeComponent extends NavComponent {
|
||||||
readonly router = inject(Router);
|
readonly router = inject(Router);
|
||||||
readonly #storage = inject(StorageService);
|
|
||||||
readonly #startup = inject(StartupService);
|
|
||||||
|
|
||||||
async onImportFileChange(event: Event) {
|
|
||||||
try {
|
|
||||||
const element = event.currentTarget as HTMLInputElement;
|
|
||||||
const file = element.files !== null ? element.files[0] : undefined;
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await file.text();
|
|
||||||
const vault = JSON.parse(text) as BrowserSyncData;
|
|
||||||
console.log(vault);
|
|
||||||
|
|
||||||
await this.#storage.importVault(vault);
|
|
||||||
this.#startup.startOver(getNewStorageServiceConfig());
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="custom-header">
|
||||||
|
<lib-icon-button
|
||||||
|
class="button"
|
||||||
|
icon="chevron-left"
|
||||||
|
(click)="navigateBack()"
|
||||||
|
></lib-icon-button>
|
||||||
|
|
||||||
|
<span class="text">Import Vault </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sam-pl sam-pr sam-flex-column gap">
|
||||||
|
<span class="sam-text-muted">
|
||||||
|
You can select any snapshot that you have previously uploaded on the
|
||||||
|
<a href="options.html" target="_blank">options page</a>.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<select class="form-select sam-text-sm" [(ngModel)]="selectedSnapshot">
|
||||||
|
@for(snapshot of snapshots; track snapshot) {
|
||||||
|
<option [ngValue]="snapshot">
|
||||||
|
<span>{{ snapshot.fileName }}</span>
|
||||||
|
</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<span class="sam-text-muted" style="font-size: 12px">
|
||||||
|
Please note that your data will be imported regarding your current SYNC
|
||||||
|
setting: <b>{{ syncText }}</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sam-flex-grow"></div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
[disabled]="!selectedSnapshot"
|
||||||
|
class="import-button btn btn-primary"
|
||||||
|
(click)="onClickImport()"
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
:host {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
.custom-header {
|
||||||
|
padding-top: var(--size);
|
||||||
|
padding-bottom: var(--size);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--background);
|
||||||
|
|
||||||
|
.button {
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: 2;
|
||||||
|
justify-self: start;
|
||||||
|
margin-left: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-row-end: 2;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
justify-self: center;
|
||||||
|
height: 32px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-button {
|
||||||
|
margin: var(--size);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { VaultImportComponent } from './vault-import.component';
|
||||||
|
|
||||||
|
describe('VaultImportComponent', () => {
|
||||||
|
let component: VaultImportComponent;
|
||||||
|
let fixture: ComponentFixture<VaultImportComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [VaultImportComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(VaultImportComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import {
|
||||||
|
BrowserSyncFlow,
|
||||||
|
GootiMetaData_VaultSnapshot,
|
||||||
|
IconButtonComponent,
|
||||||
|
NavComponent,
|
||||||
|
StartupService,
|
||||||
|
StorageService,
|
||||||
|
} from '@common';
|
||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
import { getNewStorageServiceConfig } from '../../common/data/get-new-storage-service-config';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-vault-import',
|
||||||
|
imports: [IconButtonComponent, FormsModule],
|
||||||
|
templateUrl: './vault-import.component.html',
|
||||||
|
styleUrl: './vault-import.component.scss',
|
||||||
|
})
|
||||||
|
export class VaultImportComponent extends NavComponent implements OnInit {
|
||||||
|
snapshots: GootiMetaData_VaultSnapshot[] = [];
|
||||||
|
selectedSnapshot: GootiMetaData_VaultSnapshot | undefined;
|
||||||
|
syncText: string | undefined;
|
||||||
|
|
||||||
|
readonly #storage = inject(StorageService);
|
||||||
|
readonly #startup = inject(StartupService);
|
||||||
|
|
||||||
|
async openOptionsPage() {
|
||||||
|
await browser.runtime.openOptionsPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.#loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onClickImport() {
|
||||||
|
if (!this.selectedSnapshot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.#storage.deleteVault(true);
|
||||||
|
await this.#storage.importVault(this.selectedSnapshot.data);
|
||||||
|
this.#storage.isInitialized = false;
|
||||||
|
this.#startup.startOver(getNewStorageServiceConfig());
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #loadData() {
|
||||||
|
this.snapshots = (
|
||||||
|
this.#storage.getGootiMetaHandler().gootiMetaData?.vaultSnapshots ?? []
|
||||||
|
).sortBy((x) => x.fileName, 'desc');
|
||||||
|
|
||||||
|
const syncFlow =
|
||||||
|
this.#storage.getGootiMetaHandler().gootiMetaData?.syncFlow;
|
||||||
|
|
||||||
|
switch (syncFlow) {
|
||||||
|
case BrowserSyncFlow.BROWSER_SYNC:
|
||||||
|
this.syncText = 'GOOGLE CHROME';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
case BrowserSyncFlow.NO_SYNC:
|
||||||
|
this.syncText = 'OFF';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="content-login-vault">
|
<div class="content-login-vault">
|
||||||
<div class="sam-flex-column gap" style="align-items: center">
|
<div class="sam-flex-column gap" style="align-items: center">
|
||||||
<div class="logo-frame">
|
<div class="logo-frame">
|
||||||
<img src="gooti.svg" height="120" width="120" alt=""/>
|
<img src="gooti.svg" height="120" width="120" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sam-mt-2 input-group">
|
<div class="sam-mt-2 input-group">
|
||||||
@@ -45,14 +45,14 @@
|
|||||||
class="sam-mt"
|
class="sam-mt"
|
||||||
(click)="
|
(click)="
|
||||||
confirm.show(
|
confirm.show(
|
||||||
'Do you really want to delete your vault? All existing data will be lost.',
|
'Do you really want to reset the extension? All data will be lost.',
|
||||||
onClickDeleteVault.bind(this)
|
onClickResetExtension.bind(this)
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-link"
|
class="btn btn-link"
|
||||||
>
|
>
|
||||||
Delete Vault
|
Reset Extension
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ export class VaultLoginComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClickDeleteVault() {
|
async onClickResetExtension() {
|
||||||
try {
|
try {
|
||||||
await this.#storage.deleteVault();
|
await this.#storage.resetExtension();
|
||||||
this.#startup.startOver(getNewStorageServiceConfig());
|
this.#startup.startOver(getNewStorageServiceConfig());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
130
projects/chrome/src/options.ts
Normal file
130
projects/chrome/src/options.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import {
|
||||||
|
BrowserSyncData,
|
||||||
|
GOOTI_META_DATA_KEY,
|
||||||
|
GootiMetaData_VaultSnapshot,
|
||||||
|
} from '@common';
|
||||||
|
import './app/common/extensions/array';
|
||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functions
|
||||||
|
//
|
||||||
|
|
||||||
|
async function getGootiMetaDataVaultSnapshots(): Promise<
|
||||||
|
GootiMetaData_VaultSnapshot[]
|
||||||
|
> {
|
||||||
|
const data = (await browser.storage.local.get(
|
||||||
|
GOOTI_META_DATA_KEY.vaultSnapshots
|
||||||
|
)) as {
|
||||||
|
vaultSnapshots?: GootiMetaData_VaultSnapshot[];
|
||||||
|
};
|
||||||
|
|
||||||
|
return typeof data.vaultSnapshots === 'undefined'
|
||||||
|
? []
|
||||||
|
: data.vaultSnapshots.sortBy((x) => x.fileName, 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setGootiMetaDataVaultSnapshots(
|
||||||
|
vaultSnapshots: GootiMetaData_VaultSnapshot[]
|
||||||
|
): Promise<void> {
|
||||||
|
await browser.storage.local.set({
|
||||||
|
vaultSnapshots,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildSnapshotsList(snapshots: GootiMetaData_VaultSnapshot[]) {
|
||||||
|
const ul = document.getElementById('snapshotsList');
|
||||||
|
if (!ul) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the list
|
||||||
|
ul.innerHTML = '';
|
||||||
|
|
||||||
|
for (const snapshot of snapshots) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
|
||||||
|
const test =
|
||||||
|
'"' +
|
||||||
|
snapshot.fileName +
|
||||||
|
'"' +
|
||||||
|
' -> vault version: ' +
|
||||||
|
snapshot.data.version +
|
||||||
|
' -> identities: ' +
|
||||||
|
snapshot.data.identities.length +
|
||||||
|
' -> relays: ' +
|
||||||
|
snapshot.data.relays.length +
|
||||||
|
'';
|
||||||
|
|
||||||
|
li.innerText = test;
|
||||||
|
ul.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Main
|
||||||
|
//
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const uploadSnapshotsButton = document.getElementById(
|
||||||
|
'uploadSnapshotsButton'
|
||||||
|
);
|
||||||
|
const deleteSnapshotsButton = document.getElementById(
|
||||||
|
'deleteSnapshotsButton'
|
||||||
|
);
|
||||||
|
const uploadSnapshotInput = document.getElementById(
|
||||||
|
'uploadSnapshotInput'
|
||||||
|
) as HTMLInputElement;
|
||||||
|
|
||||||
|
deleteSnapshotsButton?.addEventListener('click', async () => {
|
||||||
|
await setGootiMetaDataVaultSnapshots([]);
|
||||||
|
rebuildSnapshotsList([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadSnapshotsButton?.addEventListener('click', async () => {
|
||||||
|
uploadSnapshotInput?.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadSnapshotInput?.addEventListener('change', async (event) => {
|
||||||
|
const files = (event.target as HTMLInputElement).files;
|
||||||
|
if (!files) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingSnapshots = await getGootiMetaDataVaultSnapshots();
|
||||||
|
|
||||||
|
const newSnapshots: GootiMetaData_VaultSnapshot[] = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const text = await file.text();
|
||||||
|
const vault = JSON.parse(text) as BrowserSyncData;
|
||||||
|
|
||||||
|
// Check, if the "new" file is already in the list (via fileName comparison)
|
||||||
|
if (existingSnapshots.some((x) => x.fileName === file.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSnapshots.push({
|
||||||
|
fileName: file.name,
|
||||||
|
data: vault,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshots = [...existingSnapshots, ...newSnapshots].sortBy(
|
||||||
|
(x) => x.fileName,
|
||||||
|
'desc'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Persist the new snapshots to the local storage
|
||||||
|
await setGootiMetaDataVaultSnapshots(snapshots);
|
||||||
|
|
||||||
|
//
|
||||||
|
rebuildSnapshotsList(snapshots);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshots = await getGootiMetaDataVaultSnapshots();
|
||||||
|
rebuildSnapshotsList(snapshots);
|
||||||
|
});
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
"src/background.ts",
|
"src/background.ts",
|
||||||
"src/gooti-extension.ts",
|
"src/gooti-extension.ts",
|
||||||
"src/gooti-content-script.ts",
|
"src/gooti-content-script.ts",
|
||||||
"src/prompt.ts"
|
"src/prompt.ts",
|
||||||
|
"src/options.ts"
|
||||||
],
|
],
|
||||||
"include": ["src/**/*.d.ts"]
|
"include": ["src/**/*.d.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
export class NavComponent {
|
export class NavComponent {
|
||||||
|
readonly #router = inject(Router);
|
||||||
|
|
||||||
navigateBack() {
|
navigateBack() {
|
||||||
window.history.back();
|
window.history.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigate(path: string) {
|
||||||
|
this.#router.navigate([path]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export abstract class GootiMetaHandler {
|
|||||||
|
|
||||||
#gootiMetaData?: GootiMetaData;
|
#gootiMetaData?: GootiMetaData;
|
||||||
|
|
||||||
readonly metaProperties = ['syncFlow'];
|
readonly metaProperties = ['syncFlow', 'vaultSnapshots'];
|
||||||
/**
|
/**
|
||||||
* Load the full data from the storage. If the storage is used for storing
|
* Load the full data from the storage. If the storage is used for storing
|
||||||
* other data (e.g. browser sync data when the user decided to NOT sync),
|
* other data (e.g. browser sync data when the user decided to NOT sync),
|
||||||
@@ -39,5 +39,5 @@ export abstract class GootiMetaHandler {
|
|||||||
await this.saveFullData(this.#gootiMetaData);
|
await this.saveFullData(this.#gootiMetaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract clearData(): Promise<void>;
|
abstract clearData(keep: string[]): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ export const deleteVault = async function (
|
|||||||
|
|
||||||
await this.getBrowserSyncHandler().clearData();
|
await this.getBrowserSyncHandler().clearData();
|
||||||
await this.getBrowserSessionHandler().clearData();
|
await this.getBrowserSessionHandler().clearData();
|
||||||
await this.getGootiMetaHandler().clearData();
|
|
||||||
|
|
||||||
if (!doNotSetIsInitializedToFalse) {
|
if (!doNotSetIsInitializedToFalse) {
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
|
|||||||
@@ -115,6 +115,14 @@ export class StorageService {
|
|||||||
await deleteVault.call(this, doNotSetIsInitializedToFalse);
|
await deleteVault.call(this, doNotSetIsInitializedToFalse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resetExtension() {
|
||||||
|
this.assureIsInitialized();
|
||||||
|
await this.getBrowserSyncHandler().clearData();
|
||||||
|
await this.getBrowserSessionHandler().clearData();
|
||||||
|
await this.getGootiMetaHandler().clearData([]);
|
||||||
|
this.isInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
async unlockVault(password: string): Promise<void> {
|
async unlockVault(password: string): Promise<void> {
|
||||||
await unlockVault.call(this, password);
|
await unlockVault.call(this, password);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,17 @@ export interface BrowserSessionData {
|
|||||||
relays: Relay_DECRYPTED[];
|
relays: Relay_DECRYPTED[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GootiMetaData_VaultSnapshot {
|
||||||
|
fileName: string;
|
||||||
|
data: BrowserSyncData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GOOTI_META_DATA_KEY = {
|
||||||
|
vaultSnapshots: 'vaultSnapshots',
|
||||||
|
};
|
||||||
|
|
||||||
export interface GootiMetaData {
|
export interface GootiMetaData {
|
||||||
syncFlow?: number; // 0 = no sync, 1 = browser sync, (future: 2 = Gooti sync, 3 = Custom sync (bring your own sync))
|
syncFlow?: number; // 0 = no sync, 1 = browser sync, (future: 2 = Gooti sync, 3 = Custom sync (bring your own sync))
|
||||||
|
|
||||||
|
vaultSnapshots?: GootiMetaData_VaultSnapshot[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
margin-top: var(--size-h);
|
margin-top: var(--size-h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sam-mt-hh {
|
||||||
|
margin-top: var(--size-hh);
|
||||||
|
}
|
||||||
|
|
||||||
.sam-mb {
|
.sam-mb {
|
||||||
margin-bottom: var(--size);
|
margin-bottom: var(--size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
--size-2: 32px;
|
--size-2: 32px;
|
||||||
--size: 16px;
|
--size: 16px;
|
||||||
--size-h: 8px;
|
--size-h: 8px;
|
||||||
|
--size-hh: 4px;
|
||||||
|
|
||||||
--background: #161c26;
|
--background: #161c26;
|
||||||
--background-light: #202733;
|
--background-light: #202733;
|
||||||
|
|||||||
Reference in New Issue
Block a user