Compare commits

..

28 Commits

Author SHA1 Message Date
a315e58284 Merge pull request #26 from cupcakearmy/1.4.1
fallback route & dep updates
2022-03-05 12:47:55 +01:00
d576b71bc5 fallback route & dep updates 2022-03-05 12:47:11 +01:00
e02f7f59c6 changelog 2022-03-02 16:59:25 +01:00
e8c6467faa dont' allow empty notes 2022-03-02 16:55:10 +01:00
43f67c795d don't remove already selected files 2022-03-02 16:55:04 +01:00
83f0902291 lokalise 2022-03-02 16:32:36 +01:00
11a6621bd7 script to download locales 2022-03-02 16:15:25 +01:00
36fa451249 enforce limits 2022-03-01 16:16:02 +01:00
d112eba8fe add config for max views, expiration and advanced 2022-03-01 15:24:17 +01:00
ef39f9ec0b cleanup 2022-03-01 02:04:43 +01:00
8517c20e6c remove unused code 2022-03-01 02:01:57 +01:00
728ad56b33 enforce strict typescript 2022-03-01 02:00:01 +01:00
f185ccee03 add svelte check 2022-03-01 01:52:20 +01:00
284bbcbae2 better typings 2022-03-01 01:52:09 +01:00
7eba454f1b visual improvements 2022-03-01 01:52:04 +01:00
dcd9efaeba use native icons 2022-03-01 01:51:43 +01:00
f13bcbaf3f update robots 2022-03-01 01:51:21 +01:00
8e7e0414a6 add copy to clipboard note 2022-03-01 01:16:31 +01:00
229c8d8368 update actix to version 4 2022-03-01 00:53:47 +01:00
1adf87b884 set memcached size 2022-03-01 00:53:34 +01:00
a061b540b1 make top margin smaller 2022-01-21 17:46:13 +01:00
824603ff4a add locales 2022-01-16 14:39:45 +01:00
539d99d35f version bump 2022-01-16 14:04:49 +01:00
716034660c translate the app 2022-01-16 14:02:53 +01:00
bab65bcdad bug due to dep update 2022-01-03 18:16:54 +01:00
a0732a4593 move folder 2022-01-02 23:46:08 +01:00
835f7df0f6 update dep 2022-01-02 18:43:31 +01:00
2def365cae quality of life improvemnts 2021-12-30 22:36:28 +01:00
87 changed files with 2909 additions and 2035 deletions

View File

@@ -1,2 +1,2 @@
target
node_modules
/**/target
/**/node_modules

BIN
.github/lokalise.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
# Backend
/target
target
# Client
.DS_Store

View File

@@ -1,6 +1,6 @@
{
"cSpell.words": [
"ciphertext",
"cryptgeon"
]
"cSpell.words": ["ciphertext", "cryptgeon"],
"i18n-ally.localesPaths": ["frontend/locales"],
"i18n-ally.enabledFrameworks": ["svelte"],
"i18n-ally.keystyle": "nested"
}

View File

@@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.1] - 2022-03-05
### Fixed
- Router in prod build.
## [1.4.0] - 2022-03-02
### Added
- Support for multiple languages.
- Select multiple files without removing already selected ones.
- Tooltip for copy action.
- Configure maximum views, expiration and advanced options for the server.
### Changed
- Use native SVGs instead of images.
- Update robots.txt file to allow only root.
- Stronger frontend types.
## [1.3.3] - 2022-01-03
### Fixed
- Bug fix due to dependency update.
## [1.3.2] - 2022-01-02
### Changed
- Dependencies updates.
- Folder structure.
## [1.3.1] - 2021-12-30
### Added
- Short explanation in the home page.
### Changed
- Explanation in about & readme.
- Shorten server ids from 512 to 256bit.
## [1.3.0] - 2021-12-22
### Added

View File

@@ -1,26 +1,28 @@
# Frontend
FROM node:16-alpine as CLIENT
WORKDIR /tmp
COPY ./client ./
COPY ./frontend ./
RUN npm install -g pnpm
RUN pnpm install
RUN pnpm run build
FROM rust:1.51-alpine as RUST
# Backend
FROM rust:1.59-alpine as RUST
WORKDIR /tmp
RUN apk add libc-dev openssl-dev alpine-sdk
COPY ./Cargo* ./
COPY ./src ./src
COPY ./backend ./
RUN cargo build --release
FROM scratch
# Server
FROM alpine
WORKDIR /app
COPY --from=RUST /tmp/target/release/cryptgeon .
COPY --from=CLIENT /tmp/build ./client/build
COPY --from=CLIENT /tmp/build ./frontend/build
ENV MEMCACHE=memcached:11211

View File

@@ -10,13 +10,18 @@
</a>
<br/>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
<a href=""><img src="./.github/lokalise.png" height="50">
<br/>
## About?
_cryptgeon_ is a secure, open source sharing note or file service inspired by [_PrivNote_](https://privnote.com)
> 🌍 If you want to translate the project feel free to reach out to me.
>
> Thanks to [Lokalise](https://lokalise.com/) for providing free access to their platform.
## Demo
Check out the demo and see for yourself https://cryptgeon.nicco.io.
@@ -30,7 +35,12 @@ Check out the demo and see for yourself https://cryptgeon.nicco.io.
## How does it work?
each note has a 512bit generated <i>id</i> that is used to retrieve the note. data is stored in memory and never persisted to disk.
each note has a generated <code>id (256bit)</code> and <code>key 256(bit)</code>. The
<code>id</code>
is used to save & retrieve the note. the note is then encrypted with aes in gcm mode on the
client side with the <code>key</code> and then sent to the server. data is stored in memory and
never persisted to disk. the server never sees the encryption key and cannot decrypt the contents
of the notes even if it tried to.
## Screenshot

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
[package]
name = "cryptgeon"
version = "1.3.0"
version = "1.4.1"
authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2018"
edition = "2021"
[[bin]]
name = "cryptgeon"
@@ -11,9 +11,9 @@ path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "3"
actix-files = "0.5"
serde = "1"
actix-web = "4"
actix-files = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
lazy_static = "1"
ring = "0.16"

12
backend/src/api.rs Normal file
View File

@@ -0,0 +1,12 @@
use actix_web::web;
use crate::note;
use crate::status;
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api")
.service(note::init())
.service(status::init()),
);
}

14
backend/src/client.rs Normal file
View File

@@ -0,0 +1,14 @@
use actix_files::{Files, NamedFile};
use actix_web::{web, Result};
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
Files::new("/", "./frontend/build")
.index_file("index.html")
.use_etag(true),
);
}
pub async fn index() -> Result<NamedFile> {
Ok(NamedFile::open("./frontend/build/index.html")?)
}

23
backend/src/config.rs Normal file
View File

@@ -0,0 +1,23 @@
use byte_unit::Byte;
lazy_static! {
pub static ref VERSION: String = option_env!("CARGO_PKG_VERSION")
.unwrap_or("Unknown")
.to_string();
pub static ref LIMIT: u32 =
Byte::from_str(std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()))
.unwrap()
.get_bytes() as u32;
pub static ref MAX_VIEWS: u32 = std::env::var("MAX_VIEWS")
.unwrap_or("100".to_string())
.parse()
.unwrap();
pub static ref MAX_EXPIRATION: u32 = std::env::var("MAX_EXPIRATION")
.unwrap_or("360".to_string()) // 6 hours in minutes
.parse()
.unwrap();
pub static ref ALLOW_ADVANCED: bool = std::env::var("ALLOW_ADVANCED")
.unwrap_or("true".to_string())
.parse()
.unwrap();
}

View File

@@ -4,9 +4,12 @@ use dotenv::dotenv;
#[macro_use]
extern crate lazy_static;
mod api;
mod client;
mod config;
mod note;
mod size;
mod status;
mod store;
#[actix_web::main]
@@ -17,9 +20,9 @@ async fn main() -> std::io::Result<()> {
.wrap(middleware::Compress::default())
.wrap(middleware::DefaultHeaders::default())
.configure(size::init)
.configure(note::init)
.configure(api::init)
.configure(client::init)
.default_service(web::resource("").route(web::get().to(client::fallback_fn)))
.default_service(web::to(client::index))
})
.bind("0.0.0.0:5000")?
.run()

View File

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
pub struct Note {
pub meta: String,
pub contents: String,
pub views: Option<u8>,
pub views: Option<u32>,
pub expiration: Option<u32>,
}
@@ -20,7 +20,7 @@ pub struct NotePublic {
}
pub fn generate_id() -> String {
let mut id: [u8; 64] = [0; 64];
let mut id: [u8; 32] = [0; 32];
let sr = ring::rand::SystemRandom::new();
let _ = sr.fill(&mut id);
return bs62::encode_data(&id);

View File

@@ -1,9 +1,9 @@
use actix_web::{delete, get, post, web, HttpResponse, Responder};
use actix_web::{delete, get, post, web, HttpResponse, Responder, Scope};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use crate::config;
use crate::note::{generate_id, Note, NoteInfo, NotePublic};
use crate::size::LIMIT;
use crate::store;
pub fn now() -> u32 {
@@ -41,17 +41,22 @@ async fn create(note: web::Json<Note>) -> impl Responder {
if n.views == None && n.expiration == None {
return bad_req;
}
if !*config::ALLOW_ADVANCED {
n.views = Some(1);
n.expiration = None;
}
match n.views {
Some(v) => {
if v > 100 {
if v > *config::MAX_VIEWS {
return bad_req;
}
n.expiration = None; // views overrides expiration
}
_ => {}
}
match n.expiration {
Some(e) => {
if e > 360 {
if e > *config::MAX_EXPIRATION {
return bad_req;
}
let expiration = now() + (e * 60);
@@ -111,27 +116,9 @@ struct Status {
max_size: usize,
}
#[get("/status")]
async fn status() -> impl Responder {
println!("Limit: {}", *LIMIT);
return HttpResponse::Ok().json(Status {
version: option_env!("CARGO_PKG_VERSION")
.unwrap_or("Unknown")
.to_string(),
max_size: *LIMIT,
});
}
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api")
.service(
web::scope("/notes")
.service(one)
.service(create)
.service(delete)
.service(status),
)
.service(status),
);
pub fn init() -> Scope {
web::scope("/notes")
.service(one)
.service(create)
.service(delete)
}

View File

@@ -10,11 +10,9 @@ lazy_static! {
}
pub fn init(cfg: &mut web::ServiceConfig) {
println!("Limit: {}", *LIMIT);
let json = web::JsonConfig::default().limit(*LIMIT);
let plain = web::PayloadConfig::default()
.limit(*LIMIT)
.mimetype(mime::STAR_STAR);
cfg.data(json);
cfg.data(plain);
cfg.app_data(json).app_data(plain);
}

View File

@@ -0,0 +1,5 @@
mod model;
mod routes;
pub use model::*;
pub use routes::*;

View File

@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Status {
pub version: String,
pub max_size: u32,
pub max_views: u32,
pub max_expiration: u32,
pub allow_advanced: bool,
}

View File

@@ -0,0 +1,19 @@
use actix_web::{get, web, HttpResponse, Responder, Scope};
use crate::config;
use crate::status::Status;
#[get("/")]
async fn get_status() -> impl Responder {
return HttpResponse::Ok().json(Status {
version: config::VERSION.to_string(),
max_size: *config::LIMIT,
max_views: *config::MAX_VIEWS,
max_expiration: *config::MAX_EXPIRATION,
allow_advanced: *config::ALLOW_ADVANCED,
});
}
pub fn init() -> Scope {
web::scope("/status").service(get_status)
}

View File

@@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
# create a new project in my-app
npm init svelte@next my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
```bash
npm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.

View File

@@ -1,25 +0,0 @@
{
"private": true,
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"licenses": "license-checker --summary > licenses.csv"
},
"type": "module",
"devDependencies": {
"@sveltejs/adapter-static": "^1.0.0-next.22",
"@sveltejs/kit": "^1.0.0-next.202",
"svelte": "^3.44.3",
"svelte-preprocess": "^4.10.1",
"tslib": "^2.3.1",
"typescript": "^4.5.4",
"vite": "^2.7.4"
},
"dependencies": {
"@fontsource/fira-mono": "^4.5.0",
"copy-to-clipboard": "^3.3.1",
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
}
}

638
client/pnpm-lock.yaml generated
View File

@@ -1,638 +0,0 @@
lockfileVersion: 5.3
specifiers:
'@fontsource/fira-mono': ^4.5.0
'@sveltejs/adapter-static': ^1.0.0-next.22
'@sveltejs/kit': ^1.0.0-next.202
copy-to-clipboard: ^3.3.1
file-saver: ^2.0.5
pretty-bytes: ^5.6.0
svelte: ^3.44.3
svelte-preprocess: ^4.10.1
tslib: ^2.3.1
typescript: ^4.5.4
vite: ^2.7.4
dependencies:
'@fontsource/fira-mono': 4.5.0
copy-to-clipboard: 3.3.1
file-saver: 2.0.5
pretty-bytes: 5.6.0
devDependencies:
'@sveltejs/adapter-static': 1.0.0-next.22
'@sveltejs/kit': 1.0.0-next.202_svelte@3.44.3
svelte: 3.44.3
svelte-preprocess: 4.10.1_svelte@3.44.3+typescript@4.5.4
tslib: 2.3.1
typescript: 4.5.4
vite: 2.7.4
packages:
/@fontsource/fira-mono/4.5.0:
resolution: {integrity: sha512-KE+d3wmgq/YKM0BqgUF7p2yeBNi805Nfof1lC1wJ7E9i2EWoC363sGdKG+MQBVm+ei3GYZu+Bo8Xha1w1pkB7g==}
dev: false
/@rollup/pluginutils/4.1.2:
resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==}
engines: {node: '>= 8.0.0'}
dependencies:
estree-walker: 2.0.2
picomatch: 2.3.0
dev: true
/@sveltejs/adapter-static/1.0.0-next.22:
resolution: {integrity: sha512-Dc1V9Z72dA7caVwNxxzl9Jhcq4uN9N1udA2GKNTLMu3aWX3Cq+v6C2CddY9Aazr+F9h6J0vi9AienuH+ySRXzQ==}
dev: true
/@sveltejs/kit/1.0.0-next.202_svelte@3.44.3:
resolution: {integrity: sha512-rXmJ0FplkWvD1CaeCfejRYhOJYrlmeUm5Fkw7gIKDdWPQev5rqOhd9B9ZvRpq35oMqCAwaOfK+e5S6k+83feEQ==}
engines: {node: '>=14.13'}
hasBin: true
peerDependencies:
svelte: ^3.44.0
dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.32_svelte@3.44.3+vite@2.7.4
cheap-watch: 1.0.4
sade: 1.7.4
svelte: 3.44.3
vite: 2.7.4
transitivePeerDependencies:
- diff-match-patch
- less
- sass
- stylus
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte/1.0.0-next.32_svelte@3.44.3+vite@2.7.4:
resolution: {integrity: sha512-Lhf5BxVylosHIW6U2s6WDQA39ycd+bXivC8gHsXCJeLzxoHj7Pv7XAOk25xRSXT4wHg9DWFMBQh2DFU0DxHZ2g==}
engines: {node: ^14.13.1 || >= 16}
peerDependencies:
diff-match-patch: ^1.0.5
svelte: ^3.44.0
vite: ^2.7.0
peerDependenciesMeta:
diff-match-patch:
optional: true
dependencies:
'@rollup/pluginutils': 4.1.2
debug: 4.3.3
kleur: 4.1.4
magic-string: 0.25.7
require-relative: 0.8.7
svelte: 3.44.3
svelte-hmr: 0.14.7_svelte@3.44.3
vite: 2.7.4
transitivePeerDependencies:
- supports-color
dev: true
/@types/node/17.0.1:
resolution: {integrity: sha512-NXKvBVUzIbs6ylBwmOwHFkZS2EXCcjnqr8ZCRNaXBkHAf+3mn/rPcJxwrzuc6movh8fxQAsUUfYklJ/EG+hZqQ==}
dev: true
/@types/pug/2.0.5:
resolution: {integrity: sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==}
dev: true
/@types/sass/1.43.1:
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
dependencies:
'@types/node': 17.0.1
dev: true
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/buffer-crc32/0.2.13:
resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
dev: true
/cheap-watch/1.0.4:
resolution: {integrity: sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw==}
engines: {node: '>=8'}
dev: true
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
dev: true
/copy-to-clipboard/3.3.1:
resolution: {integrity: sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==}
dependencies:
toggle-selection: 1.0.6
dev: false
/debug/4.3.3:
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: true
/detect-indent/6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
dev: true
/es6-promise/3.3.1:
resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
dev: true
/esbuild-android-arm64/0.13.15:
resolution: {integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-64/0.13.15:
resolution: {integrity: sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-arm64/0.13.15:
resolution: {integrity: sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-64/0.13.15:
resolution: {integrity: sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-arm64/0.13.15:
resolution: {integrity: sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-32/0.13.15:
resolution: {integrity: sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-64/0.13.15:
resolution: {integrity: sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm/0.13.15:
resolution: {integrity: sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm64/0.13.15:
resolution: {integrity: sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-mips64le/0.13.15:
resolution: {integrity: sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-ppc64le/0.13.15:
resolution: {integrity: sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-netbsd-64/0.13.15:
resolution: {integrity: sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-openbsd-64/0.13.15:
resolution: {integrity: sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-sunos-64/0.13.15:
resolution: {integrity: sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-32/0.13.15:
resolution: {integrity: sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-64/0.13.15:
resolution: {integrity: sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-arm64/0.13.15:
resolution: {integrity: sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild/0.13.15:
resolution: {integrity: sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==}
hasBin: true
requiresBuild: true
optionalDependencies:
esbuild-android-arm64: 0.13.15
esbuild-darwin-64: 0.13.15
esbuild-darwin-arm64: 0.13.15
esbuild-freebsd-64: 0.13.15
esbuild-freebsd-arm64: 0.13.15
esbuild-linux-32: 0.13.15
esbuild-linux-64: 0.13.15
esbuild-linux-arm: 0.13.15
esbuild-linux-arm64: 0.13.15
esbuild-linux-mips64le: 0.13.15
esbuild-linux-ppc64le: 0.13.15
esbuild-netbsd-64: 0.13.15
esbuild-openbsd-64: 0.13.15
esbuild-sunos-64: 0.13.15
esbuild-windows-32: 0.13.15
esbuild-windows-64: 0.13.15
esbuild-windows-arm64: 0.13.15
dev: true
/estree-walker/2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/file-saver/2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: false
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/glob/7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/graceful-fs/4.2.8:
resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}
dev: true
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: true
/inflight/1.0.6:
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-core-module/2.8.0:
resolution: {integrity: sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==}
dependencies:
has: 1.0.3
dev: true
/kleur/4.1.4:
resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}
engines: {node: '>=6'}
dev: true
/magic-string/0.25.7:
resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
dependencies:
sourcemap-codec: 1.4.8
dev: true
/min-indent/1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
dev: true
/minimatch/3.0.4:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimist/1.2.5:
resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}
dev: true
/mkdirp/0.5.5:
resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}
hasBin: true
dependencies:
minimist: 1.2.5
dev: true
/mri/1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
dev: true
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/nanoid/3.1.30:
resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'}
dev: true
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/picomatch/2.3.0:
resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
engines: {node: '>=8.6'}
dev: true
/postcss/8.4.5:
resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.1.30
picocolors: 1.0.0
source-map-js: 1.0.1
dev: true
/pretty-bytes/5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
dev: false
/require-relative/0.8.7:
resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}
dev: true
/resolve/1.20.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
dependencies:
is-core-module: 2.8.0
path-parse: 1.0.7
dev: true
/rimraf/2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.0
dev: true
/rollup/2.61.1:
resolution: {integrity: sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/sade/1.7.4:
resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==}
engines: {node: '>= 6'}
dependencies:
mri: 1.2.0
dev: true
/sander/0.5.1:
resolution: {integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0=}
dependencies:
es6-promise: 3.3.1
graceful-fs: 4.2.8
mkdirp: 0.5.5
rimraf: 2.7.1
dev: true
/sorcery/0.10.0:
resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
hasBin: true
dependencies:
buffer-crc32: 0.2.13
minimist: 1.2.5
sander: 0.5.1
sourcemap-codec: 1.4.8
dev: true
/source-map-js/1.0.1:
resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==}
engines: {node: '>=0.10.0'}
dev: true
/sourcemap-codec/1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
dev: true
/strip-indent/3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
dependencies:
min-indent: 1.0.1
dev: true
/svelte-hmr/0.14.7_svelte@3.44.3:
resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}
peerDependencies:
svelte: '>=3.19.0'
dependencies:
svelte: 3.44.3
dev: true
/svelte-preprocess/4.10.1_svelte@3.44.3+typescript@4.5.4:
resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==}
engines: {node: '>= 9.11.2'}
requiresBuild: true
peerDependencies:
'@babel/core': ^7.10.2
coffeescript: ^2.5.1
less: ^3.11.3
node-sass: '*'
postcss: ^7 || ^8
postcss-load-config: ^2.1.0 || ^3.0.0
pug: ^3.0.0
sass: ^1.26.8
stylus: ^0.54.7
sugarss: ^2.0.0
svelte: ^3.23.0
typescript: ^4.5.2
peerDependenciesMeta:
'@babel/core':
optional: true
coffeescript:
optional: true
less:
optional: true
node-sass:
optional: true
postcss:
optional: true
postcss-load-config:
optional: true
pug:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
typescript:
optional: true
dependencies:
'@types/pug': 2.0.5
'@types/sass': 1.43.1
detect-indent: 6.1.0
magic-string: 0.25.7
sorcery: 0.10.0
strip-indent: 3.0.0
svelte: 3.44.3
typescript: 4.5.4
dev: true
/svelte/3.44.3:
resolution: {integrity: sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg==}
engines: {node: '>= 8'}
dev: true
/toggle-selection/1.0.6:
resolution: {integrity: sha1-bkWxJj8gF/oKzH2J14sVuL932jI=}
dev: false
/tslib/2.3.1:
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
dev: true
/typescript/4.5.4:
resolution: {integrity: sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/vite/2.7.4:
resolution: {integrity: sha512-f+0426k9R/roz5mRNwJlQ+6UOnhCwIypJSbfgCmsVzVJe9jTTM5iRX2GWYUean+iqPBWaU/dYLryx9AoH2pmrw==}
engines: {node: '>=12.2.0'}
hasBin: true
peerDependencies:
less: '*'
sass: '*'
stylus: '*'
peerDependenciesMeta:
less:
optional: true
sass:
optional: true
stylus:
optional: true
dependencies:
esbuild: 0.13.15
postcss: 8.4.5
resolve: 1.20.0
rollup: 2.61.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: true

View File

@@ -1,34 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte'
export let icon: string = ''
export let href: string = ''
$: src = href || `/icons/${icon}.svg`
let html = null
onMount(async () => {
html = await fetch(src).then((res) => res.text())
})
</script>
{#if html === null}
<img on:click {...$$restProps} {src} alt={icon} />
{:else}
<div on:click {...$$restProps}>
{@html html}
</div>
{/if}
<style>
img,
div {
display: inline-block;
contain: strict;
box-sizing: content-box;
}
div > :global(svg) {
display: block;
fill: currentColor;
}
</style>

View File

@@ -1,32 +0,0 @@
<script lang="ts">
import { init } from '$lib/stores/status'
import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte'
import { onMount } from 'svelte'
import '../app.css'
onMount(() => {
init()
})
</script>
<svelte:head>
<title>cryptgeon</title>
</svelte:head>
<main>
<Header />
<slot />
</main>
<Footer />
<style>
main {
padding: 1rem;
padding-bottom: 4rem;
width: 100%;
max-width: 35rem;
margin: 0 auto;
}
</style>

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Contrast</title><path d='M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z'/></svg>

Before

Width:  |  Height:  |  Size: 279 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Copy</title><path d='M456 480H136a24 24 0 01-24-24V128a16 16 0 0116-16h328a24 24 0 0124 24v320a24 24 0 01-24 24z'/><path d='M112 80h288V56a24 24 0 00-24-24H60a28 28 0 00-28 28v316a24 24 0 0024 24h24V112a32 32 0 0132-32z'/></svg>

Before

Width:  |  Height:  |  Size: 313 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Dice</title><path d='M48 366.92L240 480V284L48 170zM192 288c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zm-96 32c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zM272 284v196l192-113.08V170zm48 140c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm96 32c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm32 77.64zM256 32L64 144l192 112 192-112zm0 120c-13.25 0-24-7.16-24-16s10.75-16 24-16 24 7.16 24 16-10.75 16-24 16z'/></svg>

Before

Width:  |  Height:  |  Size: 728 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Eye Off</title><path d='M63.998 86.004l21.998-21.998L448 426.01l-21.998 21.998zM259.34 192.09l60.57 60.57a64.07 64.07 0 00-60.57-60.57zM252.66 319.91l-60.57-60.57a64.07 64.07 0 0060.57 60.57z'/><path d='M256 352a96 96 0 01-92.6-121.34l-69.07-69.08C66.12 187.42 39.24 221.14 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416A233.47 233.47 0 00335 402.2l-53.61-53.6A95.84 95.84 0 01256 352zM256 160a96 96 0 0192.6 121.34L419.26 352c29.15-26.25 56.07-61.56 76.74-96-26.38-43.43-62.9-88.56-101.18-114.82C351.1 111.2 304.31 96 255.76 96a222.92 222.92 0 00-78.21 14.29l53.11 53.11A95.84 95.84 0 01256 160z'/></svg>

Before

Width:  |  Height:  |  Size: 720 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Eye</title><circle cx='256' cy='256' r='64'/><path d='M394.82 141.18C351.1 111.2 304.31 96 255.76 96c-43.69 0-86.28 13-126.59 38.48C88.52 160.23 48.67 207 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416c49 0 95.85-15.07 139.3-44.79C433.31 345 469.71 299.82 496 256c-26.38-43.43-62.9-88.56-101.18-114.82zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z'/></svg>

Before

Width:  |  Height:  |  Size: 474 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Lock Closed</title><path d='M420 192h-68v-80a96 96 0 10-192 0v80H92a12 12 0 00-12 12v280a12 12 0 0012 12h328a12 12 0 0012-12V204a12 12 0 00-12-12zm-106 0H198v-80.75a58 58 0 11116 0z'/></svg>

Before

Width:  |  Height:  |  Size: 275 B

View File

@@ -1,5 +0,0 @@
{
"fixturesFolder": false,
"pluginsFile": false,
"supportFile": false
}

View File

@@ -7,7 +7,7 @@ services:
memcached:
image: memcached:1-alpine
restart: unless-stopped
entrypoint: memcached -m 128M -I 4M
entrypoint: memcached -m 256M -I 128M
ports:
- 11211:11211
@@ -16,6 +16,6 @@ services:
depends_on:
- memcached
environment:
SIZE_LIMIT: 4M
SIZE_LIMIT: 128M
ports:
- 80:5000

View File

@@ -3,7 +3,7 @@ version: '3.8'
services:
memcached:
image: memcached:1-alpine
entrypoint: memcached -m 128 # Limit to 128 MB Ram, customize at free will.
entrypoint: memcached -m 256 -I 128 # Limit to 128 MB Ram, customize at free will. -m must be at least double than -I.
app:
image: cupcakearmy/cryptgeon:latest

18
frontend/README.md Normal file
View File

@@ -0,0 +1,18 @@
# Cryptgeon Frontend
## Locale
Download with these settings:
```json
{
"format": "json",
"indentation": "tab",
"json_unescaped_slashes": true,
"export_sort": "first_added",
"original_filenames": false,
"export_empty_as": "skip",
"add_newline_eof": true,
"replace_breaks": false
}
```

42
frontend/locales/de.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "Hinweis",
"file": "Datei",
"advanced": "erweitert",
"create": "erstellen",
"loading": "Läd...",
"mode": "Modus",
"views": "{n, plural, =0 {Ansichten} =1 {1 Ansicht} other {# Ansichten}}",
"minutes": "{n, plural, =0 {Minuten} =1 {1 Minute} other {# Minuten}}",
"max": "max",
"share_link": "Link teilen",
"copy_clipboard": "in die Zwischenablage kopieren"
},
"home": {
"intro": "Senden Sie ganz einfach <i>vollständig verschlüsselte</i>, sichere Notizen oder Dateien mit einem Klick. Erstellen Sie einfach eine Notiz und teilen Sie den Link.",
"explanation": "die Notiz verfällt und wird nach {type} zerstört.",
"new_note": "neue Note",
"new_note_notice": "<b>Verfügbarkeit:</b><br />es ist nicht garantiert, dass die Notiz gespeichert wird, da alles im Speicher gehalten wird. Wenn dieser voll ist, werden die ältesten Notizen entfernt.<br />(Sie werden wahrscheinlich keine Probleme haben, seien Sie nur gewarnt).",
"errors": {
"note_to_big": "Notiz konnte nicht erstellt werden. Notiz ist zu groß",
"note_error": "konnte keine Notiz erstellen. Bitte versuchen Sie es erneut.",
"max": "max: {n}",
"empty_content": "Notiz ist leer."
},
"copied_to_clipboard": "in die Zwischenablage kopiert 🔗"
},
"show": {
"errors": {
"not_found": "wurde nicht gefunden oder wurde bereits gelöscht.",
"decryption_failed": "falsches Passwort. konnte nicht entziffert werden. wahrscheinlich ein defekter Link. Notiz wurde zerstört."
},
"explanation": "Klicken Sie unten, um die Notiz anzuzeigen und zu löschen, wenn der Zähler sein Limit erreicht hat",
"show_note": "Notiz anzeigen",
"warning_will_not_see_again": "haben Sie <b>keine</b> Gelegenheit, die Notiz noch einmal zu sehen.",
"download_all": "alle herunterladen"
},
"file_upload": {
"selected_files": "Ausgewählte Dateien",
"no_files_selected": "Keine Dateien ausgewählt"
}
}

42
frontend/locales/en.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "note",
"file": "file",
"advanced": "advanced",
"create": "create",
"loading": "loading...",
"mode": "mode",
"views": "{n, plural, =0 {views} =1 {1 view} other {# views}}",
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
"max": "max",
"share_link": "share link",
"copy_clipboard": "copy to clipboard"
},
"home": {
"intro": "Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and share the link.",
"explanation": "the note will expire and be destroyed after {type}.",
"new_note": "new note",
"new_note_notice": "<b>availability:</b><br />the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes will be removed.<br />(you probably will be fine, just be warned.)",
"errors": {
"note_to_big": "could not create note. note is to big",
"note_error": "could not create note. please try again.",
"max": "max: {n}",
"empty_content": "note is empty."
},
"copied_to_clipboard": "copied to clipboard 🔗"
},
"show": {
"errors": {
"not_found": "note was not found or was already deleted.",
"decryption_failed": "wrong password. could not decipher. probably a broken link. note was destroyed."
},
"explanation": "click below to show and delete the note if the counter has reached it's limit",
"show_note": "show note",
"warning_will_not_see_again": "you will <b>not</b> get the chance to see the note again.",
"download_all": "download all"
},
"file_upload": {
"selected_files": "Selected Files",
"no_files_selected": "No Files Selected"
}
}

42
frontend/locales/es.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "nota",
"file": "archivo",
"advanced": "avanzado",
"create": "crear",
"loading": "cargando...",
"mode": "modo",
"views": "{n, plural, =0 {vistas} =1 {1 vista} other {# vistas}}",
"minutes": "{n, plural, =0 {minutos} =1 {1 minuto} other {# minutos}}",
"max": "max",
"share_link": "compartir enlace",
"copy_clipboard": "copiar al portapapeles"
},
"home": {
"intro": "Envía fácilmente notas o archivos <i>totalmente encriptados</i> y seguros con un solo clic. Solo tienes que crear una nota y compartir el enlace.",
"explanation": "la nota expirará y se destruirá después de {type}.",
"new_note": "nueva nota",
"new_note_notice": "<b>disponibilidad:</b><br />no se garantiza que la nota se almacene, ya que todo se guarda en la memoria RAM, si se llena se eliminarán las notas más antiguas.<br />(probablemente estará bien, sólo está advertido.)",
"errors": {
"note_to_big": "no se pudo crear la nota. la nota es demasiado grande",
"note_error": "No se ha podido crear la nota. Por favor, inténtelo de nuevo.",
"max": "max: {n}",
"empty_content": "la nota está vacía."
},
"copied_to_clipboard": "copiado al portapapeles 🔗"
},
"show": {
"errors": {
"not_found": "la nota no se encontró o ya fue borrada.",
"decryption_failed": "contraseña incorrecta. no se pudo descifrar. probablemente un enlace roto. la nota fue destruida."
},
"explanation": "pulse abajo para mostrar y borrar la nota si el contador ha llegado a su límite",
"show_note": "mostrar nota",
"warning_will_not_see_again": " <b>no</b> tendrás la oportunidad de volver a ver la nota.",
"download_all": "descargar todo"
},
"file_upload": {
"selected_files": "Archivos seleccionados",
"no_files_selected": "No hay archivos seleccionados"
}
}

42
frontend/locales/fr.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "note",
"file": "fichier",
"advanced": "avancé",
"create": "créer",
"loading": "chargement...",
"mode": "mode",
"views": "{n, plural, =0 {vues} =1 {1 vue} other {# vues}}",
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
"max": "max",
"share_link": "partager le lien",
"copy_clipboard": "copier dans le presse-papiers"
},
"home": {
"intro": "Envoyez facilement des notes ou des fichiers <i>entièrement cryptés</i> et sécurisés en un seul clic. Il suffit de créer une note et de partager le lien.",
"explanation": "la note expirera et sera détruite après {type}.",
"new_note": "nouvelle note",
"new_note_notice": "<b>disponibilité :</b><br />la note n'est pas garantie d'être stockée car tout est conservé dans la mémoire vive, si elle se remplit les notes les plus anciennes seront supprimées.<br />(vous serez probablement bien, soyez juste averti.)",
"errors": {
"note_to_big": "Impossible de créer une note. La note est trop grande",
"note_error": "n'a pas pu créer de note. Veuillez réessayer.",
"max": "max: {n}",
"empty_content": "La note est vide."
},
"copied_to_clipboard": "copié dans le presse-papiers 🔗"
},
"show": {
"errors": {
"not_found": "La note n'a pas été trouvée ou a déjà été supprimée.",
"decryption_failed": "mauvais mot de passe. impossible à déchiffrer. probablement un lien brisé. la note a été détruite."
},
"explanation": "Cliquez ci-dessous pour afficher et supprimer la note si le compteur a atteint sa limite.",
"show_note": "note de présentation",
"warning_will_not_see_again": "vous <b>n'aurez pas</b> la chance de revoir la note.",
"download_all": "télécharger tout"
},
"file_upload": {
"selected_files": "Fichiers sélectionnés",
"no_files_selected": "Aucun fichier sélectionné"
}
}

42
frontend/locales/it.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "nota",
"file": "file",
"advanced": "avanzato",
"create": "crea",
"loading": "carica...",
"mode": "modalita",
"views": "{n, plural, =0 {viste} =1 {1 vista} other {# viste}}",
"minutes": "{n, plural, =0 {minuti} =1 {1 minuto} other {# minuti}}",
"max": "max",
"share_link": "condividi link",
"copy_clipboard": "copia negli appunti"
},
"home": {
"intro": "Invia facilmente note o file <i>completamente criptati</i> e sicuri con un solo clic. Basta creare una nota e condividere il link.",
"explanation": "la nota scadrà e sarà distrutta dopo {type}.",
"new_note": "nuova nota",
"new_note_notice": "<b>disponibilità:</b><br />la nota non è garantita per essere memorizzata come tutto è tenuto in ram, se si riempie le note più vecchie saranno rimosse.<br />(probabilmente andrà bene, basta essere avvertiti).",
"errors": {
"note_to_big": "impossibile creare una nota. la nota è troppo grande",
"note_error": "Impossibile creare la nota. Riprova.",
"max": "max: {n}",
"empty_content": "la nota è vuota."
},
"copied_to_clipboard": "copiato negli appunti 🔗"
},
"show": {
"errors": {
"not_found": "non è stata trovata o è stata già cancellata.",
"decryption_failed": "password sbagliata. non ha potuto decifrare. probabilmente un link rotto. la nota è stata distrutta."
},
"explanation": "clicca sotto per mostrare e cancellare la nota se il contatore ha raggiunto il suo limite",
"show_note": "mostra la nota",
"warning_will_not_see_again": " <b>non</b> avrete la possibilità di rivedere la nota.",
"download_all": "scarica tutti"
},
"file_upload": {
"selected_files": "File selezionati",
"no_files_selected": "Nessun file selezionato"
}
}

33
frontend/package.json Normal file
View File

@@ -0,0 +1,33 @@
{
"private": true,
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"check": "svelte-check --tsconfig tsconfig.json",
"licenses": "license-checker --summary > licenses.csv",
"locale:download": "node scripts/locale.js"
},
"type": "module",
"devDependencies": {
"@lokalise/node-api": "^7.1.1",
"@sveltejs/adapter-static": "^1.0.0-next.28",
"@sveltejs/kit": "1.0.0-next.288",
"@types/file-saver": "^2.0.5",
"adm-zip": "^0.5.9",
"dotenv": "^16.0.0",
"svelte": "^3.46.4",
"svelte-check": "^2.4.5",
"svelte-intl-precompile": "^0.8.1",
"svelte-preprocess": "^4.10.4",
"tslib": "^2.3.1",
"typescript": "^4.6.2",
"vite": "^2.8.6"
},
"dependencies": {
"@fontsource/fira-mono": "^4.5.3",
"copy-to-clipboard": "^3.3.1",
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
}
}

1551
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
import dotenv from 'dotenv'
import { LokaliseApi } from '@lokalise/node-api'
import https from 'https'
import AdmZip from 'adm-zip'
dotenv.config()
const apiKey = process.env.LOKALISE_API_KEY
const project_id = process.env.LOKALISE_PROJECT
if (!apiKey) throw new Error('No API Key set for Lokalize! Set with "LOKALISE_API_KEY"')
if (!project_id) throw new Error('No project id set for Lokalize! Set with "LOKALISE_PROJECT"')
const client = new LokaliseApi({ apiKey })
const WGet = (url) =>
new Promise((done) => {
https
.get(url, (res) => {
const data = []
res
.on('data', (chunk) => {
data.push(chunk)
})
.on('end', () => {
let buffer = Buffer.concat(data)
done(buffer)
})
})
.on('error', (err) => {
console.log('download error:', err)
})
})
async function download() {
// For details see: https://app.lokalise.com/api2docs/curl/#transition-download-files-post
const download = await client.files().download(project_id, {
format: 'json',
indentation: 'tab',
json_unescaped_slashes: true,
original_filenames: false,
bundle_structure: '%LANG_ISO%.%FORMAT%',
export_sort: 'first_added',
export_empty_as: 'skip',
add_newline_eof: true,
replace_breaks: false,
})
const buffered = await WGet(download.bundle_url)
const zip = new AdmZip(buffered)
zip.extractAllTo('./locales', true)
}
download().catch((e) => {
console.error(e)
process.exit(1)
})

View File

@@ -7,7 +7,7 @@ export class Files {
})
}
static fromString(s: string): Promise<Blob> {
static async fromString(s: string): Promise<Blob> {
return fetch(s).then((r) => r.blob())
}
}

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Contrast</title><path
d="M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z"
/></svg
>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Copy</title><path
d="M456 480H136a24 24 0 01-24-24V128a16 16 0 0116-16h328a24 24 0 0124 24v320a24 24 0 01-24 24z"
/><path
d="M112 80h288V56a24 24 0 00-24-24H60a28 28 0 00-28 28v316a24 24 0 0024 24h24V112a32 32 0 0132-32z"
/></svg
>

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Dice</title><path
d="M48 366.92L240 480V284L48 170zM192 288c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zm-96 32c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zM272 284v196l192-113.08V170zm48 140c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm96 32c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm32 77.64zM256 32L64 144l192 112 192-112zm0 120c-13.25 0-24-7.16-24-16s10.75-16 24-16 24 7.16 24 16-10.75 16-24 16z"
/></svg
>

After

Width:  |  Height:  |  Size: 736 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Eye</title><circle cx="256" cy="256" r="64" /><path
d="M394.82 141.18C351.1 111.2 304.31 96 255.76 96c-43.69 0-86.28 13-126.59 38.48C88.52 160.23 48.67 207 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416c49 0 95.85-15.07 139.3-44.79C433.31 345 469.71 299.82 496 256c-26.38-43.43-62.9-88.56-101.18-114.82zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z"
/></svg
>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Eye Off</title><path
d="M63.998 86.004l21.998-21.998L448 426.01l-21.998 21.998zM259.34 192.09l60.57 60.57a64.07 64.07 0 00-60.57-60.57zM252.66 319.91l-60.57-60.57a64.07 64.07 0 0060.57 60.57z"
/><path
d="M256 352a96 96 0 01-92.6-121.34l-69.07-69.08C66.12 187.42 39.24 221.14 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416A233.47 233.47 0 00335 402.2l-53.61-53.6A95.84 95.84 0 01256 352zM256 160a96 96 0 0192.6 121.34L419.26 352c29.15-26.25 56.07-61.56 76.74-96-26.38-43.43-62.9-88.56-101.18-114.82C351.1 111.2 304.31 96 255.76 96a222.92 222.92 0 00-78.21 14.29l53.11 53.11A95.84 95.84 0 01256 160z"
/></svg
>

After

Width:  |  Height:  |  Size: 732 B

View File

@@ -4,13 +4,16 @@ import { writable } from 'svelte/store'
export type Status = {
version: string
max_size: number
max_views: number
max_expiration: number
allow_advanced: boolean
}
export const status = writable<null | Status>(null)
export async function init() {
const data = await call({
url: 'status',
url: 'status/',
method: 'get',
})
status.set(data)

View File

@@ -2,6 +2,8 @@
import type { FileDTO } from '$lib/api'
import { Files } from '$lib/files'
import { createEventDispatcher } from 'svelte'
import { t } from 'svelte-intl-precompile'
import Button from './Button.svelte'
import MaxSize from './MaxSize.svelte'
export let label: string = ''
@@ -11,8 +13,8 @@
async function onInput(e: Event) {
const input = e.target as HTMLInputElement
if (input.files.length) {
files = Array.from(input.files)
if (input?.files?.length) {
files = [...files, ...Array.from(input.files)]
const data: FileDTO[] = await Promise.all(
files.map(async (file) => ({
name: file.name,
@@ -21,15 +23,17 @@
contents: await Files.toString(file),
}))
)
console.debug(
'files',
data.map((d) => d.contents.length)
)
dispatch('file', JSON.stringify(data))
} else {
dispatch('file', '')
}
}
function clear(e: Event) {
e.preventDefault()
files = []
dispatch('file', '')
}
</script>
<label>
@@ -40,18 +44,20 @@
<div class="box">
{#if files.length}
<div>
<b>Selected Files</b>
<b>{$t('file_upload.selected_files')}</b>
{#each files as file}
<div class="file">
{file.name}
</div>
{/each}
<div class="spacer" />
<Button on:click={clear}>Clear</Button>
</div>
{:else}
<div>
<b>No Files Selected</b>
<b>{$t('file_upload.no_files_selected')}</b>
<br />
<small>max: <MaxSize /></small>
<small>{$t('common.max')}: <MaxSize /></small>
</div>
{/if}
</div>
@@ -69,4 +75,8 @@
align-items: center;
cursor: pointer;
}
.spacer {
margin-top: 1rem;
}
</style>

View File

@@ -0,0 +1,37 @@
<script lang="ts" context="module">
import IconContrast from '$lib/icons/IconContrast.svelte'
import IconCopy from '$lib/icons/IconCopy.svelte'
import IconDice from '$lib/icons/IconDice.svelte'
import IconEye from '$lib/icons/IconEye.svelte'
import IconEyeOff from '$lib/icons/IconEyeOff.svelte'
const map = {
contrast: IconContrast,
copy: IconCopy,
dice: IconDice,
eye: IconEye,
'eye-off': IconEyeOff,
}
</script>
<script lang="ts">
export let icon: keyof typeof map
</script>
<div on:click {...$$restProps}>
{#if map[icon]}
<svelte:component this={map[icon]} />
{/if}
</div>
<style>
div {
display: inline-block;
contain: strict;
box-sizing: content-box;
}
div > :global(svg) {
display: block;
fill: currentColor;
}
</style>

View File

@@ -1,12 +1,13 @@
<script lang="ts">
import { status } from '$lib/stores/status'
import prettyBytes from 'pretty-bytes'
import { _ } from 'svelte-intl-precompile'
</script>
<span>
{#if $status !== null}
{prettyBytes($status.max_size, { binary: true })}
{:else}
loading...
{$_('common.loading')}
{/if}
</span>

View File

@@ -4,6 +4,7 @@
import copy from 'copy-to-clipboard'
import { saveAs } from 'file-saver'
import prettyBytes from 'pretty-bytes'
import { t } from 'svelte-intl-precompile'
import Button from './Button.svelte'
export let note: NotePublic
@@ -28,20 +29,20 @@
}
</script>
<p class="error-text">you will <b>not</b> get the chance to see the note again.</p>
<p class="error-text">{@html $t('show.warning_will_not_see_again')}</p>
{#if note.meta.type === 'text'}
<div class="note" data-testid="note-result">
<div class="note">
{note.contents}
</div>
<Button on:click={() => copy(note.contents)}>copy to clipboard</Button>
<Button on:click={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
{:else}
{#each files as file}
<div class="note file" data-testid="note-result">
<div class="note file">
<b on:click={() => downloadFile(file)}> {file.name}</b>
<small> {file.type} {prettyBytes(file.size)}</small>
</div>
{/each}
<Button on:click={download}>download all</Button>
<Button on:click={download}>{$t('show.download_all')}</Button>
{/if}
<style>
@@ -59,6 +60,9 @@
.note b {
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.note.file {
@@ -66,4 +70,8 @@
justify-content: space-between;
align-items: center;
}
.note.file small {
padding-left: 1rem;
}
</style>

View File

@@ -49,7 +49,7 @@
height: 2rem;
width: 1.25rem;
left: 0.125rem;
bottom: 0.1rem;
bottom: 0.125rem;
background-color: var(--ui-bg-1);
-webkit-transition: 0.4s;
transition: var(--ui-anim);

View File

@@ -1,19 +1,23 @@
<script lang="ts">
import { getRandomBytes, Hex } from '$lib/crypto'
import copyToClipboard from 'copy-to-clipboard'
import { t } from 'svelte-intl-precompile'
import { fade } from 'svelte/transition'
import Icon from './Icon.svelte'
export let label: string = ''
export let value
export let value: any
export let validate: (value: any) => boolean | string = () => true
export let copy: boolean = false
export let random: boolean = false
const initialType = $$restProps.type
const isPassword = initialType === 'password'
let hidden = true
let notification: string | null = null
let notificationTimeout: NodeJS.Timeout | null = null
$: valid = validate(value)
$: if (isPassword) {
value
@@ -24,29 +28,46 @@
hidden = !hidden
}
function copyFN() {
copyToClipboard(value)
copyToClipboard(value.toString())
notify($t('home.copied_to_clipboard'))
}
function randomFN() {
value = Hex.encode(getRandomBytes(20))
}
function notify(msg: string, delay: number = 2000) {
if (notificationTimeout) {
clearTimeout(notificationTimeout)
}
notificationTimeout = setTimeout(() => {
notification = null
}, delay)
notification = msg
}
</script>
<label>
<small disabled={$$restProps.disabled}>
{label}
{#if valid !== true}
<span class="error-text">{valid}</span>
{/if}
</small>
<input bind:value {...$$restProps} />
<input bind:value {...$$restProps} class:valid={valid === true} />
<div class="icons">
{#if isPassword}
<Icon class="icon" icon={hidden ? 'eye-sharp' : 'eye-off-sharp'} on:click={toggle} />
<Icon class="icon" icon={hidden ? 'eye' : 'eye-off'} on:click={toggle} />
{/if}
{#if random}
<Icon class="icon" icon="dice-sharp" on:click={randomFN} />
<Icon class="icon" icon="dice" on:click={randomFN} />
{/if}
{#if copy}
<Icon class="icon" icon="copy-sharp" on:click={copyFN} />
<Icon class="icon" icon="copy" on:click={copyFN} />
{/if}
</div>
{#if notification}
<div class="notification" transition:fade><small>{notification}</small></div>
{/if}
</label>
<style>
@@ -55,6 +76,10 @@
display: block;
}
label > small {
display: block;
}
input {
width: 100%;
margin: 0;
@@ -68,6 +93,10 @@
border-color: var(--ui-clr-primary);
}
input:not(.valid) {
border-color: var(--ui-clr-error);
}
.icons {
border: 1px red;
position: absolute;
@@ -88,4 +117,11 @@
.icons > :global(.icon:hover) {
border-color: var(--ui-clr-primary);
}
.notification {
text-align: right;
position: absolute;
right: 0;
bottom: -1.5em;
}
</style>

View File

@@ -41,7 +41,7 @@
</script>
<div on:click={change}>
<Icon class="icon" icon="contrast-sharp" />
<Icon class="icon" icon="contrast" />
{$theme}
</div>

View File

@@ -1,12 +1,14 @@
<script lang="ts">
import { create, Note, PayloadToLargeError } from '$lib/api'
import { encrypt, getKeyFromString, getRandomBytes, Hex } from '$lib/crypto'
import { status } from '$lib/stores/status'
import Button from '$lib/ui/Button.svelte'
import FileUpload from '$lib/ui/FileUpload.svelte'
import MaxSize from '$lib/ui/MaxSize.svelte'
import Switch from '$lib/ui/Switch.svelte'
import TextArea from '$lib/ui/TextArea.svelte'
import TextInput from '$lib/ui/TextInput.svelte'
import { t } from 'svelte-intl-precompile'
import { blur } from 'svelte/transition'
let note: Note = {
@@ -18,20 +20,24 @@
let result: { password: string; id: string } | null = null
let advanced = false
let file = false
let type = false
let timeExpiration = false
let message = ''
let loading = false
let error: string | null = null
$: if (!advanced) {
note.views = 1
type = false
timeExpiration = false
}
$: {
let fraction: string
fraction = type ? `${note.expiration} minutes` : `${note.views} views`
message = 'the note will expire and be destroyed after ' + fraction
message = $t('home.explanation', {
values: {
type: $t(timeExpiration ? 'common.minutes' : 'common.views', {
values: { n: (timeExpiration ? note.expiration : note.views) ?? '?' },
}),
},
})
}
$: note.meta.type = file ? 'file' : 'text'
@@ -40,20 +46,21 @@
note.contents = ''
}
class EmptyContentError extends Error {}
async function submit() {
try {
error = null
loading = true
const password = Hex.encode(getRandomBytes(32))
const key = await getKeyFromString(password)
if (note.contents === '') throw new EmptyContentError()
const data: Note = {
contents: await encrypt(note.contents, key),
meta: note.meta,
}
// @ts-ignore
if (type) data.expiration = parseInt(note.expiration)
// @ts-ignore
else data.views = parseInt(note.views)
if (timeExpiration) data.expiration = parseInt(note.expiration as any)
else data.views = parseInt(note.views as any)
const response = await create(data)
result = {
@@ -62,9 +69,11 @@
}
} catch (e) {
if (e instanceof PayloadToLargeError) {
error = 'could not create not. note is to big'
error = $t('home.errors.note_to_big')
} else if (e instanceof EmptyContentError) {
error = $t('home.errors.empty_content')
} else {
error = 'could not create note. please try again.'
error = $t('home.errors.note_error')
}
} finally {
loading = false
@@ -80,44 +89,38 @@
<TextInput
type="text"
readonly
label="share link"
label={$t('common.share_link')}
value="{window.location.origin}/note/{result.id}#{result.password}"
copy
data-testid="note-share-link"
/>
<br />
<p>
<b>availability:</b>
<br />
the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes
will be removed.
<br />
(you probably will be fine, just be warned.)
{@html $t('home.new_note_notice')}
</p>
<br />
<Button on:click={reset}>new note</Button>
<Button on:click={reset}>{$t('home.new_note')}</Button>
{:else}
<p>
{@html $t('home.intro')}
</p>
<form on:submit|preventDefault={submit}>
<fieldset disabled={loading}>
{#if file}
<FileUpload label="file" on:file={(f) => (note.contents = f.detail)} />
<FileUpload label={$t('common.file')} on:file={(f) => (note.contents = f.detail)} />
{:else}
<TextArea
label="note"
bind:value={note.contents}
placeholder="..."
data-testid="input-note"
/>
<TextArea label={$t('common.note')} bind:value={note.contents} placeholder="..." />
{/if}
<div class="bottom">
<Switch class="file" label="file" bind:value={file} />
<Switch label="advanced" bind:value={advanced} />
<Switch class="file" label={$t('common.file')} bind:value={file} />
{#if $status?.allow_advanced}
<Switch label={$t('common.advanced')} bind:value={advanced} />
{/if}
<div class="grow" />
<div class="tr">
<small>max: <MaxSize /> </small>
<small>{$t('common.max')}: <MaxSize /> </small>
<br />
<Button type="submit" data-testid="button-create">create</Button>
<Button type="submit">{$t('common.create')}</Button>
</div>
</div>
@@ -128,7 +131,7 @@
<p>
<br />
{#if loading}
loading...
{$t('common.loading')}
{:else}
{message}
{/if}
@@ -140,20 +143,26 @@
<div class="fields">
<TextInput
type="number"
label="views"
label={$t('common.views', { values: { n: 0 } })}
bind:value={note.views}
disabled={type}
max={100}
disabled={timeExpiration}
max={$status?.max_views}
validate={(v) =>
($status && v < $status?.max_views) ||
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
/>
<div class="middle-switch">
<Switch label="mode" bind:value={type} color={false} />
<Switch label={$t('common.mode')} bind:value={timeExpiration} color={false} />
</div>
<TextInput
type="number"
label="minutes"
label={$t('common.minutes', { values: { n: 0 } })}
bind:value={note.expiration}
disabled={!type}
max={360}
disabled={!timeExpiration}
max={$status?.max_expiration}
validate={(v) =>
($status && v < $status?.max_expiration) ||
$t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })}
/>
</div>
</div>

View File

@@ -1,5 +1,4 @@
<script lang="ts">
import Icon from '$lib/ui/Icon.svelte'
import ThemeToggle from '$lib/ui/ThemeToggle.svelte'
</script>

View File

@@ -83,7 +83,7 @@
header {
text-align: center;
margin-top: 4rem;
margin-top: 3rem;
margin-bottom: 2rem;
}

View File

@@ -0,0 +1,42 @@
<script lang="ts" context="module">
import { init, waitLocale, getLocaleFromNavigator } from 'svelte-intl-precompile'
// @ts-ignore
import { registerAll } from '$locales'
registerAll()
init({ initialLocale: getLocaleFromNavigator() ?? undefined, fallbackLocale: 'en' })
</script>
<script lang="ts">
import { init as initStores } from '$lib/stores/status'
import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte'
import { onMount } from 'svelte'
import '../app.css'
onMount(() => {
initStores()
})
</script>
<svelte:head>
<title>cryptgeon</title>
</svelte:head>
{#await waitLocale() then _}
<main>
<Header />
<slot />
</main>
<Footer />
{/await}
<style>
main {
padding: 1rem;
padding-bottom: 4rem;
width: 100%;
max-width: 35rem;
margin: 0 auto;
}
</style>

View File

@@ -5,7 +5,6 @@
export const hydrate = dev
export const router = browser
export const prerender = true
</script>
<svelte:head>
@@ -22,10 +21,12 @@
<AboutParagraph title="how does it work?">
<span>
each note has a 512bit generated <i>id</i> that is used to retrieve the note. the note is then
encrypted with aes in gcm mode on the client side and then sent to the server. data is stored in
memory and never persisted to disk. the server never sees the encryption key and cannot decrypt
the contents of the notes even if it tried to.
each note has a generated <code>id (256bit)</code> and <code>key 256(bit)</code>. The
<code>id</code>
is used to save & retrieve the note. the note is then encrypted with aes in gcm mode on the client
side with the <code>key</code> and then sent to the server. data is stored in memory and never
persisted to disk. the server never sees the encryption key and cannot decrypt the contents of
the notes even if it tried to.
</span>
</AboutParagraph>
@@ -48,6 +49,14 @@
</span>
</AboutParagraph>
<AboutParagraph title="translations">
<span
>translations are managed on <a href="https://lokalise.com/" target="_blank">Lokalise</a>,
which granted an open source license to use the paid version. If you are interested in helping
translating don't hesitate to contact me!
</span>
</AboutParagraph>
<AboutParagraph title="attribution">
<span>
icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from

View File

@@ -1,7 +1,3 @@
<script context="module" lang="ts">
export const prerender = true
</script>
<script lang="ts">
import Create from '$lib/views/Create.svelte'
</script>

View File

@@ -1,18 +1,22 @@
<script context="module" lang="ts">
export async function load({ page }) {
import type { Load } from '@sveltejs/kit'
export const load: Load = async ({ params }) => {
return {
props: page.params,
props: params,
}
}
</script>
<script lang="ts">
import { onMount } from 'svelte'
import { t } from 'svelte-intl-precompile'
import type { NotePublic } from '$lib/api'
import { get, info } from '$lib/api'
import { decrypt, getKeyFromString } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte'
import ShowNote from '$lib/ui/ShowNote.svelte'
import { onMount } from 'svelte'
export let id: string
@@ -26,7 +30,7 @@
onMount(async () => {
try {
loading = true
error = null
error = false
password = window.location.hash.slice(1)
await info(id)
exists = true
@@ -55,20 +59,18 @@
{#if !loading}
{#if !exists}
<p class="error-text" data-testid="note-not-found">
note was not found or was already deleted.
</p>
<p class="error-text">{$t('show.errors.not_found')}</p>
{:else if note && !error}
<ShowNote {note} />
{:else}
<form on:submit|preventDefault={show}>
<fieldset>
<p>click below to show and delete the note if the counter has reached it's limit</p>
<Button type="submit" data-testid="button-show">show note</Button>
<p>{$t('show.explanation')}</p>
<Button type="submit">{$t('show.show_note')}</Button>
{#if error}
<br />
<p class="error-text">
wrong password. could not decipher. probably a broken link. note was destroyed.
{$t('show.errors.decryption_failed')}
<br />
</p>
{/if}
@@ -77,5 +79,5 @@
{/if}
{/if}
{#if loading}
<p>loading...</p>
<p>{$t('common.loading')}</p>
{/if}

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -1,3 +1,4 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Allow: /$
Disallow: /

View File

@@ -1,5 +1,6 @@
import preprocess from 'svelte-preprocess'
import adapter from '@sveltejs/adapter-static'
import precompileIntl from 'svelte-intl-precompile/sveltekit-plugin'
export default {
preprocess: preprocess(),
@@ -8,6 +9,10 @@ export default {
adapter: adapter({
fallback: 'index.html',
}),
target: '#svelte',
vite: {
plugins: [
precompileIntl('locales'), // if your translations are defined in /locales/[lang].json
],
},
},
}

View File

@@ -24,7 +24,8 @@
"checkJs": true,
"paths": {
"$lib/*": ["src/lib/*"]
}
},
"strict": true
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
}

View File

@@ -1,17 +1,13 @@
{
"scripts": {
"dev:docker": "docker-compose up memcached",
"dev:backend": "cargo watch -x 'run --bin cryptgeon'",
"dev:front": "pnpm --prefix client run dev",
"dev:backend": "cd backend && cargo watch -x 'run --bin cryptgeon'",
"dev:front": "pnpm --prefix frontend run dev",
"dev:proxy": "node proxy.mjs",
"dev": "run-p dev:*"
},
"devDependencies": {
"http-proxy": "^1.18.1",
"npm-run-all": "^4.1.5"
},
"dependencies": {
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
}
}

207
pnpm-lock.yaml generated
View File

@@ -1,14 +1,8 @@
lockfileVersion: 5.3
specifiers:
file-saver: ^2.0.5
http-proxy: ^1.18.1
npm-run-all: ^4.1.5
pretty-bytes: ^5.6.0
dependencies:
file-saver: 2.0.5
pretty-bytes: 5.6.0
devDependencies:
http-proxy: 1.18.1
@@ -88,21 +82,25 @@ packages:
is-arrayish: 0.2.1
dev: true
/es-abstract/1.18.0:
resolution: {integrity: sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==}
/es-abstract/1.19.1:
resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
es-to-primitive: 1.2.1
function-bind: 1.1.1
get-intrinsic: 1.1.1
get-symbol-description: 1.0.0
has: 1.0.3
has-symbols: 1.0.2
is-callable: 1.2.3
is-negative-zero: 2.0.1
is-regex: 1.1.2
is-string: 1.0.5
object-inspect: 1.10.2
internal-slot: 1.0.3
is-callable: 1.2.4
is-negative-zero: 2.0.2
is-regex: 1.1.4
is-shared-array-buffer: 1.0.1
is-string: 1.0.7
is-weakref: 1.0.2
object-inspect: 1.12.0
object-keys: 1.1.1
object.assign: 4.1.2
string.prototype.trimend: 1.0.4
@@ -114,9 +112,9 @@ packages:
resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
engines: {node: '>= 0.4'}
dependencies:
is-callable: 1.2.3
is-date-object: 1.0.2
is-symbol: 1.0.3
is-callable: 1.2.4
is-date-object: 1.0.5
is-symbol: 1.0.4
dev: true
/escape-string-regexp/1.0.5:
@@ -128,12 +126,8 @@ packages:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: true
/file-saver/2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: false
/follow-redirects/1.14.6:
resolution: {integrity: sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==}
/follow-redirects/1.14.7:
resolution: {integrity: sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
@@ -154,8 +148,16 @@ packages:
has-symbols: 1.0.2
dev: true
/graceful-fs/4.2.6:
resolution: {integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==}
/get-symbol-description/1.0.0:
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.1.1
dev: true
/graceful-fs/4.2.9:
resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==}
dev: true
/has-bigints/1.0.1:
@@ -172,6 +174,13 @@ packages:
engines: {node: '>= 0.4'}
dev: true
/has-tostringtag/1.0.0:
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
engines: {node: '>= 0.4'}
dependencies:
has-symbols: 1.0.2
dev: true
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@@ -188,73 +197,101 @@ packages:
engines: {node: '>=8.0.0'}
dependencies:
eventemitter3: 4.0.7
follow-redirects: 1.14.6
follow-redirects: 1.14.7
requires-port: 1.0.0
transitivePeerDependencies:
- debug
dev: true
/internal-slot/1.0.3:
resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.1.1
has: 1.0.3
side-channel: 1.0.4
dev: true
/is-arrayish/0.2.1:
resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=}
dev: true
/is-bigint/1.0.1:
resolution: {integrity: sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==}
/is-bigint/1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
dependencies:
has-bigints: 1.0.1
dev: true
/is-boolean-object/1.1.0:
resolution: {integrity: sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==}
/is-boolean-object/1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
has-tostringtag: 1.0.0
dev: true
/is-callable/1.2.3:
resolution: {integrity: sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==}
/is-callable/1.2.4:
resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==}
engines: {node: '>= 0.4'}
dev: true
/is-core-module/2.3.0:
resolution: {integrity: sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==}
/is-core-module/2.8.1:
resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==}
dependencies:
has: 1.0.3
dev: true
/is-date-object/1.0.2:
resolution: {integrity: sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==}
/is-date-object/1.0.5:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.0
dev: true
/is-negative-zero/2.0.2:
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
engines: {node: '>= 0.4'}
dev: true
/is-negative-zero/2.0.1:
resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==}
/is-number-object/1.0.6:
resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==}
engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.0
dev: true
/is-number-object/1.0.4:
resolution: {integrity: sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==}
engines: {node: '>= 0.4'}
dev: true
/is-regex/1.1.2:
resolution: {integrity: sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==}
/is-regex/1.1.4:
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
has-symbols: 1.0.2
has-tostringtag: 1.0.0
dev: true
/is-string/1.0.5:
resolution: {integrity: sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==}
/is-shared-array-buffer/1.0.1:
resolution: {integrity: sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==}
dev: true
/is-string/1.0.7:
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.0
dev: true
/is-symbol/1.0.3:
resolution: {integrity: sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==}
/is-symbol/1.0.4:
resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
engines: {node: '>= 0.4'}
dependencies:
has-symbols: 1.0.2
dev: true
/is-weakref/1.0.2:
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
dependencies:
call-bind: 1.0.2
dev: true
/isexe/2.0.0:
resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=}
dev: true
@@ -267,7 +304,7 @@ packages:
resolution: {integrity: sha1-L19Fq5HjMhYjT9U62rZo607AmTs=}
engines: {node: '>=4'}
dependencies:
graceful-fs: 4.2.6
graceful-fs: 4.2.9
parse-json: 4.0.0
pify: 3.0.0
strip-bom: 3.0.0
@@ -292,7 +329,7 @@ packages:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
hosted-git-info: 2.8.9
resolve: 1.20.0
resolve: 1.21.0
semver: 5.7.1
validate-npm-package-license: 3.0.4
dev: true
@@ -309,12 +346,12 @@ packages:
minimatch: 3.0.4
pidtree: 0.3.1
read-pkg: 3.0.0
shell-quote: 1.7.2
string.prototype.padend: 3.1.2
shell-quote: 1.7.3
string.prototype.padend: 3.1.3
dev: true
/object-inspect/1.10.2:
resolution: {integrity: sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==}
/object-inspect/1.12.0:
resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==}
dev: true
/object-keys/1.1.1:
@@ -345,8 +382,8 @@ packages:
engines: {node: '>=4'}
dev: true
/path-parse/1.0.6:
resolution: {integrity: sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==}
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/path-type/3.0.0:
@@ -367,11 +404,6 @@ packages:
engines: {node: '>=4'}
dev: true
/pretty-bytes/5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
dev: false
/read-pkg/3.0.0:
resolution: {integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=}
engines: {node: '>=4'}
@@ -385,11 +417,13 @@ packages:
resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=}
dev: true
/resolve/1.20.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
/resolve/1.21.0:
resolution: {integrity: sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==}
hasBin: true
dependencies:
is-core-module: 2.3.0
path-parse: 1.0.6
is-core-module: 2.8.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
/semver/5.7.1:
@@ -409,15 +443,23 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/shell-quote/1.7.2:
resolution: {integrity: sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==}
/shell-quote/1.7.3:
resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
dev: true
/side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.1.1
object-inspect: 1.12.0
dev: true
/spdx-correct/3.1.1:
resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
dependencies:
spdx-expression-parse: 3.0.1
spdx-license-ids: 3.0.7
spdx-license-ids: 3.0.11
dev: true
/spdx-exceptions/2.3.0:
@@ -428,20 +470,20 @@ packages:
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
dependencies:
spdx-exceptions: 2.3.0
spdx-license-ids: 3.0.7
spdx-license-ids: 3.0.11
dev: true
/spdx-license-ids/3.0.7:
resolution: {integrity: sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==}
/spdx-license-ids/3.0.11:
resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==}
dev: true
/string.prototype.padend/3.1.2:
resolution: {integrity: sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==}
/string.prototype.padend/3.1.3:
resolution: {integrity: sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.3
es-abstract: 1.18.0
es-abstract: 1.19.1
dev: true
/string.prototype.trimend/1.0.4:
@@ -470,6 +512,11 @@ packages:
has-flag: 3.0.0
dev: true
/supports-preserve-symlinks-flag/1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
dev: true
/unbox-primitive/1.0.1:
resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==}
dependencies:
@@ -489,11 +536,11 @@ packages:
/which-boxed-primitive/1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
dependencies:
is-bigint: 1.0.1
is-boolean-object: 1.1.0
is-number-object: 1.0.4
is-string: 1.0.5
is-symbol: 1.0.3
is-bigint: 1.0.4
is-boolean-object: 1.1.2
is-number-object: 1.0.6
is-string: 1.0.7
is-symbol: 1.0.4
dev: true
/which/1.3.1:

View File

@@ -1,19 +1,15 @@
import http from 'http'
import httpProxy from 'http-proxy'
function start() {
try {
const proxy = httpProxy.createProxyServer({})
const server = http.createServer(function (req, res) {
const target = req.url.startsWith('/api/') ? 'http://localhost:5000' : 'http://localhost:3000'
proxy.web(req, res, { target })
})
server.listen(1234)
const proxy = httpProxy.createProxyServer()
proxy.on('error', function (err, req, res) {
res.writeHead(500, { 'Content-Type': 'text/plain' })
res.end('500 Internal Server Error')
})
server.on('error', () => start())
} catch (e) {
start()
}
}
start()
const server = http.createServer(function (req, res) {
const target = req.url.startsWith('/api/') ? 'http://localhost:5000' : 'http://localhost:3000'
proxy.web(req, res, { target })
})
server.listen(1234)
console.log('Proxy on http://localhost:1234')

View File

@@ -1,10 +0,0 @@
use actix_files::{Files, NamedFile};
use actix_web::{web, Responder};
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(Files::new("/", "./client/build").index_file("index.html"));
}
pub async fn fallback_fn() -> impl Responder {
NamedFile::open("./client/build/index.html")
}