Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
a0732a4593 | |||
835f7df0f6 | |||
2def365cae | |||
c8b2539414 | |||
c8a25eb9bf | |||
15bceb1715 | |||
8acc4108ae | |||
0f708f53c0 | |||
8d03ad8e15 | |||
33829768eb | |||
8cee6579e2 | |||
8eeb2a8de7 | |||
e4ce767444 | |||
00fd514da5 | |||
ba38d2b819 | |||
d0f83e6148 | |||
a040ad469e | |||
0c01866344 | |||
048c5198a2 | |||
f606916d97 | |||
aea85c3b73 | |||
5f904b3971 | |||
ac5d52a010 | |||
8644a937d0 | |||
a0ebb97bc5 | |||
19cd9b8507 | |||
fe653e91c8 | |||
a78ec72687 |
@@ -1,2 +1,2 @@
|
|||||||
target
|
/**/target
|
||||||
node_modules
|
/**/node_modules
|
||||||
|
3
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
/target
|
target
|
||||||
|
|
||||||
# Client
|
# Client
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -8,3 +8,4 @@ node_modules
|
|||||||
/.svelte
|
/.svelte
|
||||||
/build
|
/build
|
||||||
/functions
|
/functions
|
||||||
|
.env
|
||||||
|
78
CHANGELOG.md
@@ -5,114 +5,144 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
- Option to set a custom size limit.
|
||||||
|
- Options to share files.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Don't delete note if time is not expired yet
|
||||||
|
- Use pnpm instead of npm.
|
||||||
|
|
||||||
## [1.2.0] - 2021-11-11
|
## [1.2.0] - 2021-11-11
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Switch to pnpm
|
- Switch to pnpm.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Dependencies updated
|
- Dependencies updated.
|
||||||
|
|
||||||
## [1.1.1] - 2021-05-17
|
## [1.1.1] - 2021-05-17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Height on big displays
|
- Height on big displays.
|
||||||
- About page
|
- About page.
|
||||||
|
|
||||||
## [1.1.0] - 2021-05-16
|
## [1.1.0] - 2021-05-16
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Using hash `#` instead of path
|
- Using hash `#` instead of path.
|
||||||
|
|
||||||
## [1.0.11] - 2021-05-08
|
## [1.0.11] - 2021-05-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- loading text
|
- loading text.
|
||||||
- description for created notes about availability
|
- description for created notes about availability.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- iterations from 100 to 100k
|
- iterations from 100 to 100k.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- time based view bug
|
- time based view bug.
|
||||||
|
|
||||||
## [1.0.10] - 2021-05-08
|
## [1.0.10] - 2021-05-08
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- API endpoint was not reachable
|
- API endpoint was not reachable.
|
||||||
|
|
||||||
## [1.0.9] - 2021-05-07
|
## [1.0.9] - 2021-05-07
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
- Removed a dependency
|
- Removed a dependency.
|
||||||
|
|
||||||
## [1.0.8] - 2021-05-05
|
## [1.0.8] - 2021-05-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Manual theme override option
|
- Manual theme override option.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Removed Arm builds for now
|
- Removed Arm builds for now.
|
||||||
- iOS style bugs
|
- iOS style bugs.
|
||||||
|
|
||||||
## [1.0.7] - 2021-05-04
|
## [1.0.7] - 2021-05-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Arm images
|
- Arm images.
|
||||||
|
|
||||||
## [1.0.6] - 2021-05-04
|
## [1.0.6] - 2021-05-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Always use encryption with random passwords included links
|
- Always use encryption with random passwords included links.
|
||||||
|
|
||||||
## [1.0.5] - 2021-05-03
|
## [1.0.5] - 2021-05-03
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Typos
|
- Typos.
|
||||||
|
|
||||||
## [1.0.4] - 2021-05-02
|
## [1.0.4] - 2021-05-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- From scratch docker image
|
- From scratch docker image.
|
||||||
|
|
||||||
## [1.0.3] - 2021-05-02
|
## [1.0.3] - 2021-05-02
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Higher default text area
|
- Higher default text area.
|
||||||
- Mobile touchups
|
- Mobile touchups.
|
||||||
|
|
||||||
## [1.0.2] - 2021-05-02
|
## [1.0.2] - 2021-05-02
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- SVG Icons
|
- SVG Icons.
|
||||||
|
|
||||||
## [1.0.1] - 2021-05-02
|
## [1.0.1] - 2021-05-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Dark mode support
|
- Dark mode support.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Don't reload data on wrong password
|
- Don't reload data on wrong password.
|
||||||
|
|
||||||
## [1.0.0] - 2021-05-02
|
## [1.0.0] - 2021-05-02
|
||||||
|
|
||||||
Initial release
|
Initial release.
|
||||||
|
14
Dockerfile
@@ -1,26 +1,28 @@
|
|||||||
|
# Client
|
||||||
FROM node:16-alpine as CLIENT
|
FROM node:16-alpine as CLIENT
|
||||||
|
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
COPY ./client ./
|
COPY ./frontend ./
|
||||||
|
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
RUN pnpm install
|
RUN pnpm install
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|
||||||
FROM rust:1.51-alpine as RUST
|
# Rust
|
||||||
|
FROM rust:1.56-alpine as RUST
|
||||||
|
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
RUN apk add libc-dev openssl-dev alpine-sdk
|
RUN apk add libc-dev openssl-dev alpine-sdk
|
||||||
COPY ./Cargo* ./
|
COPY ./backend ./
|
||||||
COPY ./src ./src
|
|
||||||
|
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|
||||||
FROM scratch
|
# Server
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=RUST /tmp/target/release/cryptgeon .
|
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
|
ENV MEMCACHE=memcached:11211
|
||||||
|
|
||||||
|
84
README.md
@@ -1,16 +1,21 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./design/Github.png">
|
<img src="./design/Github.png" alt="logo">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||

|
<a href="https://discord.gg/nuby6RnxZt">
|
||||||

|
<img alt="discord" src="https://img.shields.io/discord/252403122348097536?style=for-the-badge" />
|
||||||

|
<img alt="docker pulls" src="https://img.shields.io/docker/pulls/cupcakearmy/cryptgeon?style=for-the-badge" />
|
||||||
|
<img alt="Docker image size badge" src="https://img.shields.io/docker/image-size/cupcakearmy/cryptgeon?style=for-the-badge" />
|
||||||
|
<img alt="Latest version" src="https://img.shields.io/github/v/release/cupcakearmy/cryptgeon?style=for-the-badge" />
|
||||||
|
</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" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
## About?
|
## About?
|
||||||
|
|
||||||
_cryptgeon_ is a secure, open source sharing note service inspired by [_PrivNote_](https://privnote.com)
|
_cryptgeon_ is a secure, open source sharing note or file service inspired by [_PrivNote_](https://privnote.com)
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
@@ -19,22 +24,36 @@ Check out the demo and see for yourself https://cryptgeon.nicco.io.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- server cannot decrypt contents due to client side encryption
|
- server cannot decrypt contents due to client side encryption
|
||||||
- view and time constraints
|
- view or time constraints
|
||||||
- in memory, no persistence
|
- in memory, no persistence
|
||||||
- obligatory dark mode support
|
- obligatory dark mode support
|
||||||
|
|
||||||
## How does it work?
|
## 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
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
| ------------ | ----------------- | --------------------------------------------------------------------------------------- |
|
||||||
|
| `MEMCACHE` | `memcached:11211` | Memcached URL to connect to. |
|
||||||
|
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/) |
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
ℹ️ `https` is required otherwise browsers will not support the cryptographic functions.
|
ℹ️ `https` is required otherwise browsers will not support the cryptographic functions.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
Docker is the easiest way. There is the [official image here](https://hub.docker.com/r/cupcakearmy/cryptgeon).
|
Docker is the easiest way. There is the [official image here](https://hub.docker.com/r/cupcakearmy/cryptgeon).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -45,21 +64,64 @@ version: '3.7'
|
|||||||
services:
|
services:
|
||||||
memcached:
|
memcached:
|
||||||
image: memcached:1-alpine
|
image: memcached:1-alpine
|
||||||
entrypoint: memcached -m 128 # Limit to 128 MB Ram, customize at free will.
|
entrypoint: memcached -m 128M -I 4M # Limit to 128 MB Ram, 4M per entry, customize at free will.
|
||||||
|
|
||||||
app:
|
app:
|
||||||
image: cupcakearmy/cryptgeon:latest
|
image: cupcakearmy/cryptgeon:latest
|
||||||
depends_on:
|
depends_on:
|
||||||
- memcached
|
- memcached
|
||||||
|
environment:
|
||||||
|
SIZE_LIMIT: 4M
|
||||||
ports:
|
ports:
|
||||||
- 80:5000
|
- 80:5000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### NGINX Proxy
|
||||||
|
|
||||||
|
See the [examples/nginx](https://github.com/cupcakearmy/cryptgeon/tree/main/examples/nginx) folder. There an example with a simple proxy, and one with https. You need to specify the server names and certificates.
|
||||||
|
|
||||||
|
### Traefik 2
|
||||||
|
|
||||||
|
Assumptions:
|
||||||
|
|
||||||
|
- External proxy docker network `proxy`
|
||||||
|
- A certificate resolver `le`
|
||||||
|
- A https entrypoint `secure`
|
||||||
|
- Domain name `example.org`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
memcached:
|
||||||
|
image: memcached:1-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
entrypoint: memcached -m 128M -I 4M # Limit to 128 MB Ram, 4M per entry, customize at free will.
|
||||||
|
|
||||||
|
app:
|
||||||
|
image: cupcakearmy/cryptgeon:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- memcached
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.cryptgeon.rule=Host(`example.org`)
|
||||||
|
- traefik.http.routers.cryptgeon.entrypoints=secure
|
||||||
|
- traefik.http.routers.cryptgeon.tls.certresolver=le
|
||||||
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
1. Clone
|
1. Clone
|
||||||
2. run `npm i` in the root and and client `client/` folders.
|
2. run `pnpm i` in the root and and client `client/` folders.
|
||||||
3. Run `npm run dev` to start development.
|
3. Run `pnpm run dev` to start development.
|
||||||
|
|
||||||
Running `npm run dev` in the root folder will start the following things
|
Running `npm run dev` in the root folder will start the following things
|
||||||
|
|
||||||
@@ -67,7 +129,7 @@ Running `npm run dev` in the root folder will start the following things
|
|||||||
- rust backend with hot reload
|
- rust backend with hot reload
|
||||||
- client with hot reload
|
- client with hot reload
|
||||||
|
|
||||||
You can see the app under [localhost:3000](http://localhost:3000).
|
You can see the app under [localhost:1234](http://localhost:1234).
|
||||||
|
|
||||||
###### Attributions
|
###### Attributions
|
||||||
|
|
||||||
|
546
Cargo.lock → backend/Cargo.lock
generated
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
version = "1.2.0"
|
version = "1.3.2"
|
||||||
authors = ["cupcakearmy <hi@nicco.io>"]
|
authors = ["cupcakearmy <hi@nicco.io>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
@@ -18,4 +18,7 @@ serde_json = "1"
|
|||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
ring = "0.16"
|
ring = "0.16"
|
||||||
bs62 = "0.1"
|
bs62 = "0.1"
|
||||||
memcache = "0.15"
|
memcache = "0.16"
|
||||||
|
byte-unit = "4"
|
||||||
|
dotenv = "0.15"
|
||||||
|
mime = "0.3"
|
@@ -2,9 +2,9 @@ use actix_files::{Files, NamedFile};
|
|||||||
use actix_web::{web, Responder};
|
use actix_web::{web, Responder};
|
||||||
|
|
||||||
pub fn init(cfg: &mut web::ServiceConfig) {
|
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(Files::new("/", "./client/build").index_file("index.html"));
|
cfg.service(Files::new("/", "./frontend/build").index_file("index.html"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fallback_fn() -> impl Responder {
|
pub async fn fallback_fn() -> impl Responder {
|
||||||
NamedFile::open("./client/build/index.html")
|
NamedFile::open("./frontend/build/index.html")
|
||||||
}
|
}
|
@@ -1,23 +1,27 @@
|
|||||||
use actix_web::{middleware, web, App, HttpServer};
|
use actix_web::{middleware, web, App, HttpServer};
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod note;
|
mod note;
|
||||||
|
mod size;
|
||||||
mod store;
|
mod store;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
HttpServer::new(|| {
|
dotenv().ok();
|
||||||
|
return HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::Compress::default())
|
.wrap(middleware::Compress::default())
|
||||||
.wrap(middleware::DefaultHeaders::default())
|
.wrap(middleware::DefaultHeaders::default())
|
||||||
|
.configure(size::init)
|
||||||
.configure(note::init)
|
.configure(note::init)
|
||||||
.configure(client::init)
|
.configure(client::init)
|
||||||
.default_service(web::resource("").route(web::get().to(client::fallback_fn)))
|
.default_service(web::resource("").route(web::get().to(client::fallback_fn)))
|
||||||
})
|
})
|
||||||
.bind("0.0.0.0:5000")?
|
.bind("0.0.0.0:5000")?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await;
|
||||||
}
|
}
|
@@ -4,9 +4,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
|
pub meta: String,
|
||||||
pub contents: String,
|
pub contents: String,
|
||||||
pub views: Option<u8>,
|
pub views: Option<u8>,
|
||||||
pub expiration: Option<u64>,
|
pub expiration: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
@@ -14,11 +15,12 @@ pub struct NoteInfo {}
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct NotePublic {
|
pub struct NotePublic {
|
||||||
|
pub meta: String,
|
||||||
pub contents: String,
|
pub contents: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_id() -> String {
|
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 = ring::rand::SystemRandom::new();
|
||||||
let _ = sr.fill(&mut id);
|
let _ = sr.fill(&mut id);
|
||||||
return bs62::encode_data(&id);
|
return bs62::encode_data(&id);
|
@@ -3,13 +3,14 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use crate::note::{generate_id, Note, NoteInfo, NotePublic};
|
use crate::note::{generate_id, Note, NoteInfo, NotePublic};
|
||||||
|
use crate::size::LIMIT;
|
||||||
use crate::store;
|
use crate::store;
|
||||||
|
|
||||||
fn now() -> u64 {
|
pub fn now() -> u32 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs()
|
.as_secs() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -17,7 +18,7 @@ struct NotePath {
|
|||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/notes/{id}")]
|
#[get("/{id}")]
|
||||||
async fn one(path: web::Path<NotePath>) -> impl Responder {
|
async fn one(path: web::Path<NotePath>) -> impl Responder {
|
||||||
let p = path.into_inner();
|
let p = path.into_inner();
|
||||||
let note = store::get(&p.id);
|
let note = store::get(&p.id);
|
||||||
@@ -32,14 +33,11 @@ struct CreateResponse {
|
|||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/notes")]
|
#[post("/")]
|
||||||
async fn create(note: web::Json<Note>) -> impl Responder {
|
async fn create(note: web::Json<Note>) -> impl Responder {
|
||||||
let mut n = note.into_inner();
|
let mut n = note.into_inner();
|
||||||
let id = generate_id();
|
let id = generate_id();
|
||||||
let bad_req = HttpResponse::BadRequest().finish();
|
let bad_req = HttpResponse::BadRequest().finish();
|
||||||
if n.contents.chars().count() > 8192 {
|
|
||||||
return bad_req;
|
|
||||||
}
|
|
||||||
if n.views == None && n.expiration == None {
|
if n.views == None && n.expiration == None {
|
||||||
return bad_req;
|
return bad_req;
|
||||||
}
|
}
|
||||||
@@ -65,7 +63,7 @@ async fn create(note: web::Json<Note>) -> impl Responder {
|
|||||||
return HttpResponse::Ok().json(CreateResponse { id: id });
|
return HttpResponse::Ok().json(CreateResponse { id: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/notes/{id}")]
|
#[delete("/{id}")]
|
||||||
async fn delete(path: web::Path<NotePath>) -> impl Responder {
|
async fn delete(path: web::Path<NotePath>) -> impl Responder {
|
||||||
let p = path.into_inner();
|
let p = path.into_inner();
|
||||||
let note = store::get(&p.id);
|
let note = store::get(&p.id);
|
||||||
@@ -88,10 +86,12 @@ async fn delete(path: web::Path<NotePath>) -> impl Responder {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let n = now();
|
||||||
match changed.expiration {
|
match changed.expiration {
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
store::del(&p.id.clone());
|
if e < n {
|
||||||
if e < now() {
|
store::del(&p.id.clone());
|
||||||
return HttpResponse::BadRequest().finish();
|
return HttpResponse::BadRequest().finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,16 +99,39 @@ async fn delete(path: web::Path<NotePath>) -> impl Responder {
|
|||||||
}
|
}
|
||||||
return HttpResponse::Ok().json(NotePublic {
|
return HttpResponse::Ok().json(NotePublic {
|
||||||
contents: changed.contents,
|
contents: changed.contents,
|
||||||
|
meta: changed.meta,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Status {
|
||||||
|
version: String,
|
||||||
|
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) {
|
pub fn init(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::scope("/api")
|
web::scope("/api")
|
||||||
.service(create)
|
.service(
|
||||||
.service(delete)
|
web::scope("/notes")
|
||||||
.service(one),
|
.service(one)
|
||||||
|
.service(create)
|
||||||
|
.service(delete)
|
||||||
|
.service(status),
|
||||||
|
)
|
||||||
|
.service(status),
|
||||||
);
|
);
|
||||||
}
|
}
|
20
backend/src/size.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use actix_web::web;
|
||||||
|
use byte_unit::Byte;
|
||||||
|
use mime;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref LIMIT: usize =
|
||||||
|
Byte::from_str(std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()))
|
||||||
|
.unwrap()
|
||||||
|
.get_bytes() as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
use memcache;
|
use memcache;
|
||||||
|
|
||||||
|
use crate::note::now;
|
||||||
use crate::note::Note;
|
use crate::note::Note;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -12,7 +13,11 @@ lazy_static! {
|
|||||||
|
|
||||||
pub fn set(id: &String, note: &Note) {
|
pub fn set(id: &String, note: &Note) {
|
||||||
let serialized = serde_json::to_string(¬e.clone()).unwrap();
|
let serialized = serde_json::to_string(¬e.clone()).unwrap();
|
||||||
CLIENT.set(id, serialized, 0).unwrap();
|
let expiration: u32 = match note.expiration {
|
||||||
|
Some(e) => e - now(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
CLIENT.set(id, serialized, expiration).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(id: &String) -> Option<Note> {
|
pub fn get(id: &String) -> Option<Note> {
|
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "svelte-kit dev",
|
|
||||||
"build": "svelte-kit build",
|
|
||||||
"preview": "svelte-kit preview",
|
|
||||||
"licenses": "npx license-checker --summary > licenses.csv"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@sveltejs/adapter-static": "next",
|
|
||||||
"@sveltejs/kit": "next",
|
|
||||||
"svelte": "^3.44.1",
|
|
||||||
"svelte-preprocess": "^4.9.8",
|
|
||||||
"tslib": "^2.3.1",
|
|
||||||
"typescript": "^4.4.4",
|
|
||||||
"vite": "^2.6.14"
|
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
|
||||||
"@fontsource/fira-mono": "^4.5.0",
|
|
||||||
"copy-to-clipboard": "^3.3.1"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,51 +0,0 @@
|
|||||||
import { dev } from '$app/env'
|
|
||||||
|
|
||||||
export type Note = {
|
|
||||||
contents: string
|
|
||||||
views?: number
|
|
||||||
expiration?: number
|
|
||||||
}
|
|
||||||
export type NoteInfo = {}
|
|
||||||
export type NotePublic = Pick<Note, 'contents'>
|
|
||||||
|
|
||||||
type CallOptions = {
|
|
||||||
url: string
|
|
||||||
method: string
|
|
||||||
body?: any
|
|
||||||
}
|
|
||||||
const base = dev ? 'http://localhost:5000/api/' : '/api/'
|
|
||||||
async function call(options: CallOptions) {
|
|
||||||
return fetch(base + options.url, {
|
|
||||||
method: options.method,
|
|
||||||
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
||||||
mode: 'cors',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
}).then((r) => r.json())
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function create(note: Note) {
|
|
||||||
const data = await call({
|
|
||||||
url: 'notes',
|
|
||||||
method: 'post',
|
|
||||||
body: note,
|
|
||||||
})
|
|
||||||
return data as { id: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get(id: string) {
|
|
||||||
const data = await call({
|
|
||||||
url: `notes/${id}`,
|
|
||||||
method: 'delete',
|
|
||||||
})
|
|
||||||
return data as NotePublic
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function info(id: string) {
|
|
||||||
const data = await call({
|
|
||||||
url: `notes/${id}`,
|
|
||||||
method: 'get',
|
|
||||||
})
|
|
||||||
return data as NoteInfo
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let label: string = ''
|
|
||||||
export let value: string
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<small>
|
|
||||||
{label}
|
|
||||||
</small>
|
|
||||||
<textarea {...$$restProps} bind:value />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
min-height: min(calc(100vh - 30rem), 30rem);
|
|
||||||
margin: 0;
|
|
||||||
border: 2px solid var(--ui-bg-1);
|
|
||||||
resize: vertical;
|
|
||||||
outline: none;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 30rem) {
|
|
||||||
textarea {
|
|
||||||
min-height: calc(100vh - 25rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea:hover,
|
|
||||||
textarea:focus {
|
|
||||||
border-color: var(--ui-clr-primary);
|
|
||||||
}
|
|
||||||
</style>
|
|
@@ -1,68 +0,0 @@
|
|||||||
<script context="module">
|
|
||||||
import { browser, dev } from '$app/env'
|
|
||||||
export const hydrate = dev
|
|
||||||
export const router = browser
|
|
||||||
export const prerender = true
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>About</title>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<section class="content">
|
|
||||||
<h1>About</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<i>cryptgeon</i> is a secure, open source sharing note service inspired by
|
|
||||||
<a href="https://privnote.com"><i>PrivNote</i></a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<b>▶ how does it work?</b>
|
|
||||||
<br />
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<b>▶ features</b>
|
|
||||||
<ul>
|
|
||||||
<li>server cannot decrypt contents due to client side encryption</li>
|
|
||||||
<li>view and time constraints</li>
|
|
||||||
<li>in memory, no persistence</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<b>▶ tech stack</b>
|
|
||||||
<br />
|
|
||||||
the backend is written in rust and the frontend is svelte and typescript.
|
|
||||||
<br />
|
|
||||||
you are welcomed to check & audit the
|
|
||||||
<a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener">source code</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<br />
|
|
||||||
<b>▶ attributions</b>
|
|
||||||
<br />
|
|
||||||
<small>
|
|
||||||
icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from
|
|
||||||
<a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
padding-left: 1rem;
|
|
||||||
list-style: square;
|
|
||||||
}
|
|
||||||
</style>
|
|
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"fixturesFolder": false,
|
|
||||||
"pluginsFile": false,
|
|
||||||
"supportFile": false
|
|
||||||
}
|
|
@@ -6,7 +6,8 @@ version: '3.7'
|
|||||||
services:
|
services:
|
||||||
memcached:
|
memcached:
|
||||||
image: memcached:1-alpine
|
image: memcached:1-alpine
|
||||||
entrypoint: memcached -m 128
|
restart: unless-stopped
|
||||||
|
entrypoint: memcached -m 128M -I 4M
|
||||||
ports:
|
ports:
|
||||||
- 11211:11211
|
- 11211:11211
|
||||||
|
|
||||||
@@ -14,5 +15,7 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
depends_on:
|
depends_on:
|
||||||
- memcached
|
- memcached
|
||||||
|
environment:
|
||||||
|
SIZE_LIMIT: 4M
|
||||||
ports:
|
ports:
|
||||||
- 5000:5000
|
- 80:5000
|
||||||
|
22
examples/nginx/docker-compose.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
memcached:
|
||||||
|
image: memcached:1-alpine
|
||||||
|
entrypoint: memcached -m 128 # Limit to 128 MB Ram, customize at free will.
|
||||||
|
|
||||||
|
app:
|
||||||
|
image: cupcakearmy/cryptgeon:latest
|
||||||
|
depends_on:
|
||||||
|
- memcached
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
image: nginx:alpine
|
||||||
|
depends_on:
|
||||||
|
- app
|
||||||
|
volumes:
|
||||||
|
- ./nginx-plain.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
# Or with tls
|
||||||
|
# - ./nginx-tls.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
ports:
|
||||||
|
- 80:80
|
13
examples/nginx/nginx-plain.conf
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://app:5000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
29
examples/nginx/nginx-tls.conf
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# You should change the server_name to something sensible.
|
||||||
|
# Also you need to specify the path to the ssl certificates.
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Enforce HTTPS
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_certificate /path/to/fullchain.pem;
|
||||||
|
ssl_certificate_key /path/to/privkey.pem;
|
||||||
|
ssl_trusted_certificate /path/to/fullchain.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://app:5000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
0
client/.gitignore → frontend/.gitignore
vendored
@@ -1,4 +1,4 @@
|
|||||||
├─ MIT: 43
|
├─ MIT: 46
|
||||||
├─ MIT*: 2
|
├─ MIT*: 2
|
||||||
├─ BSD-3-Clause: 2
|
├─ BSD-3-Clause: 2
|
||||||
├─ ISC: 1
|
├─ ISC: 1
|
|
25
frontend/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"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.24",
|
||||||
|
"@sveltejs/kit": "^1.0.0-next.212",
|
||||||
|
"svelte": "^3.44.3",
|
||||||
|
"svelte-preprocess": "^4.10.1",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
|
"vite": "^2.7.10"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/fira-mono": "^4.5.0",
|
||||||
|
"copy-to-clipboard": "^3.3.1",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"pretty-bytes": "^5.6.0"
|
||||||
|
}
|
||||||
|
}
|
284
client/pnpm-lock.yaml → frontend/pnpm-lock.yaml
generated
@@ -2,27 +2,31 @@ lockfileVersion: 5.3
|
|||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@fontsource/fira-mono': ^4.5.0
|
'@fontsource/fira-mono': ^4.5.0
|
||||||
'@sveltejs/adapter-static': next
|
'@sveltejs/adapter-static': ^1.0.0-next.24
|
||||||
'@sveltejs/kit': next
|
'@sveltejs/kit': ^1.0.0-next.212
|
||||||
copy-to-clipboard: ^3.3.1
|
copy-to-clipboard: ^3.3.1
|
||||||
svelte: ^3.44.1
|
file-saver: ^2.0.5
|
||||||
svelte-preprocess: ^4.9.8
|
pretty-bytes: ^5.6.0
|
||||||
|
svelte: ^3.44.3
|
||||||
|
svelte-preprocess: ^4.10.1
|
||||||
tslib: ^2.3.1
|
tslib: ^2.3.1
|
||||||
typescript: ^4.4.4
|
typescript: ^4.5.4
|
||||||
vite: ^2.6.14
|
vite: ^2.7.10
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fontsource/fira-mono': 4.5.0
|
'@fontsource/fira-mono': 4.5.0
|
||||||
copy-to-clipboard: 3.3.1
|
copy-to-clipboard: 3.3.1
|
||||||
|
file-saver: 2.0.5
|
||||||
|
pretty-bytes: 5.6.0
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@sveltejs/adapter-static': 1.0.0-next.21
|
'@sveltejs/adapter-static': 1.0.0-next.24
|
||||||
'@sveltejs/kit': 1.0.0-next.195_svelte@3.44.1
|
'@sveltejs/kit': 1.0.0-next.212_svelte@3.44.3
|
||||||
svelte: 3.44.1
|
svelte: 3.44.3
|
||||||
svelte-preprocess: 4.9.8_svelte@3.44.1+typescript@4.4.4
|
svelte-preprocess: 4.10.1_svelte@3.44.3+typescript@4.5.4
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.4.4
|
typescript: 4.5.4
|
||||||
vite: 2.6.14
|
vite: 2.7.10
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -30,30 +34,31 @@ packages:
|
|||||||
resolution: {integrity: sha512-KE+d3wmgq/YKM0BqgUF7p2yeBNi805Nfof1lC1wJ7E9i2EWoC363sGdKG+MQBVm+ei3GYZu+Bo8Xha1w1pkB7g==}
|
resolution: {integrity: sha512-KE+d3wmgq/YKM0BqgUF7p2yeBNi805Nfof1lC1wJ7E9i2EWoC363sGdKG+MQBVm+ei3GYZu+Bo8Xha1w1pkB7g==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@rollup/pluginutils/4.1.1:
|
/@rollup/pluginutils/4.1.2:
|
||||||
resolution: {integrity: sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==}
|
resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==}
|
||||||
engines: {node: '>= 8.0.0'}
|
engines: {node: '>= 8.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
picomatch: 2.3.0
|
picomatch: 2.3.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/adapter-static/1.0.0-next.21:
|
/@sveltejs/adapter-static/1.0.0-next.24:
|
||||||
resolution: {integrity: sha512-B4+QoUVAaANKx+mHntG8SqF45zbj3Ct4Akg/cGauo6COyfKZRhO5OsMa+wPuT2TKJBZC4eEDK0p+p9nyQBkxKQ==}
|
resolution: {integrity: sha512-lMiwZrZumWRrTQxaj9pFs5oW0h/97spyDl1QjmnkNaA006WeqornoETt31WpU0Lz2/2uYNXvUBBcL1LGc9Vylg==}
|
||||||
|
dependencies:
|
||||||
|
tiny-glob: 0.2.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/kit/1.0.0-next.195_svelte@3.44.1:
|
/@sveltejs/kit/1.0.0-next.212_svelte@3.44.3:
|
||||||
resolution: {integrity: sha512-R2X4FgzXQhp63XOik6S1Flw91S2CEA7sTxdsnNFrq3O+bIN7pQhJhkm6zgH68MZANdDcq8oIiSRkxT4M3t1+jQ==}
|
resolution: {integrity: sha512-hjpk/Rqrl6hhNf1Qsx6EDvL3Cm9JvmvW/Z1FRYVhGg1xin/JQkPgFzTU27NBSYhtK1t4buLmlO1tqdMvurs9Fg==}
|
||||||
engines: {node: '>=14.13'}
|
engines: {node: '>=14.13'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: ^3.44.0
|
svelte: ^3.44.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/vite-plugin-svelte': 1.0.0-next.30_svelte@3.44.1+vite@2.6.14
|
'@sveltejs/vite-plugin-svelte': 1.0.0-next.33_svelte@3.44.3+vite@2.7.10
|
||||||
cheap-watch: 1.0.4
|
sade: 1.8.0
|
||||||
sade: 1.7.4
|
svelte: 3.44.3
|
||||||
svelte: 3.44.1
|
vite: 2.7.10
|
||||||
vite: 2.6.14
|
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- diff-match-patch
|
- diff-match-patch
|
||||||
- less
|
- less
|
||||||
@@ -62,41 +67,41 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/vite-plugin-svelte/1.0.0-next.30_svelte@3.44.1+vite@2.6.14:
|
/@sveltejs/vite-plugin-svelte/1.0.0-next.33_svelte@3.44.3+vite@2.7.10:
|
||||||
resolution: {integrity: sha512-YQqdMxjL1VgSFk4/+IY3yLwuRRapPafPiZTiaGEq1psbJYSNYUWx9F1zMm32GMsnogg3zn99mGJOqe3ld3HZSg==}
|
resolution: {integrity: sha512-aj0h2+ZixgT+yoJFIs8dRRw/Cj9tgNu3+hY4CJikpa04mfhR61wXqJFfi2ZEFMUvFda5nCxKYIChFkc6wq5fJA==}
|
||||||
engines: {node: ^14.13.1 || >= 16}
|
engines: {node: ^14.13.1 || >= 16}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
diff-match-patch: ^1.0.5
|
diff-match-patch: ^1.0.5
|
||||||
svelte: ^3.44.0
|
svelte: ^3.44.0
|
||||||
vite: ^2.6.0
|
vite: ^2.7.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
diff-match-patch:
|
diff-match-patch:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 4.1.1
|
'@rollup/pluginutils': 4.1.2
|
||||||
debug: 4.3.2
|
debug: 4.3.3
|
||||||
kleur: 4.1.4
|
kleur: 4.1.4
|
||||||
magic-string: 0.25.7
|
magic-string: 0.25.7
|
||||||
require-relative: 0.8.7
|
require-relative: 0.8.7
|
||||||
svelte: 3.44.1
|
svelte: 3.44.3
|
||||||
svelte-hmr: 0.14.7_svelte@3.44.1
|
svelte-hmr: 0.14.9_svelte@3.44.3
|
||||||
vite: 2.6.14
|
vite: 2.7.10
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/node/16.11.7:
|
/@types/node/17.0.6:
|
||||||
resolution: {integrity: sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==}
|
resolution: {integrity: sha512-+XBAjfZmmivILUzO0HwBJoYkAyyySSLg5KCGBDFLomJo0sV6szvVLAf4ANZZ0pfWzgEds5KmGLG9D5hfEqOhaA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/pug/2.0.5:
|
/@types/pug/2.0.6:
|
||||||
resolution: {integrity: sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==}
|
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/sass/1.43.0:
|
/@types/sass/1.43.1:
|
||||||
resolution: {integrity: sha512-DPSXNJ1rYLo88GyF9tuB4bsYGfpKI1a4+wOQmc+LI1SUoocm9QLRSpz0GxxuyjmJsYFIQo/dDlRSSpIXngff+w==}
|
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 16.11.7
|
'@types/node': 17.0.6
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/balanced-match/1.0.2:
|
/balanced-match/1.0.2:
|
||||||
@@ -114,11 +119,6 @@ packages:
|
|||||||
resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
|
resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/cheap-watch/1.0.4:
|
|
||||||
resolution: {integrity: sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/concat-map/0.0.1:
|
/concat-map/0.0.1:
|
||||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -129,8 +129,8 @@ packages:
|
|||||||
toggle-selection: 1.0.6
|
toggle-selection: 1.0.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/debug/4.3.2:
|
/debug/4.3.3:
|
||||||
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
|
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
supports-color: '*'
|
supports-color: '*'
|
||||||
@@ -150,170 +150,174 @@ packages:
|
|||||||
resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
|
resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/esbuild-android-arm64/0.13.13:
|
/esbuild-android-arm64/0.13.15:
|
||||||
resolution: {integrity: sha512-T02aneWWguJrF082jZworjU6vm8f4UQ+IH2K3HREtlqoY9voiJUwHLRL6khRlsNLzVglqgqb7a3HfGx7hAADCQ==}
|
resolution: {integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-darwin-64/0.13.13:
|
/esbuild-darwin-64/0.13.15:
|
||||||
resolution: {integrity: sha512-wkaiGAsN/09X9kDlkxFfbbIgR78SNjMOfUhoel3CqKBDsi9uZhw7HBNHNxTzYUK8X8LAKFpbODgcRB3b/I8gHA==}
|
resolution: {integrity: sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-darwin-arm64/0.13.13:
|
/esbuild-darwin-arm64/0.13.15:
|
||||||
resolution: {integrity: sha512-b02/nNKGSV85Gw9pUCI5B48AYjk0vFggDeom0S6QMP/cEDtjSh1WVfoIFNAaLA0MHWfue8KBwoGVsN7rBshs4g==}
|
resolution: {integrity: sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-freebsd-64/0.13.13:
|
/esbuild-freebsd-64/0.13.15:
|
||||||
resolution: {integrity: sha512-ALgXYNYDzk9YPVk80A+G4vz2D22Gv4j4y25exDBGgqTcwrVQP8rf/rjwUjHoh9apP76oLbUZTmUmvCMuTI1V9A==}
|
resolution: {integrity: sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-freebsd-arm64/0.13.13:
|
/esbuild-freebsd-arm64/0.13.15:
|
||||||
resolution: {integrity: sha512-uFvkCpsZ1yqWQuonw5T1WZ4j59xP/PCvtu6I4pbLejhNo4nwjW6YalqnBvBSORq5/Ifo9S/wsIlVHzkzEwdtlw==}
|
resolution: {integrity: sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-linux-32/0.13.13:
|
/esbuild-linux-32/0.13.15:
|
||||||
resolution: {integrity: sha512-yxR9BBwEPs9acVEwTrEE2JJNHYVuPQC9YGjRfbNqtyfK/vVBQYuw8JaeRFAvFs3pVJdQD0C2BNP4q9d62SCP4w==}
|
resolution: {integrity: sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-linux-64/0.13.13:
|
/esbuild-linux-64/0.13.15:
|
||||||
resolution: {integrity: sha512-kzhjlrlJ+6ESRB/n12WTGll94+y+HFeyoWsOrLo/Si0s0f+Vip4b8vlnG0GSiS6JTsWYAtGHReGczFOaETlKIw==}
|
resolution: {integrity: sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-linux-arm/0.13.13:
|
/esbuild-linux-arm/0.13.15:
|
||||||
resolution: {integrity: sha512-hXub4pcEds+U1TfvLp1maJ+GHRw7oizvzbGRdUvVDwtITtjq8qpHV5Q5hWNNn6Q+b3b2UxF03JcgnpzCw96nUQ==}
|
resolution: {integrity: sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-linux-arm64/0.13.13:
|
/esbuild-linux-arm64/0.13.15:
|
||||||
resolution: {integrity: sha512-KMrEfnVbmmJxT3vfTnPv/AiXpBFbbyExH13BsUGy1HZRPFMi5Gev5gk8kJIZCQSRfNR17aqq8sO5Crm2KpZkng==}
|
resolution: {integrity: sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-linux-mips64le/0.13.13:
|
/esbuild-linux-mips64le/0.13.15:
|
||||||
resolution: {integrity: sha512-cJT9O1LYljqnnqlHaS0hdG73t7hHzF3zcN0BPsjvBq+5Ad47VJun+/IG4inPhk8ta0aEDK6LdP+F9299xa483w==}
|
resolution: {integrity: sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==}
|
||||||
cpu: [mips64el]
|
cpu: [mips64el]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-linux-ppc64le/0.13.13:
|
/esbuild-linux-ppc64le/0.13.15:
|
||||||
resolution: {integrity: sha512-+rghW8st6/7O6QJqAjVK3eXzKkZqYAw6LgHv7yTMiJ6ASnNvghSeOcIvXFep3W2oaJc35SgSPf21Ugh0o777qQ==}
|
resolution: {integrity: sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-netbsd-64/0.13.13:
|
/esbuild-netbsd-64/0.13.15:
|
||||||
resolution: {integrity: sha512-A/B7rwmzPdzF8c3mht5TukbnNwY5qMJqes09ou0RSzA5/jm7Jwl/8z853ofujTFOLhkNHUf002EAgokzSgEMpQ==}
|
resolution: {integrity: sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [netbsd]
|
os: [netbsd]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-openbsd-64/0.13.13:
|
/esbuild-openbsd-64/0.13.15:
|
||||||
resolution: {integrity: sha512-szwtuRA4rXKT3BbwoGpsff6G7nGxdKgUbW9LQo6nm0TVCCjDNDC/LXxT994duIW8Tyq04xZzzZSW7x7ttDiw1w==}
|
resolution: {integrity: sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [openbsd]
|
os: [openbsd]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-sunos-64/0.13.13:
|
/esbuild-sunos-64/0.13.15:
|
||||||
resolution: {integrity: sha512-ihyds9O48tVOYF48iaHYUK/boU5zRaLOXFS+OOL3ceD39AyHo46HVmsJLc7A2ez0AxNZCxuhu+P9OxfPfycTYQ==}
|
resolution: {integrity: sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [sunos]
|
os: [sunos]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-windows-32/0.13.13:
|
/esbuild-windows-32/0.13.15:
|
||||||
resolution: {integrity: sha512-h2RTYwpG4ldGVJlbmORObmilzL8EECy8BFiF8trWE1ZPHLpECE9//J3Bi+W3eDUuv/TqUbiNpGrq4t/odbayUw==}
|
resolution: {integrity: sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-windows-64/0.13.13:
|
/esbuild-windows-64/0.13.15:
|
||||||
resolution: {integrity: sha512-oMrgjP4CjONvDHe7IZXHrMk3wX5Lof/IwFEIbwbhgbXGBaN2dke9PkViTiXC3zGJSGpMvATXVplEhlInJ0drHA==}
|
resolution: {integrity: sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild-windows-arm64/0.13.13:
|
/esbuild-windows-arm64/0.13.15:
|
||||||
resolution: {integrity: sha512-6fsDfTuTvltYB5k+QPah/x7LrI2+OLAJLE3bWLDiZI6E8wXMQU+wLqtEO/U/RvJgVY1loPs5eMpUBpVajczh1A==}
|
resolution: {integrity: sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/esbuild/0.13.13:
|
/esbuild/0.13.15:
|
||||||
resolution: {integrity: sha512-Z17A/R6D0b4s3MousytQ/5i7mTCbaF+Ua/yPfoe71vdTv4KBvVAvQ/6ytMngM2DwGJosl8WxaD75NOQl2QF26Q==}
|
resolution: {integrity: sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
esbuild-android-arm64: 0.13.13
|
esbuild-android-arm64: 0.13.15
|
||||||
esbuild-darwin-64: 0.13.13
|
esbuild-darwin-64: 0.13.15
|
||||||
esbuild-darwin-arm64: 0.13.13
|
esbuild-darwin-arm64: 0.13.15
|
||||||
esbuild-freebsd-64: 0.13.13
|
esbuild-freebsd-64: 0.13.15
|
||||||
esbuild-freebsd-arm64: 0.13.13
|
esbuild-freebsd-arm64: 0.13.15
|
||||||
esbuild-linux-32: 0.13.13
|
esbuild-linux-32: 0.13.15
|
||||||
esbuild-linux-64: 0.13.13
|
esbuild-linux-64: 0.13.15
|
||||||
esbuild-linux-arm: 0.13.13
|
esbuild-linux-arm: 0.13.15
|
||||||
esbuild-linux-arm64: 0.13.13
|
esbuild-linux-arm64: 0.13.15
|
||||||
esbuild-linux-mips64le: 0.13.13
|
esbuild-linux-mips64le: 0.13.15
|
||||||
esbuild-linux-ppc64le: 0.13.13
|
esbuild-linux-ppc64le: 0.13.15
|
||||||
esbuild-netbsd-64: 0.13.13
|
esbuild-netbsd-64: 0.13.15
|
||||||
esbuild-openbsd-64: 0.13.13
|
esbuild-openbsd-64: 0.13.15
|
||||||
esbuild-sunos-64: 0.13.13
|
esbuild-sunos-64: 0.13.15
|
||||||
esbuild-windows-32: 0.13.13
|
esbuild-windows-32: 0.13.15
|
||||||
esbuild-windows-64: 0.13.13
|
esbuild-windows-64: 0.13.15
|
||||||
esbuild-windows-arm64: 0.13.13
|
esbuild-windows-arm64: 0.13.15
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/estree-walker/2.0.2:
|
/estree-walker/2.0.2:
|
||||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/file-saver/2.0.5:
|
||||||
|
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fs.realpath/1.0.0:
|
/fs.realpath/1.0.0:
|
||||||
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
|
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -341,6 +345,14 @@ packages:
|
|||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/globalyzer/0.1.0:
|
||||||
|
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/globrex/0.1.2:
|
||||||
|
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/graceful-fs/4.2.8:
|
/graceful-fs/4.2.8:
|
||||||
resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}
|
resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -436,20 +448,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/picomatch/2.3.0:
|
/picomatch/2.3.1:
|
||||||
resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
|
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/postcss/8.3.11:
|
/postcss/8.4.5:
|
||||||
resolution: {integrity: sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA==}
|
resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.1.30
|
nanoid: 3.1.30
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
source-map-js: 0.6.2
|
source-map-js: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/pretty-bytes/5.6.0:
|
||||||
|
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/require-relative/0.8.7:
|
/require-relative/0.8.7:
|
||||||
resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}
|
resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -468,17 +485,17 @@ packages:
|
|||||||
glob: 7.2.0
|
glob: 7.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rollup/2.59.0:
|
/rollup/2.62.0:
|
||||||
resolution: {integrity: sha512-l7s90JQhCQ6JyZjKgo7Lq1dKh2RxatOM+Jr6a9F7WbS9WgKbocyUSeLmZl8evAse7y96Ae98L2k1cBOwWD8nHw==}
|
resolution: {integrity: sha512-cJEQq2gwB0GWMD3rYImefQTSjrPYaC6s4J9pYqnstVLJ1CHa/aZNVkD4Epuvg4iLeMA4KRiq7UM7awKK6j7jcw==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/sade/1.7.4:
|
/sade/1.8.0:
|
||||||
resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==}
|
resolution: {integrity: sha512-NRfCA8AVYuAA7Hu8bs18od6J4BdcXXwOv6OJuNgwbw8LcLK8JKwaM3WckLZ+MGyPJUS/ivVgK3twltrOIJJnug==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>=6'}
|
||||||
dependencies:
|
dependencies:
|
||||||
mri: 1.2.0
|
mri: 1.2.0
|
||||||
dev: true
|
dev: true
|
||||||
@@ -502,8 +519,8 @@ packages:
|
|||||||
sourcemap-codec: 1.4.8
|
sourcemap-codec: 1.4.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/source-map-js/0.6.2:
|
/source-map-js/1.0.1:
|
||||||
resolution: {integrity: sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==}
|
resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -518,16 +535,16 @@ packages:
|
|||||||
min-indent: 1.0.1
|
min-indent: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/svelte-hmr/0.14.7_svelte@3.44.1:
|
/svelte-hmr/0.14.9_svelte@3.44.3:
|
||||||
resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}
|
resolution: {integrity: sha512-bKE9+4qb4sAnA+TKHiYurUl970rjA0XmlP9TEP7K/ncyWz3m81kA4HOgmlZK/7irGK7gzZlaPDI3cmf8fp/+tg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: '>=3.19.0'
|
svelte: '>=3.19.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
svelte: 3.44.1
|
svelte: 3.44.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/svelte-preprocess/4.9.8_svelte@3.44.1+typescript@4.4.4:
|
/svelte-preprocess/4.10.1_svelte@3.44.3+typescript@4.5.4:
|
||||||
resolution: {integrity: sha512-EQS/oRZzMtYdAprppZxY3HcysKh11w54MgA63ybtL+TAZ4hVqYOnhw41JVJjWN9dhPnNjjLzvbZ2tMhTsla1Og==}
|
resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==}
|
||||||
engines: {node: '>= 9.11.2'}
|
engines: {node: '>= 9.11.2'}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -542,7 +559,7 @@ packages:
|
|||||||
stylus: ^0.54.7
|
stylus: ^0.54.7
|
||||||
sugarss: ^2.0.0
|
sugarss: ^2.0.0
|
||||||
svelte: ^3.23.0
|
svelte: ^3.23.0
|
||||||
typescript: ^3.9.5 || ^4.0.0
|
typescript: ^4.5.2
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@babel/core':
|
'@babel/core':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -567,21 +584,28 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/pug': 2.0.5
|
'@types/pug': 2.0.6
|
||||||
'@types/sass': 1.43.0
|
'@types/sass': 1.43.1
|
||||||
detect-indent: 6.1.0
|
detect-indent: 6.1.0
|
||||||
magic-string: 0.25.7
|
magic-string: 0.25.7
|
||||||
sorcery: 0.10.0
|
sorcery: 0.10.0
|
||||||
strip-indent: 3.0.0
|
strip-indent: 3.0.0
|
||||||
svelte: 3.44.1
|
svelte: 3.44.3
|
||||||
typescript: 4.4.4
|
typescript: 4.5.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/svelte/3.44.1:
|
/svelte/3.44.3:
|
||||||
resolution: {integrity: sha512-4DrCEJoBvdR689efHNSxIQn2pnFwB7E7j2yLEJtHE/P8hxwZWIphCtJ8are7bjl/iVMlcEf5uh5pJ68IwR09vQ==}
|
resolution: {integrity: sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tiny-glob/0.2.9:
|
||||||
|
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
|
||||||
|
dependencies:
|
||||||
|
globalyzer: 0.1.0
|
||||||
|
globrex: 0.1.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/toggle-selection/1.0.6:
|
/toggle-selection/1.0.6:
|
||||||
resolution: {integrity: sha1-bkWxJj8gF/oKzH2J14sVuL932jI=}
|
resolution: {integrity: sha1-bkWxJj8gF/oKzH2J14sVuL932jI=}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -590,14 +614,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
|
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/typescript/4.4.4:
|
/typescript/4.5.4:
|
||||||
resolution: {integrity: sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==}
|
resolution: {integrity: sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==}
|
||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite/2.6.14:
|
/vite/2.7.10:
|
||||||
resolution: {integrity: sha512-2HA9xGyi+EhY2MXo0+A2dRsqsAG3eFNEVIo12olkWhOmc8LfiM+eMdrXf+Ruje9gdXgvSqjLI9freec1RUM5EA==}
|
resolution: {integrity: sha512-KEY96ntXUid1/xJihJbgmLZx7QSC2D4Tui0FdS0Old5OokYzFclcofhtxtjDdGOk/fFpPbHv9yw88+rB93Tb8w==}
|
||||||
engines: {node: '>=12.2.0'}
|
engines: {node: '>=12.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -612,10 +636,10 @@ packages:
|
|||||||
stylus:
|
stylus:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.13.13
|
esbuild: 0.13.15
|
||||||
postcss: 8.3.11
|
postcss: 8.4.5
|
||||||
resolve: 1.20.0
|
resolve: 1.20.0
|
||||||
rollup: 2.59.0
|
rollup: 2.62.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
@@ -97,3 +97,28 @@ fieldset {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 100%;
|
||||||
|
min-height: min(calc(100vh - 30rem), 20rem);
|
||||||
|
margin: 0;
|
||||||
|
border: 2px solid var(--ui-bg-1);
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 30rem) {
|
||||||
|
.box {
|
||||||
|
min-height: calc(100vh - 25rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.box:hover,
|
||||||
|
.box:focus {
|
||||||
|
border-color: var(--ui-clr-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr {
|
||||||
|
text-align: right;
|
||||||
|
}
|
74
frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
export type NoteMeta = { type: 'text' | 'file' }
|
||||||
|
|
||||||
|
export type Note = {
|
||||||
|
contents: string
|
||||||
|
meta: NoteMeta
|
||||||
|
views?: number
|
||||||
|
expiration?: number
|
||||||
|
}
|
||||||
|
export type NoteInfo = {}
|
||||||
|
export type NotePublic = Pick<Note, 'contents' | 'meta'>
|
||||||
|
export type NoteCreate = Omit<Note, 'meta'> & { meta: string }
|
||||||
|
|
||||||
|
export type FileDTO = Pick<File, 'name' | 'size' | 'type'> & {
|
||||||
|
contents: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallOptions = {
|
||||||
|
url: string
|
||||||
|
method: string
|
||||||
|
body?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PayloadToLargeError extends Error {}
|
||||||
|
|
||||||
|
export async function call(options: CallOptions) {
|
||||||
|
const response = await fetch('/api/' + options.url, {
|
||||||
|
method: options.method,
|
||||||
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
||||||
|
mode: 'cors',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 413) throw new PayloadToLargeError()
|
||||||
|
else throw new Error('API call failed')
|
||||||
|
}
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(note: Note) {
|
||||||
|
const { meta, ...rest } = note
|
||||||
|
const body: NoteCreate = {
|
||||||
|
...rest,
|
||||||
|
meta: JSON.stringify(meta),
|
||||||
|
}
|
||||||
|
const data = await call({
|
||||||
|
url: 'notes/',
|
||||||
|
method: 'post',
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
return data as { id: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function get(id: string): Promise<NotePublic> {
|
||||||
|
const data = await call({
|
||||||
|
url: `notes/${id}`,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
const { contents, meta } = data
|
||||||
|
return {
|
||||||
|
contents,
|
||||||
|
meta: JSON.parse(meta) as NoteMeta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function info(id: string): Promise<NoteInfo> {
|
||||||
|
const data = await call({
|
||||||
|
url: `notes/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
13
frontend/src/lib/files.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export class Files {
|
||||||
|
static toString(f: File | Blob): Promise<string> {
|
||||||
|
const reader = new window.FileReader()
|
||||||
|
reader.readAsDataURL(f)
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
reader.onloadend = () => resolve(reader.result as string)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromString(s: string): Promise<Blob> {
|
||||||
|
return fetch(s).then((r) => r.blob())
|
||||||
|
}
|
||||||
|
}
|
17
frontend/src/lib/stores/status.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { call } from '$lib/api'
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
|
export type Status = {
|
||||||
|
version: string
|
||||||
|
max_size: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const status = writable<null | Status>(null)
|
||||||
|
|
||||||
|
export async function init() {
|
||||||
|
const data = await call({
|
||||||
|
url: 'status',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
status.set(data)
|
||||||
|
}
|
19
frontend/src/lib/ui/AboutParagraph.svelte
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let title: string
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>▶ {title}</b>
|
||||||
|
<slot />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
b {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p > :global(span) {
|
||||||
|
padding-left: 1.25em;
|
||||||
|
}
|
||||||
|
</style>
|
72
frontend/src/lib/ui/FileUpload.svelte
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { FileDTO } from '$lib/api'
|
||||||
|
import { Files } from '$lib/files'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import MaxSize from './MaxSize.svelte'
|
||||||
|
|
||||||
|
export let label: string = ''
|
||||||
|
let files: File[] = []
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{ file: string }>()
|
||||||
|
|
||||||
|
async function onInput(e: Event) {
|
||||||
|
const input = e.target as HTMLInputElement
|
||||||
|
if (input.files.length) {
|
||||||
|
files = Array.from(input.files)
|
||||||
|
const data: FileDTO[] = await Promise.all(
|
||||||
|
files.map(async (file) => ({
|
||||||
|
name: file.name,
|
||||||
|
type: file.type,
|
||||||
|
size: file.size,
|
||||||
|
contents: await Files.toString(file),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
console.debug(
|
||||||
|
'files',
|
||||||
|
data.map((d) => d.contents.length)
|
||||||
|
)
|
||||||
|
dispatch('file', JSON.stringify(data))
|
||||||
|
} else {
|
||||||
|
dispatch('file', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<small>
|
||||||
|
{label}
|
||||||
|
</small>
|
||||||
|
<input type="file" on:change={onInput} multiple />
|
||||||
|
<div class="box">
|
||||||
|
{#if files.length}
|
||||||
|
<div>
|
||||||
|
<b>Selected Files</b>
|
||||||
|
{#each files as file}
|
||||||
|
<div class="file">
|
||||||
|
{file.name}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div>
|
||||||
|
<b>No Files Selected</b>
|
||||||
|
<br />
|
||||||
|
<small>max: <MaxSize /></small>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
12
frontend/src/lib/ui/MaxSize.svelte
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { status } from '$lib/stores/status'
|
||||||
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{#if $status !== null}
|
||||||
|
{prettyBytes($status.max_size, { binary: true })}
|
||||||
|
{:else}
|
||||||
|
loading...
|
||||||
|
{/if}
|
||||||
|
</span>
|
69
frontend/src/lib/ui/ShowNote.svelte
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { FileDTO, NotePublic } from '$lib/api'
|
||||||
|
import { Files } from '$lib/files'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
import { saveAs } from 'file-saver'
|
||||||
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
import Button from './Button.svelte'
|
||||||
|
|
||||||
|
export let note: NotePublic
|
||||||
|
|
||||||
|
let files: FileDTO[] = []
|
||||||
|
|
||||||
|
$: if (note.meta.type === 'file') {
|
||||||
|
files = JSON.parse(note.contents) as FileDTO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
$: download = () => {
|
||||||
|
for (const file of files) {
|
||||||
|
downloadFile(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadFile(file: FileDTO) {
|
||||||
|
const f = new File([await Files.fromString(file.contents)], file.name, {
|
||||||
|
type: file.type,
|
||||||
|
})
|
||||||
|
saveAs(f)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p class="error-text">you will <b>not</b> get the chance to see the note again.</p>
|
||||||
|
{#if note.meta.type === 'text'}
|
||||||
|
<div class="note" data-testid="note-result">
|
||||||
|
{note.contents}
|
||||||
|
</div>
|
||||||
|
<Button on:click={() => copy(note.contents)}>copy to clipboard</Button>
|
||||||
|
{:else}
|
||||||
|
{#each files as file}
|
||||||
|
<div class="note file" data-testid="note-result">
|
||||||
|
<b on:click={() => downloadFile(file)}>↓ {file.name}</b>
|
||||||
|
<small> {file.type} - {prettyBytes(file.size)}</small>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<Button on:click={download}>download all</Button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.note {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 2px solid var(--ui-bg-1);
|
||||||
|
outline: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
white-space: pre;
|
||||||
|
overflow: auto;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note b {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note.file {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -4,7 +4,7 @@
|
|||||||
export let color = true
|
export let color = true
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div {...$$restProps}>
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<small>{label}</small>
|
<small>{label}</small>
|
||||||
<input type="checkbox" bind:checked={value} />
|
<input type="checkbox" bind:checked={value} />
|
11
frontend/src/lib/ui/TextArea.svelte
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let label: string = ''
|
||||||
|
export let value: string
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<small>
|
||||||
|
{label}
|
||||||
|
</small>
|
||||||
|
<textarea class="box" {...$$restProps} bind:value />
|
||||||
|
</label>
|
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
export enum Theme {
|
enum Theme {
|
||||||
Dark = 'dark',
|
Dark = 'dark',
|
||||||
Light = 'light',
|
Light = 'light',
|
||||||
Auto = 'auto',
|
Auto = 'auto',
|
@@ -1,20 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Note } from '$lib/api'
|
import { create, Note, PayloadToLargeError } from '$lib/api'
|
||||||
import { create } from '$lib/api'
|
import { encrypt, getKeyFromString, getRandomBytes, Hex } from '$lib/crypto'
|
||||||
import { getKeyFromString, encrypt, Hex, getRandomBytes } from '$lib/crypto'
|
|
||||||
|
|
||||||
import Button from '$lib/ui/Button.svelte'
|
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 Switch from '$lib/ui/Switch.svelte'
|
||||||
import TextArea from '$lib/ui/TextArea.svelte'
|
import TextArea from '$lib/ui/TextArea.svelte'
|
||||||
import TextInput from '$lib/ui/TextInput.svelte'
|
import TextInput from '$lib/ui/TextInput.svelte'
|
||||||
|
import { blur } from 'svelte/transition'
|
||||||
|
|
||||||
let note: Note = {
|
let note: Note = {
|
||||||
contents: '',
|
contents: '',
|
||||||
|
meta: { type: 'text' },
|
||||||
views: 1,
|
views: 1,
|
||||||
expiration: 60,
|
expiration: 60,
|
||||||
}
|
}
|
||||||
let result: { password: string; id: string } | null = null
|
let result: { password: string; id: string } | null = null
|
||||||
let advanced = false
|
let advanced = false
|
||||||
|
let file = false
|
||||||
let type = false
|
let type = false
|
||||||
let message = ''
|
let message = ''
|
||||||
let loading = false
|
let loading = false
|
||||||
@@ -31,6 +34,12 @@
|
|||||||
message = 'the note will expire and be destroyed after ' + fraction
|
message = 'the note will expire and be destroyed after ' + fraction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: note.meta.type = file ? 'file' : 'text'
|
||||||
|
|
||||||
|
$: if (!file) {
|
||||||
|
note.contents = ''
|
||||||
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
try {
|
try {
|
||||||
error = null
|
error = null
|
||||||
@@ -39,6 +48,7 @@
|
|||||||
const key = await getKeyFromString(password)
|
const key = await getKeyFromString(password)
|
||||||
const data: Note = {
|
const data: Note = {
|
||||||
contents: await encrypt(note.contents, key),
|
contents: await encrypt(note.contents, key),
|
||||||
|
meta: note.meta,
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (type) data.expiration = parseInt(note.expiration)
|
if (type) data.expiration = parseInt(note.expiration)
|
||||||
@@ -51,8 +61,11 @@
|
|||||||
id: response.id,
|
id: response.id,
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
if (e instanceof PayloadToLargeError) {
|
||||||
error = 'could not create note.'
|
error = 'could not create not. note is to big'
|
||||||
|
} else {
|
||||||
|
error = 'could not create note. please try again.'
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
@@ -84,18 +97,32 @@
|
|||||||
<br />
|
<br />
|
||||||
<Button on:click={reset}>new note</Button>
|
<Button on:click={reset}>new note</Button>
|
||||||
{:else}
|
{:else}
|
||||||
|
<p>
|
||||||
|
Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and
|
||||||
|
share the link.
|
||||||
|
</p>
|
||||||
<form on:submit|preventDefault={submit}>
|
<form on:submit|preventDefault={submit}>
|
||||||
<fieldset disabled={loading}>
|
<fieldset disabled={loading}>
|
||||||
<TextArea
|
{#if file}
|
||||||
label="note"
|
<FileUpload label="file" on:file={(f) => (note.contents = f.detail)} />
|
||||||
bind:value={note.contents}
|
{:else}
|
||||||
placeholder="..."
|
<TextArea
|
||||||
data-testid="input-note"
|
label="note"
|
||||||
/>
|
bind:value={note.contents}
|
||||||
|
placeholder="..."
|
||||||
|
data-testid="input-note"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
|
<Switch class="file" label="file" bind:value={file} />
|
||||||
<Switch label="advanced" bind:value={advanced} />
|
<Switch label="advanced" bind:value={advanced} />
|
||||||
<Button type="submit" data-testid="button-create">create</Button>
|
<div class="grow" />
|
||||||
|
<div class="tr">
|
||||||
|
<small>max: <MaxSize /> </small>
|
||||||
|
<br />
|
||||||
|
<Button type="submit" data-testid="button-create">create</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
@@ -111,37 +138,30 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="advanced" class:hidden={!advanced}>
|
{#if advanced}
|
||||||
<br />
|
<div transition:blur={{ duration: 250 }}>
|
||||||
<div class="fields">
|
<br />
|
||||||
<TextInput
|
<div class="fields">
|
||||||
type="number"
|
<TextInput
|
||||||
label="views"
|
type="number"
|
||||||
bind:value={note.views}
|
label="views"
|
||||||
disabled={type}
|
bind:value={note.views}
|
||||||
max={100}
|
disabled={type}
|
||||||
/>
|
max={100}
|
||||||
<div class="middle-switch">
|
/>
|
||||||
<Switch label="mode" bind:value={type} color={false} />
|
<div class="middle-switch">
|
||||||
|
<Switch label="mode" bind:value={type} color={false} />
|
||||||
|
</div>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label="minutes"
|
||||||
|
bind:value={note.expiration}
|
||||||
|
disabled={!type}
|
||||||
|
max={360}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<TextInput
|
|
||||||
type="number"
|
|
||||||
label="minutes"
|
|
||||||
bind:value={note.expiration}
|
|
||||||
disabled={!type}
|
|
||||||
max={360}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
.fields {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.spacer {
|
|
||||||
width: 3rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -149,22 +169,27 @@
|
|||||||
<style>
|
<style>
|
||||||
.bottom {
|
.bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom :global(.file) {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grow {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.middle-switch {
|
.middle-switch {
|
||||||
margin: 0 1rem;
|
margin: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced {
|
.error-text {
|
||||||
max-height: 14em;
|
margin-top: 0.5rem;
|
||||||
overflow: hidden;
|
|
||||||
transition: var(--ui-anim);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced.hidden {
|
.fields {
|
||||||
max-height: 0;
|
display: flex;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@@ -1,8 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { init } from '$lib/stores/status'
|
||||||
import Footer from '$lib/views/Footer.svelte'
|
import Footer from '$lib/views/Footer.svelte'
|
||||||
import Header from '$lib/views/Header.svelte'
|
import Header from '$lib/views/Header.svelte'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
import '../app.css'
|
import '../app.css'
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
80
frontend/src/routes/about.svelte
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script context="module">
|
||||||
|
import { browser, dev } from '$app/env'
|
||||||
|
import { status } from '$lib/stores/status'
|
||||||
|
import AboutParagraph from '$lib/ui/AboutParagraph.svelte'
|
||||||
|
|
||||||
|
export const hydrate = dev
|
||||||
|
export const router = browser
|
||||||
|
export const prerender = true
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>About</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<section class="content">
|
||||||
|
<h1>About</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<i>cryptgeon</i> is a secure, open source sharing note / file service inspired by
|
||||||
|
<a href="https://privnote.com"><i>PrivNote</i></a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<AboutParagraph title="how does it work?">
|
||||||
|
<span>
|
||||||
|
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>
|
||||||
|
|
||||||
|
<AboutParagraph title="features">
|
||||||
|
<ul>
|
||||||
|
<li>server cannot decrypt contents due to client side encryption</li>
|
||||||
|
<li>view and time constraints</li>
|
||||||
|
<li>in memory, no persistence</li>
|
||||||
|
</ul>
|
||||||
|
</AboutParagraph>
|
||||||
|
|
||||||
|
<AboutParagraph title="tech stack">
|
||||||
|
<span>
|
||||||
|
the backend is written in rust and the frontend is svelte and typescript.
|
||||||
|
<br />
|
||||||
|
you are welcomed to check & audit the
|
||||||
|
<a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener">
|
||||||
|
source code
|
||||||
|
</a>.
|
||||||
|
</span>
|
||||||
|
</AboutParagraph>
|
||||||
|
|
||||||
|
<AboutParagraph title="attribution">
|
||||||
|
<span>
|
||||||
|
icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from
|
||||||
|
<a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
|
||||||
|
</span>
|
||||||
|
</AboutParagraph>
|
||||||
|
|
||||||
|
<AboutParagraph title="version">
|
||||||
|
<span>
|
||||||
|
{#if $status}
|
||||||
|
<code>v{$status.version}</code>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</AboutParagraph>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
list-style: square;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -7,13 +7,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import copy from 'copy-to-clipboard'
|
|
||||||
|
|
||||||
import type { NotePublic } from '$lib/api'
|
import type { NotePublic } from '$lib/api'
|
||||||
import { info, get } from '$lib/api'
|
import { get, info } from '$lib/api'
|
||||||
import { decrypt, getKeyFromString } from '$lib/crypto'
|
import { decrypt, getKeyFromString } from '$lib/crypto'
|
||||||
import Button from '$lib/ui/Button.svelte'
|
import Button from '$lib/ui/Button.svelte'
|
||||||
|
import ShowNote from '$lib/ui/ShowNote.svelte'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
export let id: string
|
export let id: string
|
||||||
|
|
||||||
@@ -29,7 +28,6 @@
|
|||||||
loading = true
|
loading = true
|
||||||
error = null
|
error = null
|
||||||
password = window.location.hash.slice(1)
|
password = window.location.hash.slice(1)
|
||||||
console.log(password)
|
|
||||||
await info(id)
|
await info(id)
|
||||||
exists = true
|
exists = true
|
||||||
} catch {
|
} catch {
|
||||||
@@ -61,12 +59,7 @@
|
|||||||
note was not found or was already deleted.
|
note was not found or was already deleted.
|
||||||
</p>
|
</p>
|
||||||
{:else if note && !error}
|
{:else if note && !error}
|
||||||
<p class="error-text">you will not get the chance to see the note again.</p>
|
<ShowNote {note} />
|
||||||
<div class="note" data-testid="note-result">
|
|
||||||
{note.contents}
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<Button on:click={() => copy(note.contents)}>copy to clipboard</Button>
|
|
||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={show}>
|
<form on:submit|preventDefault={show}>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -86,16 +79,3 @@
|
|||||||
{#if loading}
|
{#if loading}
|
||||||
<p>loading...</p>
|
<p>loading...</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
.note {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 2px solid var(--ui-bg-1);
|
|
||||||
outline: none;
|
|
||||||
padding: 0.5rem;
|
|
||||||
white-space: pre;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 279 B |
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 313 B |
Before Width: | Height: | Size: 728 B After Width: | Height: | Size: 728 B |
Before Width: | Height: | Size: 720 B After Width: | Height: | Size: 720 B |
Before Width: | Height: | Size: 474 B After Width: | Height: | Size: 474 B |
Before Width: | Height: | Size: 275 B After Width: | Height: | Size: 275 B |
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:docker": "docker-compose up memcached",
|
"dev:docker": "docker-compose up memcached",
|
||||||
"dev:backend": "cargo watch -x 'run --bin cryptgeon'",
|
"dev:backend": "cd backend && cargo watch -x 'run --bin cryptgeon'",
|
||||||
"dev:front": "npm --prefix client run dev",
|
"dev:front": "pnpm --prefix frontend run dev",
|
||||||
|
"dev:proxy": "node proxy.mjs",
|
||||||
"dev": "run-p dev:*"
|
"dev": "run-p dev:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"http-proxy": "^1.18.1",
|
||||||
"npm-run-all": "^4.1.5"
|
"npm-run-all": "^4.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
pnpm-lock.yaml
generated
@@ -1,9 +1,11 @@
|
|||||||
lockfileVersion: 5.3
|
lockfileVersion: 5.3
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
|
http-proxy: ^1.18.1
|
||||||
npm-run-all: ^4.1.5
|
npm-run-all: ^4.1.5
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
http-proxy: 1.18.1
|
||||||
npm-run-all: 4.1.5
|
npm-run-all: 4.1.5
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
@@ -116,6 +118,20 @@ packages:
|
|||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/eventemitter3/4.0.7:
|
||||||
|
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/follow-redirects/1.14.6:
|
||||||
|
resolution: {integrity: sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/function-bind/1.1.1:
|
/function-bind/1.1.1:
|
||||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -157,6 +173,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/http-proxy/1.18.1:
|
||||||
|
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 4.0.7
|
||||||
|
follow-redirects: 1.14.6
|
||||||
|
requires-port: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
dev: true
|
||||||
|
|
||||||
/is-arrayish/0.2.1:
|
/is-arrayish/0.2.1:
|
||||||
resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=}
|
resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -339,6 +366,10 @@ packages:
|
|||||||
path-type: 3.0.0
|
path-type: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/requires-port/1.0.0:
|
||||||
|
resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/resolve/1.20.0:
|
/resolve/1.20.0:
|
||||||
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
|
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
19
proxy.mjs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
server.on('error', () => start())
|
||||||
|
} catch (e) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|