From b20faf235967f929d6b41f7fe87c53539333033c Mon Sep 17 00:00:00 2001 From: DEV Sam Hayes Date: Tue, 4 Feb 2025 20:19:30 +0100 Subject: [PATCH] "copy" UI related things from chrome --- .husky/pre-commit | 2 +- angular.json | 29 +- firefox_prepare_manifest.sh | 7 + package.json | 7 +- .../edit-identity/edit-identity.component.ts | 6 +- projects/firefox/custom-webpack.config.ts | 26 ++ projects/firefox/public/bird.svg | 1 + projects/firefox/public/favicon.ico | Bin 15086 -> 0 bytes projects/firefox/public/gooti-with-bg.png | Bin 0 -> 983 bytes projects/firefox/public/gooti.svg | 1 + projects/firefox/public/manifest.json | 46 +++ projects/firefox/public/options.html | 173 +++++++++ projects/firefox/public/person-fill.svg | 3 + projects/firefox/public/prompt.html | 0 projects/firefox/src/app/app.component.html | 337 +----------------- projects/firefox/src/app/app.component.ts | 17 +- projects/firefox/src/app/app.routes.ts | 94 ++++- .../app/common/data/firefox-meta-handler.ts | 39 ++ .../common/data/firefox-session-handler.ts | 17 + .../common/data/firefox-sync-no-handler.ts | 63 ++++ .../common/data/firefox-sync-yes-handler.ts | 56 +++ .../data/get-new-storage-service-config.ts | 15 + .../src/app/common/extensions/array.ts | 95 +++++ .../edit-identity.component.html | 13 + .../edit-identity.component.scss | 47 +++ .../edit-identity.component.spec.ts | 23 ++ .../edit-identity/edit-identity.component.ts | 43 +++ .../edit-identity/home/home.component.html | 28 ++ .../edit-identity/home/home.component.scss | 8 + .../edit-identity/home/home.component.spec.ts | 23 ++ .../edit-identity/home/home.component.ts | 48 +++ .../edit-identity/keys/keys.component.html | 141 ++++++++ .../edit-identity/keys/keys.component.scss | 19 + .../edit-identity/keys/keys.component.spec.ts | 23 ++ .../edit-identity/keys/keys.component.ts | 74 ++++ .../permissions/permissions.component.html | 40 +++ .../permissions/permissions.component.scss | 61 ++++ .../permissions/permissions.component.spec.ts | 23 ++ .../permissions/permissions.component.ts | 75 ++++ .../relays/relays.component.html | 79 ++++ .../relays/relays.component.scss | 30 ++ .../relays/relays.component.spec.ts | 23 ++ .../edit-identity/relays/relays.component.ts | 131 +++++++ .../app/components/home/home.component.html | 36 ++ .../app/components/home/home.component.scss | 43 +++ .../components/home/home.component.spec.ts | 23 ++ .../src/app/components/home/home.component.ts | 10 + .../home/identities/identities.component.html | 78 ++++ .../home/identities/identities.component.scss | 68 ++++ .../identities/identities.component.spec.ts | 23 ++ .../home/identities/identities.component.ts | 33 ++ .../home/identity/identity.component.html | 56 +++ .../home/identity/identity.component.scss | 41 +++ .../home/identity/identity.component.spec.ts | 23 ++ .../home/identity/identity.component.ts | 117 ++++++ .../components/home/info/info.component.html | 34 ++ .../components/home/info/info.component.scss | 9 + .../home/info/info.component.spec.ts | 23 ++ .../components/home/info/info.component.ts | 13 + .../home/settings/settings.component.html | 29 ++ .../home/settings/settings.component.scss | 14 + .../home/settings/settings.component.spec.ts | 23 ++ .../home/settings/settings.component.ts | 73 ++++ .../new-identity/new-identity.component.html | 85 +++++ .../new-identity/new-identity.component.scss | 13 + .../new-identity.component.spec.ts | 23 ++ .../new-identity/new-identity.component.ts | 88 +++++ .../vault-create/home/home.component.html | 34 ++ .../vault-create/home/home.component.scss | 17 + .../vault-create/home/home.component.spec.ts | 23 ++ .../vault-create/home/home.component.ts | 12 + .../vault-create/new/new.component.html | 48 +++ .../vault-create/new/new.component.scss | 48 +++ .../vault-create/new/new.component.spec.ts | 23 ++ .../vault-create/new/new.component.ts | 34 ++ .../vault-create/vault-create.component.html | 1 + .../vault-create/vault-create.component.scss | 0 .../vault-create.component.spec.ts | 23 ++ .../vault-create/vault-create.component.ts | 12 + .../vault-import/vault-import.component.html | 39 ++ .../vault-import/vault-import.component.scss | 46 +++ .../vault-import.component.spec.ts | 23 ++ .../vault-import/vault-import.component.ts | 71 ++++ .../vault-login/vault-login.component.html | 72 ++++ .../vault-login/vault-login.component.scss | 19 + .../vault-login/vault-login.component.spec.ts | 23 ++ .../vault-login/vault-login.component.ts | 55 +++ .../components/welcome/welcome.component.html | 44 +++ .../components/welcome/welcome.component.scss | 8 + .../welcome/welcome.component.spec.ts | 23 ++ .../components/welcome/welcome.component.ts | 41 +++ projects/firefox/src/background.ts | 0 projects/firefox/src/gooti-content-script.ts | 0 projects/firefox/src/gooti-extension.ts | 0 projects/firefox/src/index.html | 5 +- projects/firefox/src/main.ts | 6 +- projects/firefox/src/options.ts | 130 +++++++ projects/firefox/src/prompt.ts | 0 projects/firefox/src/styles.scss | 21 +- projects/firefox/tsconfig.app.json | 11 +- 100 files changed, 3514 insertions(+), 362 deletions(-) create mode 100755 firefox_prepare_manifest.sh create mode 100644 projects/firefox/custom-webpack.config.ts create mode 100644 projects/firefox/public/bird.svg delete mode 100644 projects/firefox/public/favicon.ico create mode 100644 projects/firefox/public/gooti-with-bg.png create mode 100644 projects/firefox/public/gooti.svg create mode 100644 projects/firefox/public/manifest.json create mode 100644 projects/firefox/public/options.html create mode 100644 projects/firefox/public/person-fill.svg create mode 100644 projects/firefox/public/prompt.html create mode 100644 projects/firefox/src/app/common/data/firefox-meta-handler.ts create mode 100644 projects/firefox/src/app/common/data/firefox-session-handler.ts create mode 100644 projects/firefox/src/app/common/data/firefox-sync-no-handler.ts create mode 100644 projects/firefox/src/app/common/data/firefox-sync-yes-handler.ts create mode 100644 projects/firefox/src/app/common/data/get-new-storage-service-config.ts create mode 100644 projects/firefox/src/app/common/extensions/array.ts create mode 100644 projects/firefox/src/app/components/edit-identity/edit-identity.component.html create mode 100644 projects/firefox/src/app/components/edit-identity/edit-identity.component.scss create mode 100644 projects/firefox/src/app/components/edit-identity/edit-identity.component.spec.ts create mode 100644 projects/firefox/src/app/components/edit-identity/edit-identity.component.ts create mode 100644 projects/firefox/src/app/components/edit-identity/home/home.component.html create mode 100644 projects/firefox/src/app/components/edit-identity/home/home.component.scss create mode 100644 projects/firefox/src/app/components/edit-identity/home/home.component.spec.ts create mode 100644 projects/firefox/src/app/components/edit-identity/home/home.component.ts create mode 100644 projects/firefox/src/app/components/edit-identity/keys/keys.component.html create mode 100644 projects/firefox/src/app/components/edit-identity/keys/keys.component.scss create mode 100644 projects/firefox/src/app/components/edit-identity/keys/keys.component.spec.ts create mode 100644 projects/firefox/src/app/components/edit-identity/keys/keys.component.ts create mode 100644 projects/firefox/src/app/components/edit-identity/permissions/permissions.component.html create mode 100644 projects/firefox/src/app/components/edit-identity/permissions/permissions.component.scss create mode 100644 projects/firefox/src/app/components/edit-identity/permissions/permissions.component.spec.ts create mode 100644 projects/firefox/src/app/components/edit-identity/permissions/permissions.component.ts create mode 100644 projects/firefox/src/app/components/edit-identity/relays/relays.component.html create mode 100644 projects/firefox/src/app/components/edit-identity/relays/relays.component.scss create mode 100644 projects/firefox/src/app/components/edit-identity/relays/relays.component.spec.ts create mode 100644 projects/firefox/src/app/components/edit-identity/relays/relays.component.ts create mode 100644 projects/firefox/src/app/components/home/home.component.html create mode 100644 projects/firefox/src/app/components/home/home.component.scss create mode 100644 projects/firefox/src/app/components/home/home.component.spec.ts create mode 100644 projects/firefox/src/app/components/home/home.component.ts create mode 100644 projects/firefox/src/app/components/home/identities/identities.component.html create mode 100644 projects/firefox/src/app/components/home/identities/identities.component.scss create mode 100644 projects/firefox/src/app/components/home/identities/identities.component.spec.ts create mode 100644 projects/firefox/src/app/components/home/identities/identities.component.ts create mode 100644 projects/firefox/src/app/components/home/identity/identity.component.html create mode 100644 projects/firefox/src/app/components/home/identity/identity.component.scss create mode 100644 projects/firefox/src/app/components/home/identity/identity.component.spec.ts create mode 100644 projects/firefox/src/app/components/home/identity/identity.component.ts create mode 100644 projects/firefox/src/app/components/home/info/info.component.html create mode 100644 projects/firefox/src/app/components/home/info/info.component.scss create mode 100644 projects/firefox/src/app/components/home/info/info.component.spec.ts create mode 100644 projects/firefox/src/app/components/home/info/info.component.ts create mode 100644 projects/firefox/src/app/components/home/settings/settings.component.html create mode 100644 projects/firefox/src/app/components/home/settings/settings.component.scss create mode 100644 projects/firefox/src/app/components/home/settings/settings.component.spec.ts create mode 100644 projects/firefox/src/app/components/home/settings/settings.component.ts create mode 100644 projects/firefox/src/app/components/new-identity/new-identity.component.html create mode 100644 projects/firefox/src/app/components/new-identity/new-identity.component.scss create mode 100644 projects/firefox/src/app/components/new-identity/new-identity.component.spec.ts create mode 100644 projects/firefox/src/app/components/new-identity/new-identity.component.ts create mode 100644 projects/firefox/src/app/components/vault-create/home/home.component.html create mode 100644 projects/firefox/src/app/components/vault-create/home/home.component.scss create mode 100644 projects/firefox/src/app/components/vault-create/home/home.component.spec.ts create mode 100644 projects/firefox/src/app/components/vault-create/home/home.component.ts create mode 100644 projects/firefox/src/app/components/vault-create/new/new.component.html create mode 100644 projects/firefox/src/app/components/vault-create/new/new.component.scss create mode 100644 projects/firefox/src/app/components/vault-create/new/new.component.spec.ts create mode 100644 projects/firefox/src/app/components/vault-create/new/new.component.ts create mode 100644 projects/firefox/src/app/components/vault-create/vault-create.component.html create mode 100644 projects/firefox/src/app/components/vault-create/vault-create.component.scss create mode 100644 projects/firefox/src/app/components/vault-create/vault-create.component.spec.ts create mode 100644 projects/firefox/src/app/components/vault-create/vault-create.component.ts create mode 100644 projects/firefox/src/app/components/vault-import/vault-import.component.html create mode 100644 projects/firefox/src/app/components/vault-import/vault-import.component.scss create mode 100644 projects/firefox/src/app/components/vault-import/vault-import.component.spec.ts create mode 100644 projects/firefox/src/app/components/vault-import/vault-import.component.ts create mode 100644 projects/firefox/src/app/components/vault-login/vault-login.component.html create mode 100644 projects/firefox/src/app/components/vault-login/vault-login.component.scss create mode 100644 projects/firefox/src/app/components/vault-login/vault-login.component.spec.ts create mode 100644 projects/firefox/src/app/components/vault-login/vault-login.component.ts create mode 100644 projects/firefox/src/app/components/welcome/welcome.component.html create mode 100644 projects/firefox/src/app/components/welcome/welcome.component.scss create mode 100644 projects/firefox/src/app/components/welcome/welcome.component.spec.ts create mode 100644 projects/firefox/src/app/components/welcome/welcome.component.ts create mode 100644 projects/firefox/src/background.ts create mode 100644 projects/firefox/src/gooti-content-script.ts create mode 100644 projects/firefox/src/gooti-extension.ts create mode 100644 projects/firefox/src/options.ts create mode 100644 projects/firefox/src/prompt.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index 3867a0f..727581e 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -npm run lint +# npm run lint diff --git a/angular.json b/angular.json index 9f35c17..088d2a2 100644 --- a/angular.json +++ b/angular.json @@ -121,11 +121,14 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular-builders/custom-webpack:browser", "options": { + "customWebpackConfig": { + "path": "projects/firefox/custom-webpack.config.ts" + }, "outputPath": "dist/firefox", "index": "projects/firefox/src/index.html", - "browser": "projects/firefox/src/main.ts", + "main": "projects/firefox/src/main.ts", "polyfills": ["zone.js"], "tsConfig": "projects/firefox/tsconfig.app.json", "inlineStyleLanguage": "scss", @@ -136,15 +139,15 @@ } ], "styles": ["projects/firefox/src/styles.scss"], - "scripts": [] + "scripts": ["node_modules/bootstrap/dist/js/bootstrap.bundle.js"] }, "configurations": { "production": { "budgets": [ { "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" + "maximumWarning": "5MB", + "maximumError": "10MB" }, { "type": "anyComponentStyle", @@ -152,7 +155,11 @@ "maximumError": "8kB" } ], - "outputHashing": "all" + "optimization": { + "scripts": true, + "styles": false, + "fonts": true + } }, "development": { "optimization": false, @@ -192,6 +199,16 @@ "styles": ["projects/firefox/src/styles.scss"], "scripts": [] } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "projects/firefox/**/*.ts", + "projects/firefox/**/*.html" + ], + "eslintConfig": "projects/firefox/eslint.config.js" + } } } }, diff --git a/firefox_prepare_manifest.sh b/firefox_prepare_manifest.sh new file mode 100755 index 0000000..8d25799 --- /dev/null +++ b/firefox_prepare_manifest.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +version=$( cat package.json | jq '.custom.firefox.version' | tr -d '"') + +jq '.version = $newVersion' --arg newVersion $version ./projects/firefox/public/manifest.json > ./projects/firefox/public/tmp.manifest.json && mv ./projects/firefox/public/tmp.manifest.json ./projects/firefox/public/manifest.json + +echo $version \ No newline at end of file diff --git a/package.json b/package.json index 7c97c85..6ec04e1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "version": "0.0.2" }, "firefox": { - "version": "0.0.0" + "version": "0.0.1" } }, "scripts": { @@ -16,10 +16,11 @@ "start:chrome": "ng serve chrome", "start:firefox": "ng serve firefox", "prepare:chrome": "./chrome_prepare_manifest.sh", + "prepare:firefox": "./firefox_prepare_manifest.sh", "build:chrome": "npm run prepare:chrome && ng build chrome", - "build:firefox": "ng build firefox", + "build:firefox": "npm run prepare:firefox && ng build firefox", "watch:chrome": "npm run prepare:chrome && ng build chrome --watch --configuration development", - "watch:firefox": "ng build firefox --watch --configuration development", + "watch:firefox": "npm run prepare:firefox && ng build firefox --watch --configuration development", "test": "ng test", "lint": "ng lint", "prepare": "husky" diff --git a/projects/chrome/src/app/components/edit-identity/edit-identity.component.ts b/projects/chrome/src/app/components/edit-identity/edit-identity.component.ts index 4c652c5..c2031ed 100644 --- a/projects/chrome/src/app/components/edit-identity/edit-identity.component.ts +++ b/projects/chrome/src/app/components/edit-identity/edit-identity.component.ts @@ -1,6 +1,10 @@ import { Component, inject, OnInit } from '@angular/core'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; -import { IconButtonComponent, Identity_DECRYPTED, StorageService } from '@common'; +import { + IconButtonComponent, + Identity_DECRYPTED, + StorageService, +} from '@common'; @Component({ selector: 'app-edit-identity', diff --git a/projects/firefox/custom-webpack.config.ts b/projects/firefox/custom-webpack.config.ts new file mode 100644 index 0000000..d5586e0 --- /dev/null +++ b/projects/firefox/custom-webpack.config.ts @@ -0,0 +1,26 @@ +import type { Configuration } from 'webpack'; + +module.exports = { + entry: { + background: { + import: 'src/background.ts', + runtime: false, + }, + 'gooti-extension': { + import: 'src/gooti-extension.ts', + runtime: false, + }, + 'gooti-content-script': { + import: 'src/gooti-content-script.ts', + runtime: false, + }, + prompt: { + import: 'src/prompt.ts', + runtime: false, + }, + options: { + import: 'src/options.ts', + runtime: false, + }, + }, +} as Configuration; diff --git a/projects/firefox/public/bird.svg b/projects/firefox/public/bird.svg new file mode 100644 index 0000000..2f8d9ca --- /dev/null +++ b/projects/firefox/public/bird.svg @@ -0,0 +1 @@ +Origami Paper Bird Streamline Icon: https://streamlinehq.com \ No newline at end of file diff --git a/projects/firefox/public/favicon.ico b/projects/firefox/public/favicon.ico deleted file mode 100644 index 57614f9c967596fad0a3989bec2b1deff33034f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( diff --git a/projects/firefox/public/gooti-with-bg.png b/projects/firefox/public/gooti-with-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..19f2fa22fd14edd31aec059b8fbca14eb2001e1a GIT binary patch literal 983 zcmV;|11S87P)C9E*0{?yRIfACa@NSn)rS z{0GeJ%|!C|kcecF%oUFjvVDZgC?ye{MF_7sMCP_5Xqv{&k!vhaOG}6n;~pe;X?ESK z6h3;C#+MByXF!f!$IP`6vYp|#%CCrIR}iK1Z%Lim^0z48v4=08lXMO%rl%$lJI-P) zDxqvSoOh0*ciV~5i6?c;>@CPqCzxUWLcfR5L+?}lNCkDzNKy^>u?JQy%i!q;mr`yO zocP8>Z>xhykSLuvv+Iu%S$z;O-G}ZujplQJ8AhdM6SAG5=80n7cv7HC=3VK4eVYa4 zRzcUW!s#9&wcE}DofzSg8uBX+l4*f*s}}9wmJqU@8xQj;!?3GZpi6jXzu4bU|L)$0YZjy*2;x>fPvWyQ@YMQ__r z$bm+Zkt88zC(tBRl?h^HEP)ey1((`kH~@zGBzGc7CK4nq1zP=zc>6X9b{50TkPA6- zjXTjS=~=ZTAOoF*0z-W2TFyClCQ)m01gQq|9w-!i?6g2nJt90sGMXhltCmDm1bORM zbnj=3_x!-cwp2c7DWrc~vMLjba|K9)?*>+)^U2hsc&F+0DY#T%fm zrGQIb3%0caSD=u|!8Sy|OCp*jJ*$>~NQ7@AN1M^TpWyFl=X_fRVOrigami Paper Bird Streamline Icon: https://streamlinehq.com \ No newline at end of file diff --git a/projects/firefox/public/manifest.json b/projects/firefox/public/manifest.json new file mode 100644 index 0000000..31bb874 --- /dev/null +++ b/projects/firefox/public/manifest.json @@ -0,0 +1,46 @@ +{ + "manifest_version": 3, + "name": "Gooti", + "description": "Nostr Identity Manager & Signer", + "version": "0.0.1", + "homepage_url": "https://getgooti.com", + "options_page": "options.html", + "permissions": [ + "storage" + ], + "action": { + "default_popup": "index.html", + "default_icon": "gooti-with-bg.png" + }, + "background": { + "scripts": [ + "background.js" + ] + }, + "content_scripts": [ + { + "run_at": "document_end", + "matches": [ + "" + ], + "js": [ + "gooti-content-script.js" + ] + } + ], + "web_accessible_resources": [ + { + "resources": [ + "gooti-extension.js" + ], + "matches": [ + "" + ] + } + ], + "browser_specific_settings": { + "gecko": { + "id": "firefox@getgooti.com" + } + } +} diff --git a/projects/firefox/public/options.html b/projects/firefox/public/options.html new file mode 100644 index 0000000..a259a58 --- /dev/null +++ b/projects/firefox/public/options.html @@ -0,0 +1,173 @@ + + + + + Gooti - Options + + + + + +
+
+ + Gooti + OPTIONS +
+ +
+ Nostr Identity Manager & Signer + + Manage and switch between + multiple identities + while interacting with Nostr apps + +
+ +
+
+ + Vault Snapshots + + Importing a previously exported vault snapshot is not + directly + 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. + + + To circumvent this limitation, you need to upload your snapshot here + and make it available for the extension to import in the popup. + + + + Uploading a snapshot here does NOT automatically start an import! + + + + + The data remains inside this browser and is NOT uploaded to any + server! + + + +
+ + + +
+ +
    + +
+
+
+
+ + + + + diff --git a/projects/firefox/public/person-fill.svg b/projects/firefox/public/person-fill.svg new file mode 100644 index 0000000..660a118 --- /dev/null +++ b/projects/firefox/public/person-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/projects/firefox/public/prompt.html b/projects/firefox/public/prompt.html new file mode 100644 index 0000000..e69de29 diff --git a/projects/firefox/src/app/app.component.html b/projects/firefox/src/app/app.component.html index 36093e1..90c6b64 100644 --- a/projects/firefox/src/app/app.component.html +++ b/projects/firefox/src/app/app.component.html @@ -1,336 +1 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - + \ No newline at end of file diff --git a/projects/firefox/src/app/app.component.ts b/projects/firefox/src/app/app.component.ts index 033ded3..510563a 100644 --- a/projects/firefox/src/app/app.component.ts +++ b/projects/firefox/src/app/app.component.ts @@ -1,12 +1,21 @@ -import { Component } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; +import { LoggerService, StartupService } from '@common'; +import { getNewStorageServiceConfig } from './common/data/get-new-storage-service-config'; import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', imports: [RouterOutlet], templateUrl: './app.component.html', - styleUrl: './app.component.scss' + styleUrl: './app.component.scss', }) -export class AppComponent { - title = 'firefox'; +export class AppComponent implements OnInit { + readonly #startup = inject(StartupService); + readonly #logger = inject(LoggerService); + + ngOnInit(): void { + this.#logger.initialize('Gooti Firefox Extension'); + + this.#startup.startOver(getNewStorageServiceConfig()); + } } diff --git a/projects/firefox/src/app/app.routes.ts b/projects/firefox/src/app/app.routes.ts index dc39edb..fd7cd58 100644 --- a/projects/firefox/src/app/app.routes.ts +++ b/projects/firefox/src/app/app.routes.ts @@ -1,3 +1,95 @@ import { Routes } from '@angular/router'; +import { HomeComponent as VaultCreateHomeComponent } from './components/vault-create/home/home.component'; +import { NewComponent as VaultCreateNewComponent } from './components/vault-create/new/new.component'; +import { HomeComponent } from './components/home/home.component'; +import { IdentitiesComponent } from './components/home/identities/identities.component'; +import { IdentityComponent } from './components/home/identity/identity.component'; +import { InfoComponent } from './components/home/info/info.component'; +import { SettingsComponent } from './components/home/settings/settings.component'; +import { NewIdentityComponent } from './components/new-identity/new-identity.component'; +import { EditIdentityComponent } from './components/edit-identity/edit-identity.component'; +import { HomeComponent as EditIdentityHomeComponent } from './components/edit-identity/home/home.component'; +import { KeysComponent as EditIdentityKeysComponent } from './components/edit-identity/keys/keys.component'; +import { PermissionsComponent as EditIdentityPermissionsComponent } from './components/edit-identity/permissions/permissions.component'; +import { RelaysComponent as EditIdentityRelaysComponent } from './components/edit-identity/relays/relays.component'; +import { WelcomeComponent } from './components/welcome/welcome.component'; +import { VaultLoginComponent } from './components/vault-login/vault-login.component'; +import { VaultCreateComponent } from './components/vault-create/vault-create.component'; +import { VaultImportComponent } from './components/vault-import/vault-import.component'; -export const routes: Routes = []; +export const routes: Routes = [ + { + path: 'welcome', + component: WelcomeComponent, + }, + { + path: 'vault-login', + component: VaultLoginComponent, + }, + { + path: 'vault-create', + component: VaultCreateComponent, + children: [ + { + path: 'home', + component: VaultCreateHomeComponent, + }, + { + path: 'new', + component: VaultCreateNewComponent, + }, + ], + }, + { + path: 'vault-import', + component: VaultImportComponent, + }, + { + path: 'home', + component: HomeComponent, + children: [ + { + path: 'identities', + component: IdentitiesComponent, + }, + { + path: 'identity', + component: IdentityComponent, + }, + { + path: 'info', + component: InfoComponent, + }, + { + path: 'settings', + component: SettingsComponent, + }, + ], + }, + { + path: 'new-identity', + component: NewIdentityComponent, + }, + { + path: 'edit-identity/:id', + component: EditIdentityComponent, + children: [ + { + path: 'home', + component: EditIdentityHomeComponent, + }, + { + path: 'keys', + component: EditIdentityKeysComponent, + }, + { + path: 'permissions', + component: EditIdentityPermissionsComponent, + }, + { + path: 'relays', + component: EditIdentityRelaysComponent, + }, + ], + }, +]; diff --git a/projects/firefox/src/app/common/data/firefox-meta-handler.ts b/projects/firefox/src/app/common/data/firefox-meta-handler.ts new file mode 100644 index 0000000..3000787 --- /dev/null +++ b/projects/firefox/src/app/common/data/firefox-meta-handler.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { GootiMetaData, GootiMetaHandler } from '@common'; +import browser from 'webextension-polyfill'; + +export class FirefoxMetaHandler extends GootiMetaHandler { + async loadFullData(): Promise>> { + const dataWithPossibleAlienProperties = await browser.storage.local.get( + null + ); + + if (Object.keys(dataWithPossibleAlienProperties).length === 0) { + return dataWithPossibleAlienProperties; + } + + const data: Partial> = {}; + this.metaProperties.forEach((property) => { + data[property] = dataWithPossibleAlienProperties[property]; + }); + + return data; + } + + async saveFullData(data: GootiMetaData): Promise { + await browser.storage.local.set(data as Record); + console.log(data); + } + + async clearData(keep: string[]): Promise { + const toBeRemovedProperties: string[] = []; + + for (const property of this.metaProperties) { + if (!keep.includes(property)) { + toBeRemovedProperties.push(property); + } + } + + await browser.storage.local.remove(this.metaProperties); + } +} diff --git a/projects/firefox/src/app/common/data/firefox-session-handler.ts b/projects/firefox/src/app/common/data/firefox-session-handler.ts new file mode 100644 index 0000000..8efc491 --- /dev/null +++ b/projects/firefox/src/app/common/data/firefox-session-handler.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { BrowserSessionData, BrowserSessionHandler } from '@common'; +import browser from 'webextension-polyfill'; + +export class FirefoxSessionHandler extends BrowserSessionHandler { + async loadFullData(): Promise>> { + return browser.storage.session.get(null); + } + + async saveFullData(data: BrowserSessionData): Promise { + await browser.storage.session.set(data as Record); + } + + async clearData(): Promise { + await browser.storage.session.clear(); + } +} diff --git a/projects/firefox/src/app/common/data/firefox-sync-no-handler.ts b/projects/firefox/src/app/common/data/firefox-sync-no-handler.ts new file mode 100644 index 0000000..ba5640f --- /dev/null +++ b/projects/firefox/src/app/common/data/firefox-sync-no-handler.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + BrowserSyncData, + Identity_ENCRYPTED, + Permission_ENCRYPTED, + BrowserSyncHandler, + Relay_ENCRYPTED, +} from '@common'; +import browser from 'webextension-polyfill'; + +/** + * Handles the browser sync operations when the browser sync is enabled. + * If it's not enabled, it behaves like the local extension storage (which is fine). + */ +export class FirefoxSyncNoHandler extends BrowserSyncHandler { + async loadUnmigratedData(): Promise>> { + const data = await browser.storage.local.get(null); + + // Remove any available "ignore properties". + this.ignoreProperties.forEach((property) => { + delete data[property]; + }); + return data; + } + + async saveAndSetFullData(data: BrowserSyncData): Promise { + await browser.storage.local.set(data as Record); + this.setFullData(data); + } + + async saveAndSetPartialData_Permissions(data: { + permissions: Permission_ENCRYPTED[]; + }): Promise { + await browser.storage.local.set(data); + this.setPartialData_Permissions(data); + } + + async saveAndSetPartialData_Identities(data: { + identities: Identity_ENCRYPTED[]; + }): Promise { + await browser.storage.local.set(data); + this.setPartialData_Identities(data); + } + + async saveAndSetPartialData_SelectedIdentityId(data: { + selectedIdentityId: string | null; + }): Promise { + await browser.storage.local.set(data); + this.setPartialData_SelectedIdentityId(data); + } + + async saveAndSetPartialData_Relays(data: { + relays: Relay_ENCRYPTED[]; + }): Promise { + await browser.storage.local.set(data); + this.setPartialData_Relays(data); + } + + async clearData(): Promise { + const props = Object.keys(await this.loadUnmigratedData()); + await browser.storage.local.remove(props); + } +} diff --git a/projects/firefox/src/app/common/data/firefox-sync-yes-handler.ts b/projects/firefox/src/app/common/data/firefox-sync-yes-handler.ts new file mode 100644 index 0000000..1233736 --- /dev/null +++ b/projects/firefox/src/app/common/data/firefox-sync-yes-handler.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + BrowserSyncData, + Identity_ENCRYPTED, + Permission_ENCRYPTED, + BrowserSyncHandler, + Relay_ENCRYPTED, +} from '@common'; +import browser from 'webextension-polyfill'; + +/** + * Handles the browser sync operations when the browser sync is enabled. + * If it's not enabled, it behaves like the local extension storage (which is fine). + */ +export class FirefoxSyncYesHandler extends BrowserSyncHandler { + async loadUnmigratedData(): Promise>> { + return await browser.storage.sync.get(null); + } + + async saveAndSetFullData(data: BrowserSyncData): Promise { + await browser.storage.sync.set(data as Record); + this.setFullData(data); + } + + async saveAndSetPartialData_Permissions(data: { + permissions: Permission_ENCRYPTED[]; + }): Promise { + await browser.storage.sync.set(data); + this.setPartialData_Permissions(data); + } + + async saveAndSetPartialData_Identities(data: { + identities: Identity_ENCRYPTED[]; + }): Promise { + await browser.storage.sync.set(data); + this.setPartialData_Identities(data); + } + + async saveAndSetPartialData_SelectedIdentityId(data: { + selectedIdentityId: string | null; + }): Promise { + await browser.storage.sync.set(data); + this.setPartialData_SelectedIdentityId(data); + } + + async saveAndSetPartialData_Relays(data: { + relays: Relay_ENCRYPTED[]; + }): Promise { + await browser.storage.sync.set(data); + this.setPartialData_Relays(data); + } + + async clearData(): Promise { + await browser.storage.sync.clear(); + } +} diff --git a/projects/firefox/src/app/common/data/get-new-storage-service-config.ts b/projects/firefox/src/app/common/data/get-new-storage-service-config.ts new file mode 100644 index 0000000..ddf7ff1 --- /dev/null +++ b/projects/firefox/src/app/common/data/get-new-storage-service-config.ts @@ -0,0 +1,15 @@ +import { FirefoxMetaHandler } from './firefox-meta-handler'; +import { FirefoxSessionHandler } from './firefox-session-handler'; +import { FirefoxSyncNoHandler } from './firefox-sync-no-handler'; +import { FirefoxSyncYesHandler } from './firefox-sync-yes-handler'; + +export const getNewStorageServiceConfig = () => { + const storageConfig = { + browserSessionHandler: new FirefoxSessionHandler(), + browserSyncYesHandler: new FirefoxSyncYesHandler(), + browserSyncNoHandler: new FirefoxSyncNoHandler(), + gootiMetaHandler: new FirefoxMetaHandler(), + }; + + return storageConfig; +}; diff --git a/projects/firefox/src/app/common/extensions/array.ts b/projects/firefox/src/app/common/extensions/array.ts new file mode 100644 index 0000000..96e376b --- /dev/null +++ b/projects/firefox/src/app/common/extensions/array.ts @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare global { + interface Array { + /** + * Sorts the array by the provided property and returns a new sorted array. + * Default sorting is ASC. You can apply DESC sorting by using the optional parameter "order = 'desc'" + */ + sortBy(keyFunction: (t: T) => K, order?: 'asc' | 'desc'): T[]; + + /** Check if the array is empty. */ + empty(): boolean; + + groupBy( + keyFunction: (t: T) => K, + reduceFn: (items: T[]) => R + ): Map; + } +} + +if (!Array.prototype.empty) { + Array.prototype.empty = function (): boolean { + return this.length === 0; + }; +} + +if (!Array.prototype.sortBy) { + Array.prototype.sortBy = function ( + keyFunction: (t: T) => K, + order?: string + ): T[] { + if (this.length === 0) { + return []; + } + + // determine sort order (asc or desc / asc is default) + let asc = true; + if (order === 'desc') { + asc = false; + } + + const arrayClone = Array.from(this) as any[]; + const firstSortProperty = keyFunction(arrayClone[0]); + + if (typeof firstSortProperty === 'string') { + // string in-place sort + arrayClone.sort((a, b) => { + if (asc) { + return ('' + (keyFunction(a) as unknown as string)).localeCompare( + keyFunction(b) as unknown as string + ); + } + + return ('' + (keyFunction(b) as unknown as string)).localeCompare( + keyFunction(a) as unknown as string + ); + }); + } else if (typeof firstSortProperty === 'number') { + // number in-place sort + if (asc) { + arrayClone.sort( + (a, b) => Number(keyFunction(a)) - Number(keyFunction(b)) + ); + } else { + arrayClone.sort( + (a, b) => Number(keyFunction(b)) - Number(keyFunction(a)) + ); + } + } else { + throw new Error('sortBy is not implemented for that type!'); + } + + return arrayClone; + }; +} + +if (!Array.prototype.groupBy) { + Array.prototype.groupBy = function ( + fn: (item: T) => any, + reduceFn: (items: T[]) => any + ): Map { + const result = new Map(); + + const distinctKeys = new Set(this.map((x) => fn(x))); + + for (const distinctKey of distinctKeys) { + const distinctKeyItems = this.filter((x) => fn(x) === distinctKey); + + result.set(distinctKey, reduceFn(distinctKeyItems)); + } + + return result; + }; +} + +export {}; diff --git a/projects/firefox/src/app/components/edit-identity/edit-identity.component.html b/projects/firefox/src/app/components/edit-identity/edit-identity.component.html new file mode 100644 index 0000000..ba9d8c7 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/edit-identity.component.html @@ -0,0 +1,13 @@ +
+ + + {{ identity?.nick }} +
+ +
+ +
diff --git a/projects/firefox/src/app/components/edit-identity/edit-identity.component.scss b/projects/firefox/src/app/components/edit-identity/edit-identity.component.scss new file mode 100644 index 0000000..4194986 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/edit-identity.component.scss @@ -0,0 +1,47 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + overflow-y: hidden; + 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%; + } + } + + .edit-identity-outlet { + flex-grow: 1; + overflow-y: hidden; + } +} diff --git a/projects/firefox/src/app/components/edit-identity/edit-identity.component.spec.ts b/projects/firefox/src/app/components/edit-identity/edit-identity.component.spec.ts new file mode 100644 index 0000000..cd6099d --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/edit-identity.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditIdentityComponent } from './edit-identity.component'; + +describe('EditIdentityComponent', () => { + let component: EditIdentityComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EditIdentityComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(EditIdentityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/edit-identity/edit-identity.component.ts b/projects/firefox/src/app/components/edit-identity/edit-identity.component.ts new file mode 100644 index 0000000..4c652c5 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/edit-identity.component.ts @@ -0,0 +1,43 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; +import { IconButtonComponent, Identity_DECRYPTED, StorageService } from '@common'; + +@Component({ + selector: 'app-edit-identity', + templateUrl: './edit-identity.component.html', + styleUrl: './edit-identity.component.scss', + imports: [RouterOutlet, IconButtonComponent], +}) +export class EditIdentityComponent implements OnInit { + identity?: Identity_DECRYPTED; + previousRoute?: string; + + readonly #activatedRoute = inject(ActivatedRoute); + readonly #storage = inject(StorageService); + readonly #router = inject(Router); + + constructor() { + // Must be called in the constructor and NOT in ngOnInit. + this.previousRoute = this.#router + .getCurrentNavigation() + ?.previousNavigation?.extractedUrl.toString(); + } + + ngOnInit(): void { + const selectedIdentityId = this.#activatedRoute.snapshot.params['id']; + if (!selectedIdentityId) { + return; + } + + this.identity = this.#storage + .getBrowserSessionHandler() + .browserSessionData?.identities.find((x) => x.id === selectedIdentityId); + } + + onClickCancel() { + if (!this.previousRoute) { + return; + } + this.#router.navigateByUrl(this.previousRoute); + } +} diff --git a/projects/firefox/src/app/components/edit-identity/home/home.component.html b/projects/firefox/src/app/components/edit-identity/home/home.component.html new file mode 100644 index 0000000..0444cdd --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/home/home.component.html @@ -0,0 +1,28 @@ + + + + + + +
+ + + + diff --git a/projects/firefox/src/app/components/edit-identity/home/home.component.scss b/projects/firefox/src/app/components/edit-identity/home/home.component.scss new file mode 100644 index 0000000..1921a9c --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/home/home.component.scss @@ -0,0 +1,8 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + padding-left: var(--size); + padding-right: var(--size); + padding-bottom: var(--size); +} diff --git a/projects/firefox/src/app/components/edit-identity/home/home.component.spec.ts b/projects/firefox/src/app/components/edit-identity/home/home.component.spec.ts new file mode 100644 index 0000000..1191557 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/home/home.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/edit-identity/home/home.component.ts b/projects/firefox/src/app/components/edit-identity/home/home.component.ts new file mode 100644 index 0000000..fb0cbaa --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/home/home.component.ts @@ -0,0 +1,48 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { + ConfirmComponent, + Identity_DECRYPTED, + NavItemComponent, + StorageService, +} from '@common'; + +@Component({ + selector: 'app-edit-identity-home', + imports: [NavItemComponent, ConfirmComponent], + templateUrl: './home.component.html', + styleUrl: './home.component.scss', +}) +export class HomeComponent implements OnInit { + identity?: Identity_DECRYPTED; + + readonly #activatedRoute = inject(ActivatedRoute); + readonly #storage = inject(StorageService); + readonly #router = inject(Router); + + ngOnInit(): void { + const identityId = this.#activatedRoute.parent?.snapshot.params['id']; + if (!identityId) { + return; + } + + this.#initialize(identityId); + } + + onClickNavigateTo(destination: 'keys' | 'permissions' | 'relays') { + this.#router.navigateByUrl( + `/edit-identity/${this.identity?.id}/${destination}` + ); + } + + async onConfirmDeletion() { + await this.#storage.deleteIdentity(this.identity?.id); + await this.#router.navigateByUrl('/home/identities'); + } + + #initialize(selectedIdentityId: string) { + this.identity = this.#storage + .getBrowserSessionHandler() + .browserSessionData?.identities.find((x) => x.id === selectedIdentityId); + } +} diff --git a/projects/firefox/src/app/components/edit-identity/keys/keys.component.html b/projects/firefox/src/app/components/edit-identity/keys/keys.component.html new file mode 100644 index 0000000..5c52ad0 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/keys/keys.component.html @@ -0,0 +1,141 @@ +
+ + Keys +
+ +@if(identity) { +Public Key + + +
+ NPUB +
+ + +
+
+ + +
+ HEX +
+ + +
+
+ +Private Key + + +
+ NSEC +
+ + + +
+
+ + +
+ HEX +
+ + + +
+
+} + + diff --git a/projects/firefox/src/app/components/edit-identity/keys/keys.component.scss b/projects/firefox/src/app/components/edit-identity/keys/keys.component.scss new file mode 100644 index 0000000..e0b3464 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/keys/keys.component.scss @@ -0,0 +1,19 @@ +:host { + height: 100%; + overflow-y: auto; + display: flex; + flex-direction: column; + padding-left: var(--size); + padding-right: var(--size); + + .header-pane { + display: flex; + flex-direction: row; + column-gap: var(--size-h); + align-items: center; + padding-bottom: var(--size); + background-color: var(--background); + position: sticky; + top: 0; + } +} diff --git a/projects/firefox/src/app/components/edit-identity/keys/keys.component.spec.ts b/projects/firefox/src/app/components/edit-identity/keys/keys.component.spec.ts new file mode 100644 index 0000000..b7938ed --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/keys/keys.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KeysComponent } from './keys.component'; + +describe('KeysComponent', () => { + let component: KeysComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [KeysComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(KeysComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/edit-identity/keys/keys.component.ts b/projects/firefox/src/app/components/edit-identity/keys/keys.component.ts new file mode 100644 index 0000000..b9a9699 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/keys/keys.component.ts @@ -0,0 +1,74 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { + IconButtonComponent, + NavComponent, + NostrHelper, + StorageService, + ToastComponent, +} from '@common'; + +interface CustomIdentity { + id: string; + nick: string; + privkeyNsec: string; + privkeyHex: string; + pubkeyNpub: string; + pubkeyHex: string; +} + +@Component({ + selector: 'app-keys', + imports: [IconButtonComponent, FormsModule, ToastComponent], + templateUrl: './keys.component.html', + styleUrl: './keys.component.scss', +}) +export class KeysComponent extends NavComponent implements OnInit { + identity?: CustomIdentity; + + readonly #activatedRoute = inject(ActivatedRoute); + readonly #storage = inject(StorageService); + + ngOnInit(): void { + const identityId = this.#activatedRoute.parent?.snapshot.params['id']; + if (!identityId) { + return; + } + + this.#initialize(identityId); + } + + copyToClipboard(text: string) { + navigator.clipboard.writeText(text); + } + + toggleType(element: HTMLInputElement) { + if (element.type === 'password') { + element.type = 'text'; + } else { + element.type = 'password'; + } + } + + async #initialize(identityId: string) { + const identity = this.#storage + .getBrowserSessionHandler() + .browserSessionData?.identities.find((x) => x.id === identityId); + + if (!identity) { + return; + } + + const pubkey = NostrHelper.pubkeyFromPrivkey(identity.privkey); + + this.identity = { + id: identity.id, + nick: identity.nick, + privkeyHex: identity.privkey, + privkeyNsec: NostrHelper.privkey2nsec(identity.privkey), + pubkeyHex: pubkey, + pubkeyNpub: NostrHelper.pubkey2npub(pubkey), + }; + } +} diff --git a/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.html b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.html new file mode 100644 index 0000000..a27caba --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.html @@ -0,0 +1,40 @@ +
+ + Permissions +
+ +@if(hostsPermissions.length === 0) { + + Nothing configured so far. + +} @for(hostPermissions of hostsPermissions; track hostPermissions) { +
+ + {{ hostPermissions.host }} + + + @for(permission of hostPermissions.permissions; track permission) { +
+ {{ permission.methodPolicy }} + {{ permission.method }} + @if(typeof permission.kind !== 'undefined') { + (kind {{ permission.kind }}) + } +
+ +
+ } +
+ +} diff --git a/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.scss b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.scss new file mode 100644 index 0000000..f28de31 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.scss @@ -0,0 +1,61 @@ +:host { + height: 100%; + overflow-y: auto; + display: flex; + flex-direction: column; + padding-left: var(--size); + padding-right: var(--size); + + .header-pane { + display: flex; + flex-direction: row; + column-gap: var(--size-h); + align-items: center; + padding-bottom: var(--size); + background-color: var(--background); + position: sticky; + top: 0; + } + + .permissions-card { + background: var(--background-light); + border-radius: 8px; + padding: calc(var(--size) / 2) var(--size); + display: flex; + flex-direction: column; + margin-bottom: 4px; + + .permission { + display: flex; + flex-direction: row; + align-items: center; + column-gap: var(--size); + font-size: 12px; + margin-left: -8px; + padding-left: 8px; + margin-right: -8px; + padding-right: 8px; + border-radius: 4px; + + &:hover { + background: var(--background-light-hover); + } + + .action-allow { + background: var(--bs-green); + border-radius: 4px; + padding: 0 4px; + width: 40px; + text-align: center; + } + + .action-deny { + background: var(--bs-danger-border-subtle); + border-radius: 4px; + padding: 0 4px; + width: 40px; + text-align: center; + } + } + } +} diff --git a/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.spec.ts b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.spec.ts new file mode 100644 index 0000000..f2386bd --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PermissionsComponent } from './permissions.component'; + +describe('PermissionsComponent', () => { + let component: PermissionsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PermissionsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PermissionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.ts b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.ts new file mode 100644 index 0000000..6a42b86 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/permissions/permissions.component.ts @@ -0,0 +1,75 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { IconButtonComponent, Identity_DECRYPTED, NavComponent, Permission_DECRYPTED, StorageService } from '@common'; + +interface HostPermissions { + host: string; + permissions: Permission_DECRYPTED[]; +} + +@Component({ + selector: 'app-permissions', + imports: [IconButtonComponent], + templateUrl: './permissions.component.html', + styleUrl: './permissions.component.scss', +}) +export class PermissionsComponent extends NavComponent implements OnInit { + identity?: Identity_DECRYPTED; + hostsPermissions: HostPermissions[] = []; + + readonly #activatedRoute = inject(ActivatedRoute); + readonly #storage = inject(StorageService); + + ngOnInit(): void { + const selectedIdentityId = + this.#activatedRoute.parent?.snapshot.params['id']; + if (!selectedIdentityId) { + return; + } + + this.#initialize(selectedIdentityId); + } + + async onClickRevokePermission(permission: Permission_DECRYPTED) { + await this.#storage.deletePermission(permission.id); + this.#buildHostsPermissions(this.identity?.id); + } + + #initialize(identityId: string) { + this.identity = this.#storage + .getBrowserSessionHandler() + .browserSessionData?.identities.find((x) => x.id === identityId); + + if (!this.identity) { + return; + } + + this.#buildHostsPermissions(identityId); + } + + #buildHostsPermissions(identityId: string | undefined) { + if (!identityId) { + return; + } + + this.hostsPermissions = []; + + const hostPermissions = ( + this.#storage.getBrowserSessionHandler().browserSessionData + ?.permissions ?? [] + ) + .filter((x) => x.identityId === identityId) + .sortBy((x) => x.host) + .groupBy( + (x) => x.host, + (y) => y + ); + + hostPermissions.forEach((permissions, host) => { + this.hostsPermissions.push({ + host: host, + permissions: permissions.sortBy((x) => x.method), + }); + }); + } +} diff --git a/projects/firefox/src/app/components/edit-identity/relays/relays.component.html b/projects/firefox/src/app/components/edit-identity/relays/relays.component.html new file mode 100644 index 0000000..68b9893 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/relays/relays.component.html @@ -0,0 +1,79 @@ + + +
+
+ {{ relay.url | visualRelay }} +
+ + +
+
+ + +
+
+ +
+ + Relays +
+ +
+
+ +
+ + +
+
+ + +
+ +@for(relay of relays; track relay) { + +} diff --git a/projects/firefox/src/app/components/edit-identity/relays/relays.component.scss b/projects/firefox/src/app/components/edit-identity/relays/relays.component.scss new file mode 100644 index 0000000..11e71e5 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/relays/relays.component.scss @@ -0,0 +1,30 @@ +:host { + height: 100%; + overflow-y: auto; + display: flex; + flex-direction: column; + padding-left: var(--size); + padding-right: var(--size); + + .header-pane { + display: flex; + flex-direction: row; + column-gap: var(--size-h); + align-items: center; + padding-bottom: var(--size); + background-color: var(--background); + position: sticky; + top: 0; + } + + .relay { + margin-bottom: 4px; + padding: 4px 8px 6px 8px; + border-radius: 8px; + background: var(--background-light); + + &:hover { + background: var(--background-light-hover); + } + } +} diff --git a/projects/firefox/src/app/components/edit-identity/relays/relays.component.spec.ts b/projects/firefox/src/app/components/edit-identity/relays/relays.component.spec.ts new file mode 100644 index 0000000..28af595 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/relays/relays.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RelaysComponent } from './relays.component'; + +describe('RelaysComponent', () => { + let component: RelaysComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RelaysComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RelaysComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/edit-identity/relays/relays.component.ts b/projects/firefox/src/app/components/edit-identity/relays/relays.component.ts new file mode 100644 index 0000000..c4b1468 --- /dev/null +++ b/projects/firefox/src/app/components/edit-identity/relays/relays.component.ts @@ -0,0 +1,131 @@ +import { NgTemplateOutlet } from '@angular/common'; +import { Component, inject, OnInit } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { + IconButtonComponent, + Identity_DECRYPTED, + NavComponent, + Relay_DECRYPTED, + RelayRwComponent, + StorageService, + VisualRelayPipe, +} from '@common'; + +interface NewRelay { + url: string; + read: boolean; + write: boolean; +} + +@Component({ + selector: 'app-relays', + imports: [ + IconButtonComponent, + FormsModule, + RelayRwComponent, + NgTemplateOutlet, + VisualRelayPipe, + ], + templateUrl: './relays.component.html', + styleUrl: './relays.component.scss', +}) +export class RelaysComponent extends NavComponent implements OnInit { + identity?: Identity_DECRYPTED; + relays: Relay_DECRYPTED[] = []; + addRelayInputHasFocus = false; + newRelay: NewRelay = { + url: '', + read: true, + write: true, + }; + canAdd = false; + + readonly #activatedRoute = inject(ActivatedRoute); + readonly #storage = inject(StorageService); + + ngOnInit(): void { + const selectedIdentityId = + this.#activatedRoute.parent?.snapshot.params['id']; + if (!selectedIdentityId) { + return; + } + + this.#loadData(selectedIdentityId); + } + + evaluateCanAdd() { + let canAdd = true; + + if (!this.newRelay.url) { + canAdd = false; + } else if (!this.newRelay.read && !this.newRelay.write) { + canAdd = false; + } + + this.canAdd = canAdd; + } + + async onClickRemoveRelay(relay: Relay_DECRYPTED) { + if (!this.identity) { + return; + } + + try { + await this.#storage.deleteRelay(relay.id); + this.#loadData(this.identity.id); + } catch (error) { + console.log(error); + // TODO + } + } + + async onClickAddRelay() { + if (!this.identity) { + return; + } + + try { + await this.#storage.addRelay({ + identityId: this.identity.id, + url: 'wss://' + this.newRelay.url.toLowerCase(), + read: this.newRelay.read, + write: this.newRelay.write, + }); + + this.newRelay = { + url: '', + read: true, + write: true, + }; + this.evaluateCanAdd(); + this.#loadData(this.identity.id); + } catch (error) { + console.log(error); + // TODO + } + } + + async onRelayChanged(relay: Relay_DECRYPTED) { + try { + await this.#storage.updateRelay(relay); + } catch (error) { + console.log(error); + // TODO + } + } + + #loadData(identityId: string) { + this.identity = this.#storage + .getBrowserSessionHandler() + .browserSessionData?.identities.find((x) => x.id === identityId); + + const relays: Relay_DECRYPTED[] = []; + (this.#storage.getBrowserSessionHandler().browserSessionData?.relays ?? []) + .filter((x) => x.identityId === identityId) + .forEach((x) => { + relays.push(JSON.parse(JSON.stringify(x))); + }); + this.relays = relays; + } +} diff --git a/projects/firefox/src/app/components/home/home.component.html b/projects/firefox/src/app/components/home/home.component.html new file mode 100644 index 0000000..6f98ae8 --- /dev/null +++ b/projects/firefox/src/app/components/home/home.component.html @@ -0,0 +1,36 @@ +
+ +
+ + diff --git a/projects/firefox/src/app/components/home/home.component.scss b/projects/firefox/src/app/components/home/home.component.scss new file mode 100644 index 0000000..6174ebb --- /dev/null +++ b/projects/firefox/src/app/components/home/home.component.scss @@ -0,0 +1,43 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + + .tab-content { + height: calc(100% - 60px); + } + + .tabs { + height: 60px; + min-height: 60px; + background: var(--background-light); + display: flex; + flex-direction: row; + + a { + all: unset; + } + + .tab { + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + + color: gray; + border-top: 3px solid transparent; + + cursor: pointer; + + &:hover { + background: var(--background-light-hover); + } + + &.active { + color: #ffffff; + border-top: 3px solid #0d6efd; + } + } + } +} diff --git a/projects/firefox/src/app/components/home/home.component.spec.ts b/projects/firefox/src/app/components/home/home.component.spec.ts new file mode 100644 index 0000000..1191557 --- /dev/null +++ b/projects/firefox/src/app/components/home/home.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/home/home.component.ts b/projects/firefox/src/app/components/home/home.component.ts new file mode 100644 index 0000000..dc2311e --- /dev/null +++ b/projects/firefox/src/app/components/home/home.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +import { RouterModule, RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-home', + imports: [RouterOutlet, RouterModule], + templateUrl: './home.component.html', + styleUrl: './home.component.scss', +}) +export class HomeComponent {} diff --git a/projects/firefox/src/app/components/home/identities/identities.component.html b/projects/firefox/src/app/components/home/identities/identities.component.html new file mode 100644 index 0000000..528c186 --- /dev/null +++ b/projects/firefox/src/app/components/home/identities/identities.component.html @@ -0,0 +1,78 @@ + + +
+ Identities + + +
+ +@let sessionData = storage.getBrowserSessionHandler().browserSessionData; + +@let identities = sessionData?.identities ?? []; @if(identities.length === 0) { +
+ + Create your first identity by clicking on the button in the upper right + corner. + +
+ +} @for(identity of identities; track identity) { +
+ @let isSelected = identity.id === sessionData?.selectedIdentityId; + + + {{ identity.nick }} + + +
+ + @if(isSelected) { + + } + +
+ @if(!isSelected) { + + } +
+ +
+} + + diff --git a/projects/firefox/src/app/components/home/identities/identities.component.scss b/projects/firefox/src/app/components/home/identities/identities.component.scss new file mode 100644 index 0000000..b95c809 --- /dev/null +++ b/projects/firefox/src/app/components/home/identities/identities.component.scss @@ -0,0 +1,68 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + overflow-y: auto; + padding-left: var(--size); + padding-right: var(--size); + + .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: end; + } + + .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; + } + } + + .identity { + height: 48px; + min-height: 48px; + display: flex; + flex-direction: row; + align-items: center; + padding-left: 16px; + padding-right: 8px; + background: var(--background-light); + border-radius: 8px; + margin-bottom: 8px; + cursor: pointer; + + .not-active { + //color: #525b6a; + opacity: 0.4; + } + + &:hover { + background: var(--background-light-hover); + + .buttons { + visibility: visible; + } + } + + .buttons { + visibility: hidden; + } + } +} diff --git a/projects/firefox/src/app/components/home/identities/identities.component.spec.ts b/projects/firefox/src/app/components/home/identities/identities.component.spec.ts new file mode 100644 index 0000000..f72f548 --- /dev/null +++ b/projects/firefox/src/app/components/home/identities/identities.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IdentitiesComponent } from './identities.component'; + +describe('IdentitiesComponent', () => { + let component: IdentitiesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [IdentitiesComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(IdentitiesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/home/identities/identities.component.ts b/projects/firefox/src/app/components/home/identities/identities.component.ts new file mode 100644 index 0000000..a7c42f9 --- /dev/null +++ b/projects/firefox/src/app/components/home/identities/identities.component.ts @@ -0,0 +1,33 @@ +import { Component, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { + IconButtonComponent, + Identity_DECRYPTED, + StorageService, + ToastComponent, +} from '@common'; + +@Component({ + selector: 'app-identities', + imports: [IconButtonComponent, ToastComponent], + templateUrl: './identities.component.html', + styleUrl: './identities.component.scss', +}) +export class IdentitiesComponent { + readonly storage = inject(StorageService); + + readonly #router = inject(Router); + + onClickNewIdentity() { + this.#router.navigateByUrl('/new-identity'); + } + + onClickEditIdentity(identity: Identity_DECRYPTED) { + this.#router.navigateByUrl(`/edit-identity/${identity.id}/home`); + } + + async onClickSwitchIdentity(identityId: string, event: MouseEvent) { + event.stopPropagation(); + await this.storage.switchIdentity(identityId); + } +} diff --git a/projects/firefox/src/app/components/home/identity/identity.component.html b/projects/firefox/src/app/components/home/identity/identity.component.html new file mode 100644 index 0000000..af26780 --- /dev/null +++ b/projects/firefox/src/app/components/home/identity/identity.component.html @@ -0,0 +1,56 @@ + +
+ You +
+ +
+
+
+
+ +
+ + + + {{ selectedIdentity?.nick }} + + + @if(loadedData.profile) { +
+ @if(loadedData.validating) { + + } @else { @if(loadedData.nip05isValidated) { + + } @else { + + } } + + {{ + loadedData.profile.nip05 | visualNip05 + }} +
+ } @else { +   + } + + +
+
+
+ + diff --git a/projects/firefox/src/app/components/home/identity/identity.component.scss b/projects/firefox/src/app/components/home/identity/identity.component.scss new file mode 100644 index 0000000..5bcb719 --- /dev/null +++ b/projects/firefox/src/app/components/home/identity/identity.component.scss @@ -0,0 +1,41 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + + .vertically-centered { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + .name { + font-size: 20px; + font-weight: 500; + cursor: pointer; + max-width: 343px; + overflow-x: hidden; + text-overflow: ellipsis; + } + + .picture-frame { + height: 120px; + width: 120px; + border: 2px solid white; + border-radius: 100%; + &.padding { + padding: 12px; + } + + img { + border-radius: 100%; + width: 100%; + height: 100%; + } + } + + .color-activity { + color: var(--bs-border-color); + } +} diff --git a/projects/firefox/src/app/components/home/identity/identity.component.spec.ts b/projects/firefox/src/app/components/home/identity/identity.component.spec.ts new file mode 100644 index 0000000..02bf1b2 --- /dev/null +++ b/projects/firefox/src/app/components/home/identity/identity.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IdentityComponent } from './identity.component'; + +describe('IdentityComponent', () => { + let component: IdentityComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [IdentityComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(IdentityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/home/identity/identity.component.ts b/projects/firefox/src/app/components/home/identity/identity.component.ts new file mode 100644 index 0000000..fbda4cb --- /dev/null +++ b/projects/firefox/src/app/components/home/identity/identity.component.ts @@ -0,0 +1,117 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { + Identity_DECRYPTED, + NostrHelper, + PubkeyComponent, + StorageService, + ToastComponent, + VisualNip05Pipe, +} from '@common'; +import NDK, { NDKUserProfile } from '@nostr-dev-kit/ndk'; + +interface LoadedData { + profile: NDKUserProfile | undefined; + nip05: string | undefined; + nip05isValidated: boolean | undefined; + validating: boolean; +} + +@Component({ + selector: 'app-identity', + imports: [PubkeyComponent, VisualNip05Pipe, ToastComponent], + templateUrl: './identity.component.html', + styleUrl: './identity.component.scss', +}) +export class IdentityComponent implements OnInit { + selectedIdentity: Identity_DECRYPTED | undefined; + selectedIdentityNpub: string | undefined; + loadedData: LoadedData = { + profile: undefined, + nip05: undefined, + nip05isValidated: undefined, + validating: false, + }; + + readonly #storage = inject(StorageService); + readonly #router = inject(Router); + + ngOnInit(): void { + this.#loadData(); + } + + copyToClipboard(pubkey: string | undefined) { + if (!pubkey) { + return; + } + navigator.clipboard.writeText(pubkey); + } + + onClickShowDetails() { + if (!this.selectedIdentity) { + return; + } + + this.#router.navigateByUrl( + `/edit-identity/${this.selectedIdentity.id}/home` + ); + } + + async #loadData() { + try { + const selectedIdentityId = + this.#storage.getBrowserSessionHandler().browserSessionData + ?.selectedIdentityId ?? null; + + const identity = this.#storage + .getBrowserSessionHandler() + .browserSessionData?.identities.find( + (x) => x.id === selectedIdentityId + ); + + if (!identity) { + return; + } + + this.selectedIdentity = identity; + const pubkey = NostrHelper.pubkeyFromPrivkey(identity.privkey); + this.selectedIdentityNpub = NostrHelper.pubkey2npub(pubkey); + + // Determine the user's relays to check for his profile. + const relays = + this.#storage + .getBrowserSessionHandler() + .browserSessionData?.relays.filter( + (x) => x.identityId === identity.id + ) ?? []; + if (relays.length === 0) { + return; + } + + const relevantRelays = relays.filter((x) => x.write).map((x) => x.url); + + // Fetch the user's profile. + const ndk = new NDK({ + explicitRelayUrls: relevantRelays, + }); + + await ndk.connect(); + + const user = ndk.getUser({ + pubkey: NostrHelper.pubkeyFromPrivkey(identity.privkey), + //relayUrls: relevantRelays, + }); + this.loadedData.profile = (await user.fetchProfile()) ?? undefined; + if (this.loadedData.profile?.nip05) { + this.loadedData.validating = true; + this.loadedData.nip05isValidated = + (await user.validateNip05(this.loadedData.profile.nip05)) ?? + undefined; + this.loadedData.validating = false; + } + } catch (error) { + console.error(error); + // TODO + } + } +} diff --git a/projects/firefox/src/app/components/home/info/info.component.html b/projects/firefox/src/app/components/home/info/info.component.html new file mode 100644 index 0000000..d88d1f2 --- /dev/null +++ b/projects/firefox/src/app/components/home/info/info.component.html @@ -0,0 +1,34 @@ +
+ Gooti +
+ +Version {{ version }} + +  + + Website +www.getgooti.com + +  + + Source code + + github.com/sam-hayes-org/gooti-extension + + +
+ +
+ + Made with by + Sam Hayes + + + +
+ + diff --git a/projects/firefox/src/app/components/home/info/info.component.scss b/projects/firefox/src/app/components/home/info/info.component.scss new file mode 100644 index 0000000..15b26b5 --- /dev/null +++ b/projects/firefox/src/app/components/home/info/info.component.scss @@ -0,0 +1,9 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; + padding-left: var(--size); + padding-right: var(--size); +} diff --git a/projects/firefox/src/app/components/home/info/info.component.spec.ts b/projects/firefox/src/app/components/home/info/info.component.spec.ts new file mode 100644 index 0000000..6f7e9e0 --- /dev/null +++ b/projects/firefox/src/app/components/home/info/info.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InfoComponent } from './info.component'; + +describe('InfoComponent', () => { + let component: InfoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [InfoComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(InfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/home/info/info.component.ts b/projects/firefox/src/app/components/home/info/info.component.ts new file mode 100644 index 0000000..bd38e84 --- /dev/null +++ b/projects/firefox/src/app/components/home/info/info.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { PubkeyComponent, ToastComponent } from '@common'; +import packageJson from '../../../../../../../package.json'; + +@Component({ + selector: 'app-info', + imports: [PubkeyComponent, ToastComponent], + templateUrl: './info.component.html', + styleUrl: './info.component.scss', +}) +export class InfoComponent { + version = packageJson.custom.firefox.version; +} diff --git a/projects/firefox/src/app/components/home/settings/settings.component.html b/projects/firefox/src/app/components/home/settings/settings.component.html new file mode 100644 index 0000000..ec1dbf7 --- /dev/null +++ b/projects/firefox/src/app/components/home/settings/settings.component.html @@ -0,0 +1,29 @@ +
+ Settings +
+ +SYNC: {{ syncFlow }} + + + + + +
+ + + + diff --git a/projects/firefox/src/app/components/home/settings/settings.component.scss b/projects/firefox/src/app/components/home/settings/settings.component.scss new file mode 100644 index 0000000..41d4868 --- /dev/null +++ b/projects/firefox/src/app/components/home/settings/settings.component.scss @@ -0,0 +1,14 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + row-gap: var(--size); + overflow-y: auto; + padding-left: var(--size); + padding-right: var(--size); + + .file-input { + position: absolute; + visibility: hidden; + } +} diff --git a/projects/firefox/src/app/components/home/settings/settings.component.spec.ts b/projects/firefox/src/app/components/home/settings/settings.component.spec.ts new file mode 100644 index 0000000..82c748a --- /dev/null +++ b/projects/firefox/src/app/components/home/settings/settings.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SettingsComponent } from './settings.component'; + +describe('SettingsComponent', () => { + let component: SettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SettingsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/home/settings/settings.component.ts b/projects/firefox/src/app/components/home/settings/settings.component.ts new file mode 100644 index 0000000..0351cc8 --- /dev/null +++ b/projects/firefox/src/app/components/home/settings/settings.component.ts @@ -0,0 +1,73 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { + BrowserSyncFlow, + ConfirmComponent, + DateHelper, + NavComponent, + StartupService, + StorageService, +} from '@common'; +import { getNewStorageServiceConfig } from '../../../common/data/get-new-storage-service-config'; + +@Component({ + selector: 'app-settings', + imports: [ConfirmComponent], + templateUrl: './settings.component.html', + styleUrl: './settings.component.scss', +}) +export class SettingsComponent extends NavComponent implements OnInit { + syncFlow: string | undefined; + + readonly #storage = inject(StorageService); + readonly #startup = inject(StartupService); + + ngOnInit(): void { + const vault = JSON.stringify( + this.#storage.getBrowserSyncHandler().browserSyncData + ); + console.log(vault.length / 1024 + ' KB'); + + switch (this.#storage.getGootiMetaHandler().gootiMetaData?.syncFlow) { + case BrowserSyncFlow.NO_SYNC: + this.syncFlow = 'Off'; + break; + + case BrowserSyncFlow.BROWSER_SYNC: + this.syncFlow = 'Mozilla Firefox'; + break; + + default: + break; + } + } + + async onResetExtension() { + try { + await this.#storage.resetExtension(); + this.#startup.startOver(getNewStorageServiceConfig()); + } catch (error) { + console.log(error); + // TODO + } + } + + async onClickExportVault() { + const jsonVault = this.#storage.exportVault(); + + const dateTimeString = DateHelper.dateToISOLikeButLocal(new Date()); + const fileName = `Gooti Chrome - Vault Export - ${dateTimeString}.json`; + + this.#downloadJson(jsonVault, fileName); + } + + #downloadJson(jsonString: string, fileName: string) { + const dataStr = + 'data:text/json;charset=utf-8,' + encodeURIComponent(jsonString); + const downloadAnchorNode = document.createElement('a'); + downloadAnchorNode.setAttribute('href', dataStr); + downloadAnchorNode.setAttribute('download', fileName); + document.body.appendChild(downloadAnchorNode); + downloadAnchorNode.click(); + downloadAnchorNode.remove(); + } +} diff --git a/projects/firefox/src/app/components/new-identity/new-identity.component.html b/projects/firefox/src/app/components/new-identity/new-identity.component.html new file mode 100644 index 0000000..0c2b722 --- /dev/null +++ b/projects/firefox/src/app/components/new-identity/new-identity.component.html @@ -0,0 +1,85 @@ +
+ New Identity +
+ +
+ + +
+ + +
+ + +
+ + + + + + +@if(alertMessage) { +
+ +
+} diff --git a/projects/firefox/src/app/components/new-identity/new-identity.component.scss b/projects/firefox/src/app/components/new-identity/new-identity.component.scss new file mode 100644 index 0000000..711bdd9 --- /dev/null +++ b/projects/firefox/src/app/components/new-identity/new-identity.component.scss @@ -0,0 +1,13 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + + .content { + padding-left: var(--size); + padding-right: var(--size); + flex-grow: 1; + display: flex; + flex-direction: column; + } +} diff --git a/projects/firefox/src/app/components/new-identity/new-identity.component.spec.ts b/projects/firefox/src/app/components/new-identity/new-identity.component.spec.ts new file mode 100644 index 0000000..6a3ba20 --- /dev/null +++ b/projects/firefox/src/app/components/new-identity/new-identity.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewIdentityComponent } from './new-identity.component'; + +describe('NewIdentityComponent', () => { + let component: NewIdentityComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NewIdentityComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NewIdentityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/new-identity/new-identity.component.ts b/projects/firefox/src/app/components/new-identity/new-identity.component.ts new file mode 100644 index 0000000..a2efa7c --- /dev/null +++ b/projects/firefox/src/app/components/new-identity/new-identity.component.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { AfterViewInit, Component, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { NavComponent, NostrHelper, StorageService } from '@common'; +import { generateSecretKey } from 'nostr-tools'; +import { bytesToHex } from '@noble/hashes/utils'; + +@Component({ + selector: 'app-new-identity', + templateUrl: './new-identity.component.html', + styleUrl: './new-identity.component.scss', + imports: [FormsModule], +}) +export class NewIdentityComponent + extends NavComponent + implements AfterViewInit +{ + readonly identity = { + nick: '', + privkeyInput: '', + }; + canSave = false; + alertMessage: any | undefined; + + readonly #storage = inject(StorageService); + readonly #router = inject(Router); + + ngAfterViewInit(): void { + document.getElementById('nickElement')?.focus(); + } + + toggleType(element: HTMLInputElement) { + if (element.type === 'password') { + element.type = 'text'; + } else { + element.type = 'password'; + } + } + + onClickGeneratePrivkey() { + const sk = generateSecretKey(); + const privkey = bytesToHex(sk); + + this.identity.privkeyInput = NostrHelper.privkey2nsec(privkey); + this.validateCanSave(); + } + + validateCanSave() { + if (!this.identity.nick || !this.identity.privkeyInput) { + this.canSave = false; + return; + } + + try { + NostrHelper.getNostrPrivkeyObject( + this.identity.privkeyInput.toLocaleLowerCase() + ); + this.canSave = true; + } catch (error) { + console.log(error); + this.canSave = false; + } + } + + async onClickSave() { + if (!this.canSave) { + return; + } + + if (!this.identity.nick || !this.identity.privkeyInput) { + return; + } + + try { + await this.#storage.addIdentity({ + nick: this.identity.nick, + privkeyString: this.identity.privkeyInput, + }); + this.#router.navigateByUrl('/home/identities'); + } catch (error: any) { + this.alertMessage = error?.message; + setTimeout(() => { + this.alertMessage = undefined; + }, 4500); + } + } +} diff --git a/projects/firefox/src/app/components/vault-create/home/home.component.html b/projects/firefox/src/app/components/vault-create/home/home.component.html new file mode 100644 index 0000000..c0ceae1 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/home/home.component.html @@ -0,0 +1,34 @@ +
+ Gooti +
+ +
+
+
+
+ +
+ + + + or + + +
+
+
\ No newline at end of file diff --git a/projects/firefox/src/app/components/vault-create/home/home.component.scss b/projects/firefox/src/app/components/vault-create/home/home.component.scss new file mode 100644 index 0000000..1d70be5 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/home/home.component.scss @@ -0,0 +1,17 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + + .vertically-centered { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + .file-input { + position: absolute; + visibility: hidden; + } +} diff --git a/projects/firefox/src/app/components/vault-create/home/home.component.spec.ts b/projects/firefox/src/app/components/vault-create/home/home.component.spec.ts new file mode 100644 index 0000000..1191557 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/home/home.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/vault-create/home/home.component.ts b/projects/firefox/src/app/components/vault-create/home/home.component.ts new file mode 100644 index 0000000..efe03ce --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/home/home.component.ts @@ -0,0 +1,12 @@ +import { Component, inject } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-vault-create-home', + imports: [], + templateUrl: './home.component.html', + styleUrl: './home.component.scss', +}) +export class HomeComponent { + readonly router = inject(Router); +} diff --git a/projects/firefox/src/app/components/vault-create/new/new.component.html b/projects/firefox/src/app/components/vault-create/new/new.component.html new file mode 100644 index 0000000..c46c2b2 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/new/new.component.html @@ -0,0 +1,48 @@ +
+ Gooti +
+ +
+
+
+ +
+ + Please define a password for your vault. + + + Sensitive data is encypted before it is stored. + + +
+ + +
+ + +
+
diff --git a/projects/firefox/src/app/components/vault-create/new/new.component.scss b/projects/firefox/src/app/components/vault-create/new/new.component.scss new file mode 100644 index 0000000..546fdde --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/new/new.component.scss @@ -0,0 +1,48 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + + color-scheme: dark; + + .custom-header { + padding-top: var(--size); + padding-bottom: var(--size); + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto; + align-items: center; + + .back { + grid-column-start: 1; + grid-column-end: 2; + grid-row-start: 1; + grid-row-end: 2; + justify-self: start; + padding-left: var(--size); + } + + .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; + } + } + + .content { + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; + padding: 0 var(--size) var(--size) var(--size); + } + + .logo-frame { + border: 2px solid #0d6efd; + border-radius: 100%; + } +} diff --git a/projects/firefox/src/app/components/vault-create/new/new.component.spec.ts b/projects/firefox/src/app/components/vault-create/new/new.component.spec.ts new file mode 100644 index 0000000..1af4d78 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/new/new.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewComponent } from './new.component'; + +describe('NewComponent', () => { + let component: NewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NewComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/vault-create/new/new.component.ts b/projects/firefox/src/app/components/vault-create/new/new.component.ts new file mode 100644 index 0000000..8256b73 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/new/new.component.ts @@ -0,0 +1,34 @@ +import { Component, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { NavComponent, StorageService } from '@common'; + +@Component({ + selector: 'app-new', + imports: [FormsModule], + templateUrl: './new.component.html', + styleUrl: './new.component.scss', +}) +export class NewComponent extends NavComponent { + password = ''; + + readonly #router = inject(Router); + readonly #storage = inject(StorageService); + + toggleType(element: HTMLInputElement) { + if (element.type === 'password') { + element.type = 'text'; + } else { + element.type = 'password'; + } + } + + async createVault() { + if (!this.password) { + return; + } + + await this.#storage.createNewVault(this.password); + this.#router.navigateByUrl('/home/identities'); + } +} diff --git a/projects/firefox/src/app/components/vault-create/vault-create.component.html b/projects/firefox/src/app/components/vault-create/vault-create.component.html new file mode 100644 index 0000000..90c6b64 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/vault-create.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/firefox/src/app/components/vault-create/vault-create.component.scss b/projects/firefox/src/app/components/vault-create/vault-create.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/projects/firefox/src/app/components/vault-create/vault-create.component.spec.ts b/projects/firefox/src/app/components/vault-create/vault-create.component.spec.ts new file mode 100644 index 0000000..f1a73d3 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/vault-create.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VaultCreateComponent } from './vault-create.component'; + +describe('VaultCreateComponent', () => { + let component: VaultCreateComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VaultCreateComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(VaultCreateComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/vault-create/vault-create.component.ts b/projects/firefox/src/app/components/vault-create/vault-create.component.ts new file mode 100644 index 0000000..dc83061 --- /dev/null +++ b/projects/firefox/src/app/components/vault-create/vault-create.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-vault-create', + imports: [RouterOutlet], + templateUrl: './vault-create.component.html', + styleUrl: './vault-create.component.scss' +}) +export class VaultCreateComponent { + +} diff --git a/projects/firefox/src/app/components/vault-import/vault-import.component.html b/projects/firefox/src/app/components/vault-import/vault-import.component.html new file mode 100644 index 0000000..d56f06d --- /dev/null +++ b/projects/firefox/src/app/components/vault-import/vault-import.component.html @@ -0,0 +1,39 @@ +
+ + + Import Vault +
+ +
+ + You can select any snapshot that you have previously uploaded on the + options page. + + + + + + Please note that your data will be imported regarding your current SYNC + setting: {{ syncText }} + +
+ +
+ + diff --git a/projects/firefox/src/app/components/vault-import/vault-import.component.scss b/projects/firefox/src/app/components/vault-import/vault-import.component.scss new file mode 100644 index 0000000..d176ea6 --- /dev/null +++ b/projects/firefox/src/app/components/vault-import/vault-import.component.scss @@ -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); + } +} diff --git a/projects/firefox/src/app/components/vault-import/vault-import.component.spec.ts b/projects/firefox/src/app/components/vault-import/vault-import.component.spec.ts new file mode 100644 index 0000000..f5c2197 --- /dev/null +++ b/projects/firefox/src/app/components/vault-import/vault-import.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VaultImportComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(VaultImportComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/vault-import/vault-import.component.ts b/projects/firefox/src/app/components/vault-import/vault-import.component.ts new file mode 100644 index 0000000..f35bf4d --- /dev/null +++ b/projects/firefox/src/app/components/vault-import/vault-import.component.ts @@ -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 = 'MOZILLA FIREFOX'; + break; + + default: + case BrowserSyncFlow.NO_SYNC: + this.syncText = 'OFF'; + break; + } + } +} diff --git a/projects/firefox/src/app/components/vault-login/vault-login.component.html b/projects/firefox/src/app/components/vault-login/vault-login.component.html new file mode 100644 index 0000000..b01b7b8 --- /dev/null +++ b/projects/firefox/src/app/components/vault-login/vault-login.component.html @@ -0,0 +1,72 @@ +
+ Gooti +
+ + + + + + +@if(showInvalidPasswordAlert) { +
+ +
+} + + diff --git a/projects/firefox/src/app/components/vault-login/vault-login.component.scss b/projects/firefox/src/app/components/vault-login/vault-login.component.scss new file mode 100644 index 0000000..b752f0e --- /dev/null +++ b/projects/firefox/src/app/components/vault-login/vault-login.component.scss @@ -0,0 +1,19 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + justify-items: center; + + .logo-frame { + border: 2px solid #0d6efd; + border-radius: 100%; + } + + .content-login-vault { + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; + padding: 0 var(--size) var(--size) var(--size); + } +} diff --git a/projects/firefox/src/app/components/vault-login/vault-login.component.spec.ts b/projects/firefox/src/app/components/vault-login/vault-login.component.spec.ts new file mode 100644 index 0000000..c9e33f9 --- /dev/null +++ b/projects/firefox/src/app/components/vault-login/vault-login.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VaultLoginComponent } from './vault-login.component'; + +describe('VaultLoginComponent', () => { + let component: VaultLoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VaultLoginComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(VaultLoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/vault-login/vault-login.component.ts b/projects/firefox/src/app/components/vault-login/vault-login.component.ts new file mode 100644 index 0000000..38ed98a --- /dev/null +++ b/projects/firefox/src/app/components/vault-login/vault-login.component.ts @@ -0,0 +1,55 @@ +import { Component, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { ConfirmComponent, StartupService, StorageService } from '@common'; +import { getNewStorageServiceConfig } from '../../common/data/get-new-storage-service-config'; + +@Component({ + selector: 'app-vault-login', + templateUrl: './vault-login.component.html', + styleUrl: './vault-login.component.scss', + imports: [FormsModule, ConfirmComponent], +}) +export class VaultLoginComponent { + loginPassword = ''; + showInvalidPasswordAlert = false; + + readonly #storage = inject(StorageService); + readonly #router = inject(Router); + readonly #startup = inject(StartupService); + + toggleType(element: HTMLInputElement) { + if (element.type === 'password') { + element.type = 'text'; + } else { + element.type = 'password'; + } + } + + async loginVault() { + if (!this.loginPassword) { + return; + } + + try { + await this.#storage.unlockVault(this.loginPassword); + this.#router.navigateByUrl('/home/identities'); + } catch (error) { + this.showInvalidPasswordAlert = true; + console.log(error); + window.setTimeout(() => { + this.showInvalidPasswordAlert = false; + }, 2000); + } + } + + async onClickResetExtension() { + try { + await this.#storage.resetExtension(); + this.#startup.startOver(getNewStorageServiceConfig()); + } catch (error) { + console.log(error); + // TODO + } + } +} diff --git a/projects/firefox/src/app/components/welcome/welcome.component.html b/projects/firefox/src/app/components/welcome/welcome.component.html new file mode 100644 index 0000000..c44cd67 --- /dev/null +++ b/projects/firefox/src/app/components/welcome/welcome.component.html @@ -0,0 +1,44 @@ +
+ Gooti Setup - Sync Preference +
+ + + Gooti always encrypts sensitive data like private keys and site permissions + independent of the chosen sync mode. + + +Sync : Mozilla Firefox + + + Your encrypted data is synced between browser instances. You + need to be signed in with your account. + + + + +Offline + + + Your encrypted data is never uploaded to any servers. It remains in your local + browser instance. + + + + +
+ + + Your preference can later be changed at any time. + diff --git a/projects/firefox/src/app/components/welcome/welcome.component.scss b/projects/firefox/src/app/components/welcome/welcome.component.scss new file mode 100644 index 0000000..588d90f --- /dev/null +++ b/projects/firefox/src/app/components/welcome/welcome.component.scss @@ -0,0 +1,8 @@ +:host { + height: 100%; + display: flex; + flex-direction: column; + + padding-left: var(--size); + padding-right: var(--size); +} diff --git a/projects/firefox/src/app/components/welcome/welcome.component.spec.ts b/projects/firefox/src/app/components/welcome/welcome.component.spec.ts new file mode 100644 index 0000000..92182b5 --- /dev/null +++ b/projects/firefox/src/app/components/welcome/welcome.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WelcomeComponent } from './welcome.component'; + +describe('WelcomeComponent', () => { + let component: WelcomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WelcomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(WelcomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/firefox/src/app/components/welcome/welcome.component.ts b/projects/firefox/src/app/components/welcome/welcome.component.ts new file mode 100644 index 0000000..3cc4797 --- /dev/null +++ b/projects/firefox/src/app/components/welcome/welcome.component.ts @@ -0,0 +1,41 @@ +import { Component, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { BrowserSyncFlow, StorageService } from '@common'; + +@Component({ + selector: 'app-welcome', + imports: [], + templateUrl: './welcome.component.html', + styleUrl: './welcome.component.scss', +}) +export class WelcomeComponent { + readonly router = inject(Router); + readonly #storage = inject(StorageService); + + async onClickSync(enabled: boolean) { + const flow: BrowserSyncFlow = enabled + ? BrowserSyncFlow.BROWSER_SYNC + : BrowserSyncFlow.NO_SYNC; + + await this.#storage.enableBrowserSyncFlow(flow); + + // In case the user has selected the BROWSER_SYNC flow, + // we have to check if there is sync data available (e.g. from + // another browser instance). + // If so, navigate to /vault-login, otherwise to /vault-create/home. + if (flow === BrowserSyncFlow.BROWSER_SYNC) { + const browserSyncData = + await this.#storage.loadAndMigrateBrowserSyncData(); + + if ( + typeof browserSyncData !== 'undefined' && + Object.keys(browserSyncData).length > 0 + ) { + await this.router.navigateByUrl('/vault-login'); + return; + } + } + + await this.router.navigateByUrl('/vault-create/home'); + } +} diff --git a/projects/firefox/src/background.ts b/projects/firefox/src/background.ts new file mode 100644 index 0000000..e69de29 diff --git a/projects/firefox/src/gooti-content-script.ts b/projects/firefox/src/gooti-content-script.ts new file mode 100644 index 0000000..e69de29 diff --git a/projects/firefox/src/gooti-extension.ts b/projects/firefox/src/gooti-extension.ts new file mode 100644 index 0000000..e69de29 diff --git a/projects/firefox/src/index.html b/projects/firefox/src/index.html index 36374cd..090680b 100644 --- a/projects/firefox/src/index.html +++ b/projects/firefox/src/index.html @@ -1,11 +1,10 @@ - + - Firefox + Gooti - diff --git a/projects/firefox/src/main.ts b/projects/firefox/src/main.ts index 35b00f3..2ebdb6e 100644 --- a/projects/firefox/src/main.ts +++ b/projects/firefox/src/main.ts @@ -1,6 +1,8 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { AppComponent } from './app/app.component'; +import './app/common/extensions/array'; -bootstrapApplication(AppComponent, appConfig) - .catch((err) => console.error(err)); +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err) +); diff --git a/projects/firefox/src/options.ts b/projects/firefox/src/options.ts new file mode 100644 index 0000000..02bf5df --- /dev/null +++ b/projects/firefox/src/options.ts @@ -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 { + 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); +}); diff --git a/projects/firefox/src/prompt.ts b/projects/firefox/src/prompt.ts new file mode 100644 index 0000000..e69de29 diff --git a/projects/firefox/src/styles.scss b/projects/firefox/src/styles.scss index 90d4ee0..ddd4ebe 100644 --- a/projects/firefox/src/styles.scss +++ b/projects/firefox/src/styles.scss @@ -1 +1,20 @@ -/* You can add global styles to this file, and also import other style files */ +@use "sass:meta"; + +@include meta.load-css("../../../node_modules/bootstrap/scss/bootstrap"); +@include meta.load-css( + "../../../node_modules/bootstrap-icons/font/bootstrap-icons.min.css" +); + +// Load the common styles +@include meta.load-css("../../common/src/lib/styles/styles.scss"); + +body { + height: 600px; + width: 375px; + + color: #ffffff; + font-size: 16px; + background: var(--background); + + margin: 0; +} diff --git a/projects/firefox/tsconfig.app.json b/projects/firefox/tsconfig.app.json index e40712b..5e747f7 100644 --- a/projects/firefox/tsconfig.app.json +++ b/projects/firefox/tsconfig.app.json @@ -7,9 +7,12 @@ "types": [] }, "files": [ - "src/main.ts" + "src/main.ts", + "src/background.ts", + "src/gooti-extension.ts", + "src/gooti-content-script.ts", + "src/prompt.ts", + "src/options.ts" ], - "include": [ - "src/**/*.d.ts" - ] + "include": ["src/**/*.d.ts"] }