Compare commits

...

32 Commits

Author SHA1 Message Date
7c6ba654f6 correct version bump 2024-09-02 10:28:08 +02:00
cb20224317 version bumps 2024-09-02 10:27:26 +02:00
085b1c20df Merge pull request #148 from cupcakearmy/move-shared-package-into-cli
Move shared package into cli
2024-09-02 10:26:35 +02:00
4b1f939281 remove useless dependecies and use cli instead of package 2024-09-02 10:20:03 +02:00
7b919f2a53 move shared package to cli 2024-09-02 10:19:35 +02:00
868b49c1c3 add guide 2024-08-27 15:11:18 +02:00
6b5eea34a1 Merge pull request #147 from cupcakearmy/actix-2-axum
Actix 2 axum
2024-08-27 15:06:29 +02:00
d70bee14af version bump 2024-08-27 14:52:17 +02:00
4960260076 also disable icon in text 2024-08-27 14:49:08 +02:00
3247a0cfca skip expiration in safari 2024-08-27 14:35:39 +02:00
9527a499ed skip size 2024-08-27 13:26:04 +02:00
b5590bb5ef add retry 2024-08-27 11:45:43 +02:00
7691dc80f8 use pnpm command 2024-08-27 10:56:21 +02:00
f8d8fa05b0 new message 2024-08-27 10:55:59 +02:00
7aae690850 cleanup imports 2024-08-27 10:55:51 +02:00
e15d9eb537 upload artifacts 2024-08-27 00:42:21 +02:00
7fe70a6f3b revert 2024-08-27 00:38:56 +02:00
d86ef79abd use container 2024-08-27 00:36:48 +02:00
23446a4c74 Merge branch 'main' into actix-2-axum 2024-08-27 00:32:28 +02:00
ee92928d1f pipeline 2024-08-27 00:32:17 +02:00
b60a079bbe Merge pull request #140 from elfakyn/main
Fix typos in english localization
2024-08-27 00:23:28 +02:00
ba474dff31 more robust config 2024-08-27 00:22:41 +02:00
3cb002ee33 Merge branch 'main' into main 2024-08-27 00:10:44 +02:00
a10d8735dd cleanup tests 2024-08-27 00:09:51 +02:00
dfa2401eea aria check 2024-08-25 22:22:45 +02:00
ea58d89f98 cleanup 2024-08-25 22:22:13 +02:00
eaca1a981d cleanup 2024-08-25 22:21:45 +02:00
199755d18e timeout 2024-08-25 22:20:46 +02:00
724d0709d3 .env.dev 2024-08-25 22:20:29 +02:00
bd5acca97a use axum body limit 2024-08-25 22:19:41 +02:00
a0a99cd3cc cleanup status variable 2024-08-25 22:18:31 +02:00
Arya ';--bscriptalert(":)")/script
8ccfdd6e2e fix typos in english localization 2024-07-04 18:00:06 -04:00
53 changed files with 233 additions and 271 deletions

View File

View File

@@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
# Node
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
@@ -27,13 +27,16 @@ jobs:
- name: Prepare
run: |
pnpm install --frozen-lockfile
pnpm install
pnpm exec playwright install --with-deps
pnpm run test:prepare
- name: Run your tests
run: pnpm test
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: test-results
path: test-results
name: playwright-report
path: test-results/
retention-days: 7

View File

@@ -70,13 +70,13 @@ of the notes even if it tried to.
| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. |
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. |
| `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. |
| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. |
| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` |
| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable |
| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo |
| `THEME_PAGE_TITLE` | `""` | Custom text the page title |
| `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable |
| `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. |
## Deployment
@@ -144,6 +144,8 @@ There is a [guide](https://mariushosting.com/how-to-install-cryptgeon-on-your-sy
### Written Guides
- French by [zarevskaya](https://belginux.com/installer-cryptgeon-avec-docker/)
- Italian by [@nicfab](https://notes.nicfab.eu/it/posts/cryptgeon/)
- English by [@nicfab](https://notes.nicfab.eu/en/posts/cryptgeon/)
## Development

View File

@@ -11,9 +11,6 @@
},
{
"path": "packages/cli"
},
{
"path": "packages/shared"
}
],
"settings": {

View File

@@ -9,7 +9,7 @@ services:
app:
build: .
env_file: .dev.env
env_file: .env.dev
depends_on:
- redis
restart: unless-stopped

View File

@@ -5,8 +5,8 @@
"dev": "run-p dev:*",
"docker:up": "docker compose -f docker-compose.dev.yaml up",
"docker:build": "docker compose -f docker-compose.dev.yaml build",
"test": "playwright test --project chrome firefox safari",
"test:local": "playwright test --project chrome",
"test": "playwright test --project=chrome --project=firefox --project=safari",
"test:local": "playwright test --project=chrome",
"test:server": "run-s docker:up",
"test:prepare": "run-p build docker:build",
"build": "pnpm run --recursive --filter=!@cryptgeon/backend build"
@@ -17,5 +17,5 @@
"npm-run-all": "^4.1.5",
"shelljs": "^0.8.5"
},
"packageManager": "pnpm@9.8.0"
"packageManager": "pnpm@9.9.0"
}

View File

@@ -261,7 +261,7 @@ dependencies = [
[[package]]
name = "cryptgeon"
version = "2.7.0"
version = "2.8.1"
dependencies = [
"axum",
"bs62",

View File

@@ -1,6 +1,6 @@
[package]
name = "cryptgeon"
version = "2.7.0"
version = "2.8.1"
authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2021"
rust-version = "1.80"

View File

@@ -38,10 +38,6 @@ pub static ref ALLOW_FILES: bool = std::env::var("ALLOW_FILES")
.unwrap_or("true".to_string())
.parse()
.unwrap();
pub static ref THEME_NEW_NOTE_NOTICE: bool = std::env::var("THEME_NEW_NOTE_NOTICE")
.unwrap_or("true".to_string())
.parse()
.unwrap();
}
// THEME
@@ -62,4 +58,8 @@ lazy_static! {
.unwrap_or("".to_string())
.parse()
.unwrap();
pub static ref THEME_NEW_NOTE_NOTICE: bool = std::env::var("THEME_NEW_NOTE_NOTICE")
.unwrap_or("true".to_string())
.parse()
.unwrap();
}

View File

@@ -1,5 +1,5 @@
use axum::{
extract::Request,
extract::{DefaultBodyLimit, Request},
routing::{delete, get, post},
Router, ServiceExt,
};
@@ -7,7 +7,6 @@ use dotenv::dotenv;
use tower::Layer;
use tower_http::{
compression::CompressionLayer,
limit::RequestBodyLimitLayer,
normalize_path::NormalizePathLayer,
services::{ServeDir, ServeFile},
};
@@ -47,14 +46,14 @@ async fn main() {
let app = Router::new()
.nest("/api", api_routes)
.fallback_service(serve_dir)
.layer(DefaultBodyLimit::max(*config::LIMIT))
.layer(
CompressionLayer::new()
.br(true)
.deflate(true)
.gzip(true)
.zstd(true),
)
.layer(RequestBodyLimitLayer::new(*config::LIMIT));
);
let app = NormalizePathLayer::trim_trailing_slash().layer(app);
@@ -62,9 +61,7 @@ async fn main() {
.await
.unwrap();
println!("listening on {}", listener.local_addr().unwrap());
println!("Config {}", *config::LIMIT);
axum::serve(listener, ServiceExt::<Request>::into_make_service(app))
// axum::serve(listener, app)
.await
.unwrap();
}

View File

@@ -1,7 +1,9 @@
use axum::extract::Path;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use axum::{
extract::Path,
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;

View File

@@ -1,6 +1,5 @@
use crate::config;
use axum::http::StatusCode;
use axum::Json;
use axum::{http::StatusCode, Json};
use serde::Serialize;
#[derive(Serialize)]
@@ -13,12 +12,12 @@ pub struct Status {
pub max_expiration: u32,
pub allow_advanced: bool,
pub allow_files: bool,
pub theme_new_note_notice: bool,
// Theme
pub theme_image: String,
pub theme_text: String,
pub theme_page_title: String,
pub theme_favicon: String,
pub theme_new_note_notice: bool,
}
pub async fn get_status() -> (StatusCode, Json<Status>) {

View File

@@ -1,13 +1,14 @@
import pkg from './package.json' with { type: 'json' }
import { build } from 'tsup'
import pkg from './package.json' with { type: 'json' }
const watch = process.argv.slice(2)[0] === '--watch'
await build({
entry: ['src/index.ts', 'src/cli.ts'],
entry: ['src/index.ts', 'src/cli.ts', 'src/shared/shared.ts'],
dts: true,
minify: true,
format: ['esm', 'cjs'],
target: 'es2020',
clean: true,
define: { VERSION: `"${pkg.version}"` },
watch,

View File

@@ -1,6 +1,6 @@
{
"name": "cryptgeon",
"version": "2.7.0",
"version": "2.8.1",
"homepage": "https://github.com/cupcakearmy/cryptgeon",
"repository": {
"type": "git",
@@ -9,7 +9,11 @@
},
"type": "module",
"exports": {
".": "./dist/index.js"
".": "./dist/index.js",
"./shared": {
"import": "./dist/shared/shared.js",
"types": "./dist/shared/shared.d.ts"
}
},
"types": "./dist/index.d.ts",
"bin": {
@@ -25,15 +29,14 @@
"prepublishOnly": "run-s build"
},
"devDependencies": {
"@commander-js/extra-typings": "^12.0.1",
"@cryptgeon/shared": "workspace:*",
"@commander-js/extra-typings": "^12.1.0",
"@types/inquirer": "^9.0.7",
"@types/mime": "^3.0.4",
"@types/mime": "^4.0.0",
"@types/node": "^20.11.24",
"commander": "^12.0.0",
"commander": "^12.1.0",
"inquirer": "^9.2.15",
"mime": "^4.0.1",
"occulto": "^2.0.3",
"occulto": "^2.0.6",
"pretty-bytes": "^6.1.1",
"tsup": "^8.2.4",
"typescript": "^5.3.3"

View File

@@ -1,14 +1,15 @@
import { Adapters, get, info, setOptions } from '@cryptgeon/shared'
import inquirer from 'inquirer'
import { access, constants, writeFile } from 'node:fs/promises'
import { basename, resolve } from 'node:path'
import { AES, Hex } from 'occulto'
import pretty from 'pretty-bytes'
import { Adapters } from '../shared/adapters.js'
import { API } from '../shared/api.js'
export async function download(url: URL, all: boolean, suggestedPassword?: string) {
setOptions({ server: url.origin })
API.setOptions({ server: url.origin })
const id = url.pathname.split('/')[2]
const preview = await info(id).catch(() => {
const preview = await API.info(id).catch(() => {
throw new Error('Note does not exist or is expired')
})
@@ -33,7 +34,7 @@ export async function download(url: URL, all: boolean, suggestedPassword?: strin
}
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
const note = await get(id)
const note = await API.get(id)
const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password')
switch (note.meta.type) {

View File

@@ -1,9 +1,10 @@
import { readFile, stat } from 'node:fs/promises'
import { basename } from 'node:path'
import { Adapters, create, getOptions, FileDTO, Note, NoteMeta } from '@cryptgeon/shared'
import mime from 'mime'
import { AES, Hex } from 'occulto'
import { Adapters } from '../shared/adapters.js'
import { API, FileDTO, Note, NoteMeta } from '../shared/api.js'
export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
@@ -38,8 +39,8 @@ export async function upload(input: string | string[], options: UploadOptions):
// Create the actual note and upload it.
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
const result = await create(note)
let url = `${getOptions().server}/note/${result.id}`
const result = await API.create(note)
let url = `${API.getOptions().server}/note/${result.id}`
if (!derived) url += `#${Hex.encode(key)}`
return url
}

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env node
import { Argument, Option, program } from '@commander-js/extra-typings'
import { setOptions, status } from '@cryptgeon/shared'
import prettyBytes from 'pretty-bytes'
import { download } from './download.js'
import { parseFile, parseNumber } from './parsers.js'
import { getStdin } from './stdin.js'
import { upload } from './upload.js'
import { checkConstrains, exit } from './utils.js'
import { download } from './actions/download.js'
import { upload } from './actions/upload.js'
import { API } from './shared/api.js'
import { parseFile, parseNumber } from './utils/parsers.js'
import { getStdin } from './utils/stdin.js'
import { checkConstrains, exit } from './utils/utils.js'
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer)
@@ -33,8 +33,8 @@ program
.description('show information about the server')
.addOption(server)
.action(async (options) => {
setOptions({ server: options.server })
const response = await status()
API.setOptions({ server: options.server })
const response = await API.status()
const formatted = {
...response,
max_size: prettyBytes(response.max_size),
@@ -54,7 +54,7 @@ send
.addOption(minutes)
.addOption(password)
.action(async (files, options) => {
setOptions({ server: options.server })
API.setOptions({ server: options.server })
await checkConstrains(options)
options.password ||= await getStdin()
try {
@@ -72,7 +72,7 @@ send
.addOption(minutes)
.addOption(password)
.action(async (text, options) => {
setOptions({ server: options.server })
API.setOptions({ server: options.server })
await checkConstrains(options)
options.password ||= await getStdin()
try {

View File

@@ -1,4 +1,4 @@
export * from '@cryptgeon/shared'
export * from './download.js'
export * from './upload.js'
export * from './utils.js'
export * from './actions/download.js'
export * from './actions/upload.js'
export * from './shared/adapters.js'
export * from './shared/api.js'

View File

@@ -39,15 +39,15 @@ export let client: ClientOptions = {
server: '',
}
export function setOptions(options: Partial<ClientOptions>) {
function setOptions(options: Partial<ClientOptions>) {
client = { ...client, ...options }
}
export function getOptions(): ClientOptions {
function getOptions(): ClientOptions {
return client
}
export async function call(options: CallOptions) {
async function call(options: CallOptions) {
const url = client.server + '/api/' + options.url
const response = await fetch(url, {
method: options.method,
@@ -65,7 +65,7 @@ export async function call(options: CallOptions) {
return response.json()
}
export async function create(note: Note) {
async function create(note: Note) {
const { meta, ...rest } = note
const body: NoteCreate = {
...rest,
@@ -79,7 +79,7 @@ export async function create(note: Note) {
return data as { id: string }
}
export async function get(id: string): Promise<NotePublic> {
async function get(id: string): Promise<NotePublic> {
const data = await call({
url: `notes/${id}`,
method: 'delete',
@@ -93,7 +93,7 @@ export async function get(id: string): Promise<NotePublic> {
return note
}
export async function info(id: string): Promise<NoteInfo> {
async function info(id: string): Promise<NoteInfo> {
const data = await call({
url: `notes/${id}`,
method: 'get',
@@ -112,16 +112,27 @@ export type Status = {
max_views: number
max_expiration: number
allow_advanced: boolean
allow_files: boolean
theme_image: string
theme_text: string
theme_favicon: string
theme_page_title: string
theme_new_note_notice: boolean
}
export async function status() {
async function status() {
const data = await call({
url: 'status/',
method: 'get',
})
return data as Status
}
export const API = {
setOptions,
getOptions,
create,
get,
info,
status,
}

View File

@@ -21,7 +21,7 @@ export function parseURL(value: string, _: URL): URL {
}
export function parseNumber(value: string, _: number): number {
const n = parseInt(value, 10)
if (isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
const n = Number.parseInt(value, 10)
if (Number.isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
return n
}

View File

@@ -18,6 +18,7 @@ export function getStdin(timeout: number = 10): Promise<string> {
resolve('')
}, timeout)
process.stdin.on('error', reject)
process.stdin.on('data', dataHandler)
process.stdin.on('end', endHandler)
})

View File

@@ -1,5 +1,5 @@
import { status } from '@cryptgeon/shared'
import { exit as exitNode } from 'node:process'
import { API } from '../shared/api.js'
export function exit(message: string) {
console.error(message)
@@ -11,7 +11,7 @@ export async function checkConstrains(constrains: { views?: number; minutes?: nu
if (views && minutes) exit('cannot set view and minutes constrains simultaneously')
if (!views && !minutes) constrains.views = 1
const response = await status()
const response = await API.status()
if (views && views > response.max_views)
exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`)
if (minutes && minutes > response.max_expiration)

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es2022",
"module": "es2022",
"moduleResolution": "node",
"moduleResolution": "Bundler",
"declaration": true,
"emitDeclarationOnly": true,
"strict": true,

View File

@@ -25,7 +25,7 @@
"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_to_big": "could not create note. note is too big",
"note_error": "could not create note. please try again.",
"max": "max: {n}",
"empty_content": "note is empty."
@@ -44,7 +44,7 @@
"decryption_failed": "wrong password. could not decipher. probably a broken link. note was destroyed.",
"unsupported_type": "unsupported note type."
},
"explanation": "click below to show and delete the note if the counter has reached it's limit",
"explanation": "click below to show and delete the note if the counter has reached its 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",

View File

@@ -17,7 +17,6 @@
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.2",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/file-saver": "^2.0.7",
"@zerodevx/svelte-toast": "^0.9.5",
"adm-zip": "^0.5.10",
"dotenv": "^16.4.5",
@@ -29,11 +28,9 @@
"vite": "^5.1.7"
},
"dependencies": {
"@cryptgeon/shared": "workspace:*",
"cryptgeon": "workspace:*",
"@fontsource/fira-mono": "^5.0.8",
"copy-to-clipboard": "^3.3.3",
"file-saver": "^2.0.5",
"occulto": "^2.0.3",
"occulto": "^2.0.6",
"pretty-bytes": "^6.1.1",
"qrious": "^4.0.2"
}

View File

@@ -1,8 +1,8 @@
import { status as getStatus, type Status } from '@cryptgeon/shared'
import { API, type Status } from 'cryptgeon/shared'
import { writable } from 'svelte/store'
export const status = writable<null | Status>(null)
export async function init() {
status.set(await getStatus())
status.set(await API.status())
}

View File

@@ -1,11 +1,11 @@
import { toast, type SvelteToastOptions } from '@zerodevx/svelte-toast'
import { toast } from '@zerodevx/svelte-toast'
export enum NotifyType {
Success = 'success',
Error = 'error',
}
const themeMapping: Record<NotifyType, SvelteToastOptions['theme']> = {
const themeMapping: Record<NotifyType, Record<string, string>> = {
[NotifyType.Success]: {
'--toastBackground': 'var(--ui-clr-primary)',
'--toastBarBackground': 'var(--ui-clr-primary-alt)',
@@ -17,7 +17,7 @@ const themeMapping: Record<NotifyType, SvelteToastOptions['theme']> = {
}
function notifyFN(message: string, type: NotifyType = NotifyType.Success) {
const options: SvelteToastOptions = {
const options = {
duration: 5_000,
theme: {
...themeMapping[type],

View File

@@ -4,7 +4,7 @@
import { status } from '$lib/stores/status'
import Switch from '$lib/ui/Switch.svelte'
import TextInput from '$lib/ui/TextInput.svelte'
import type { Note } from '@cryptgeon/shared'
import type { Note } from 'cryptgeon/shared'
export let note: Note
export let timeExpiration = false

View File

@@ -3,7 +3,7 @@
import Button from '$lib/ui/Button.svelte'
import MaxSize from '$lib/ui/MaxSize.svelte'
import type { FileDTO } from '@cryptgeon/shared'
import type { FileDTO } from 'cryptgeon/shared'
export let label: string = ''
export let files: FileDTO[] = []

View File

@@ -18,19 +18,19 @@
export let icon: keyof typeof map
</script>
<div on:click {...$$restProps}>
<button on:click {...$$restProps}>
{#if map[icon]}
<svelte:component this={map[icon]} />
{/if}
</div>
</button>
<style>
div {
button {
display: inline-block;
contain: strict;
box-sizing: content-box;
}
div > :global(svg) {
button > :global(svg) {
display: block;
fill: currentColor;
}

View File

@@ -1,16 +1,26 @@
<script lang="ts" context="module">
export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any }
function saveAs(file: File) {
const url = window.URL.createObjectURL(file)
const a = document.createElement('a')
a.style.display = 'none'
a.href = url
a.download = file.name
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
a.remove()
}
</script>
<script lang="ts">
import pkg from 'file-saver'
const { saveAs } = pkg
import prettyBytes from 'pretty-bytes'
import { t } from 'svelte-intl-precompile'
import Button from '$lib/ui/Button.svelte'
import { copy } from '$lib/utils'
import type { FileDTO, NotePublic } from '@cryptgeon/shared'
import type { FileDTO, NotePublic } from 'cryptgeon/shared'
export let note: DecryptedNote

View File

@@ -39,13 +39,23 @@
<input bind:value {...$$restProps} class:valid={valid === true} />
<div class="icons">
{#if isPassword}
<Icon class="icon" icon={hidden ? 'eye' : 'eye-off'} on:click={toggle} />
<Icon
disabled={$$restProps.disabled}
class="icon"
icon={hidden ? 'eye' : 'eye-off'}
on:click={toggle}
/>
{/if}
{#if random}
<Icon class="icon" icon="dice" on:click={randomFN} />
<Icon disabled={$$restProps.disabled} class="icon" icon="dice" on:click={randomFN} />
{/if}
{#if copy}
<Icon class="icon" icon="copy" on:click={() => copyFN(value.toString())} />
<Icon
disabled={$$restProps.disabled}
class="icon"
icon="copy"
on:click={() => copyFN(value.toString())}
/>
{/if}
</div>
</label>

View File

@@ -1,11 +1,10 @@
import copyToClipboard from 'copy-to-clipboard'
import { t } from 'svelte-intl-precompile'
import { get } from 'svelte/store'
import { notify } from './toast'
export function copy(value: string) {
copyToClipboard(value)
window.navigator.clipboard.writeText(value)
const msg = get(t)('common.copied_to_clipboard')
notify.success(msg)
}

View File

@@ -13,8 +13,7 @@
import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte'
import Switch from '$lib/ui/Switch.svelte'
import TextArea from '$lib/ui/TextArea.svelte'
import type { FileDTO, Note } from '@cryptgeon/shared'
import { Adapters, PayloadToLargeError, create } from '@cryptgeon/shared'
import { Adapters, API, PayloadToLargeError, type FileDTO, type Note } from 'cryptgeon/shared'
let note: Note = {
contents: '',
@@ -77,7 +76,7 @@
else data.views = parseInt(note.views as any)
loading = $t('common.uploading')
const response = await create(data)
const response = await API.create(data)
result = {
id: response.id,
password: customPassword ? undefined : Hex.encode(key),

View File

@@ -7,7 +7,7 @@
import Loader from '$lib/ui/Loader.svelte'
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
import TextInput from '$lib/ui/TextInput.svelte'
import { Adapters, get, info, type NoteMeta } from '@cryptgeon/shared'
import { Adapters, API, type NoteMeta } from 'cryptgeon/shared'
import type { PageData } from './$types'
export let data: PageData
@@ -28,7 +28,7 @@
try {
loading = $t('common.loading')
password = window.location.hash.slice(1)
const note = await info(id)
const note = await API.info(id)
meta = note.meta
exists = true
} catch {
@@ -51,7 +51,7 @@
// Load note
error = null
loading = $t('common.downloading')
const data = await get(id)
const data = await API.get(id)
loading = $t('common.decrypting')
const derived = meta?.derivation && (await AES.derive(password!, meta.derivation))
const key = derived ? derived[0] : Hex.decode(password!)

View File

@@ -1,22 +0,0 @@
{
"private": true,
"name": "@cryptgeon/shared",
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"dev": "tsc -w",
"build": "tsc"
},
"devDependencies": {
"typescript": "^5.3.3"
},
"dependencies": {
"occulto": "^2.0.3"
}
}

View File

@@ -1,12 +0,0 @@
{
"compilerOptions": {
"incremental": true,
"composite": true,
"target": "es2022",
"module": "es2022",
"rootDir": "./src",
"moduleResolution": "node",
"outDir": "./dist",
"strict": true
}
}

View File

@@ -4,16 +4,17 @@ const config: PlaywrightTestConfig = {
use: {
video: 'retain-on-failure',
baseURL: 'http://localhost:3000',
actionTimeout: 10_000,
actionTimeout: 30_000,
},
outputDir: './test-results',
testDir: './test',
timeout: 10_000,
timeout: 30_000,
fullyParallel: true,
retries: 2,
webServer: {
command: 'docker compose -f docker-compose.dev.yaml up',
command: 'pnpm run docker:up',
port: 3000,
reuseExistingServer: true,
},
@@ -22,10 +23,6 @@ const config: PlaywrightTestConfig = {
{ name: 'chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'safari', use: { ...devices['Desktop Safari'] } },
{ name: 'cli', use: { ...devices['Desktop Chrome'] }, grep: [/@cli/] },
{ name: 'web', use: { ...devices['Desktop Chrome'] }, grep: [/@web/] },
{ name: 'cross', use: { ...devices['Desktop Chrome'] }, grep: [/@cross/] },
],
}

103
pnpm-lock.yaml generated
View File

@@ -26,23 +26,20 @@ importers:
packages/cli:
devDependencies:
'@commander-js/extra-typings':
specifier: ^12.0.1
version: 12.0.1(commander@12.0.0)
'@cryptgeon/shared':
specifier: workspace:*
version: link:../shared
specifier: ^12.1.0
version: 12.1.0(commander@12.1.0)
'@types/inquirer':
specifier: ^9.0.7
version: 9.0.7
'@types/mime':
specifier: ^3.0.4
version: 3.0.4
specifier: ^4.0.0
version: 4.0.0
'@types/node':
specifier: ^20.11.24
version: 20.11.24
commander:
specifier: ^12.0.0
version: 12.0.0
specifier: ^12.1.0
version: 12.1.0
inquirer:
specifier: ^9.2.15
version: 9.2.15
@@ -50,8 +47,8 @@ importers:
specifier: ^4.0.1
version: 4.0.1
occulto:
specifier: ^2.0.3
version: 2.0.3
specifier: ^2.0.6
version: 2.0.6
pretty-bytes:
specifier: ^6.1.1
version: 6.1.1
@@ -64,21 +61,15 @@ importers:
packages/frontend:
dependencies:
'@cryptgeon/shared':
specifier: workspace:*
version: link:../shared
'@fontsource/fira-mono':
specifier: ^5.0.8
version: 5.0.8
copy-to-clipboard:
specifier: ^3.3.3
version: 3.3.3
file-saver:
specifier: ^2.0.5
version: 2.0.5
cryptgeon:
specifier: workspace:*
version: link:../cli
occulto:
specifier: ^2.0.3
version: 2.0.3
specifier: ^2.0.6
version: 2.0.6
pretty-bytes:
specifier: ^6.1.1
version: 6.1.1
@@ -98,9 +89,6 @@ importers:
'@sveltejs/vite-plugin-svelte':
specifier: ^3.0.2
version: 3.0.2(svelte@4.2.12)(vite@5.1.7(@types/node@22.5.0))
'@types/file-saver':
specifier: ^2.0.7
version: 2.0.7
'@zerodevx/svelte-toast':
specifier: ^0.9.5
version: 0.9.5(svelte@4.2.12)
@@ -135,16 +123,6 @@ importers:
specifier: ^1.18.1
version: 1.18.1
packages/shared:
dependencies:
occulto:
specifier: ^2.0.3
version: 2.0.3
devDependencies:
typescript:
specifier: ^5.3.3
version: 5.3.3
packages:
'@ampproject/remapping@2.3.0':
@@ -242,10 +220,10 @@ packages:
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
engines: {node: '>=6.9.0'}
'@commander-js/extra-typings@12.0.1':
resolution: {integrity: sha512-OvkMobb1eMqOCuJdbuSin/KJkkZr7n24/UNV+Lcz/0Dhepf3r2p9PaGwpRpAWej7A+gQnny4h8mGhpFl4giKkg==}
'@commander-js/extra-typings@12.1.0':
resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==}
peerDependencies:
commander: ~12.0.0
commander: ~12.1.0
'@esbuild/aix-ppc64@0.19.12':
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
@@ -778,14 +756,12 @@ packages:
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/file-saver@2.0.7':
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
'@types/inquirer@9.0.7':
resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==}
'@types/mime@3.0.4':
resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
'@types/mime@4.0.0':
resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==}
deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed.
'@types/node@20.11.24':
resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==}
@@ -980,8 +956,8 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
commander@12.0.0:
resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==}
commander@12.1.0:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
commander@4.1.1:
@@ -1002,9 +978,6 @@ packages:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
cross-spawn@6.0.5:
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
engines: {node: '>=4.8'}
@@ -1153,9 +1126,6 @@ packages:
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
engines: {node: '>=8'}
file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@@ -1618,9 +1588,9 @@ packages:
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
engines: {node: '>= 0.4'}
occulto@2.0.3:
resolution: {integrity: sha512-rLXDNqsxM3Gusp4cn8QsveeKsmX36SbnIUcUc3mSeM88pGsNY5Tn6VnWItw/7zST01z9gtaZWQFk0F1L53jMCQ==}
engines: {node: '>=16', npm: please-use-pnpm, pnpm: '>=8', yarn: please-use-pnpm}
occulto@2.0.6:
resolution: {integrity: sha512-oaCwtnQjr+fTfFVfSPEDC5rh+L13OcEJ6uQzhmG8PlLxxYn2MxTDPmoCUVHKD7rUxwxCpH7/N4hbTu5U4mqZag==}
engines: {node: '>=18'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -2079,9 +2049,6 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@@ -2377,9 +2344,9 @@ snapshots:
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
'@commander-js/extra-typings@12.0.1(commander@12.0.0)':
'@commander-js/extra-typings@12.1.0(commander@12.1.0)':
dependencies:
commander: 12.0.0
commander: 12.1.0
'@esbuild/aix-ppc64@0.19.12':
optional: true
@@ -2733,14 +2700,14 @@ snapshots:
'@types/estree@1.0.5': {}
'@types/file-saver@2.0.7': {}
'@types/inquirer@9.0.7':
dependencies:
'@types/through': 0.0.33
rxjs: 7.8.1
'@types/mime@3.0.4': {}
'@types/mime@4.0.0':
dependencies:
mime: 4.0.1
'@types/node@20.11.24':
dependencies:
@@ -2942,7 +2909,7 @@ snapshots:
color-name@1.1.4: {}
commander@12.0.0: {}
commander@12.1.0: {}
commander@4.1.1: {}
@@ -2954,10 +2921,6 @@ snapshots:
cookie@0.6.0: {}
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
cross-spawn@6.0.5:
dependencies:
nice-try: 1.0.5
@@ -3190,8 +3153,6 @@ snapshots:
dependencies:
escape-string-regexp: 1.0.5
file-saver@2.0.5: {}
fill-range@7.0.1:
dependencies:
to-regex-range: 5.0.1
@@ -3623,7 +3584,7 @@ snapshots:
has-symbols: 1.0.3
object-keys: 1.1.1
occulto@2.0.3: {}
occulto@2.0.6: {}
once@1.4.0:
dependencies:
@@ -4117,8 +4078,6 @@ snapshots:
dependencies:
is-number: 7.0.0
toggle-selection@1.0.6: {}
totalist@3.0.1: {}
tr46@1.0.1:

View File

@@ -1,10 +1,11 @@
import { test } from '@playwright/test'
import { basename } from 'node:path'
import { Files, getFileChecksum, rm, tmpFile } from '../../files'
import { rm } from 'node:fs/promises'
import { Files, getFileChecksum, tmpFile } from '../../files'
import { CLI, getLinkFromCLI } from '../../utils'
test.describe('file @cli', () => {
test('simple', async ({ page }) => {
test('simple', async () => {
const file = await tmpFile(Files.Image)
const checksum = await getFileChecksum(file)
const note = await CLI('send', 'file', file)
@@ -17,7 +18,7 @@ test.describe('file @cli', () => {
test.expect(checksum).toBe(c)
})
test('simple with password', async ({ page }) => {
test('simple with password', async () => {
const file = await tmpFile(Files.Image)
const password = 'password'
const checksum = await getFileChecksum(file)

View File

@@ -2,7 +2,7 @@ import { test } from '@playwright/test'
import { CLI, getLinkFromCLI } from '../../utils'
test.describe('text @cli', () => {
test('simple', async ({ page }) => {
test('simple', async () => {
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of decieve play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.`
const note = await CLI('send', 'text', text)
const link = getLinkFromCLI(note.stdout)
@@ -11,7 +11,7 @@ test.describe('text @cli', () => {
test.expect(retrieved.stdout.trim()).toBe(text)
})
test('simple with password', async ({ page }) => {
test('simple with password', async () => {
const text = `Endless prejudice endless play derive joy eternal-return selfish burying.`
const password = 'password'
const note = await CLI('send', 'text', text, '--password', password)

View File

@@ -1,7 +1,8 @@
import { test } from '@playwright/test'
import { CLI, checkLinkForDownload, checkLinkForText, createNote, getLinkFromCLI } from '../../utils'
import { Files, getFileChecksum, rm, tmpFile } from '../../files'
import { basename } from 'path'
import { rm } from 'node:fs/promises'
import { basename } from 'node:path'
import { Files, getFileChecksum, tmpFile } from '../../files'
import { CLI, checkLinkForDownload, createNoteSuccessfully, getLinkFromCLI } from '../../utils'
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of decieve play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.`
const password = 'password'
@@ -30,7 +31,7 @@ test.describe('text @cross', () => {
test('web to cli', async ({ page }) => {
const files = [Files.Image]
const checksum = await getFileChecksum(files[0])
const link = await createNote(page, { files })
const link = await createNoteSuccessfully(page, { files })
const filename = basename(files[0])
await CLI('open', link, '--all')
@@ -42,7 +43,7 @@ test.describe('text @cross', () => {
test('web to cli with password', async ({ page }) => {
const files = [Files.Image]
const checksum = await getFileChecksum(files[0])
const link = await createNote(page, { files, password })
const link = await createNoteSuccessfully(page, { files, password })
const filename = basename(files[0])
await CLI('open', link, '--all', '--password', password)

View File

@@ -1,5 +1,5 @@
import { test } from '@playwright/test'
import { CLI, checkLinkForText, createNote, getLinkFromCLI } from '../../utils'
import { CLI, checkLinkForText, createNoteSuccessfully, getLinkFromCLI } from '../../utils'
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of decieve play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.`
const password = 'password'
@@ -13,7 +13,7 @@ test.describe('text @cross', () => {
})
test('web to cli', async ({ page }) => {
const link = await createNote(page, { text })
const link = await createNoteSuccessfully(page, { text })
const retrieved = await CLI('open', link)
test.expect(retrieved.stdout.trim()).toBe(text)
})
@@ -25,7 +25,7 @@ test.describe('text @cross', () => {
})
test('web to cli with password', async ({ page }) => {
const link = await createNote(page, { text, password })
const link = await createNoteSuccessfully(page, { text, password })
const retrieved = await CLI('open', link, '--password', password)
test.expect(retrieved.stdout.trim()).toBe(text)
})

View File

@@ -1,10 +1,5 @@
import { createHash } from 'crypto'
import { cp as cpFN, rm as rmFN } from 'fs'
import { readFile } from 'fs/promises'
import { promisify } from 'util'
export const cp = promisify(cpFN)
export const rm = promisify(rmFN)
import { createHash } from 'node:crypto'
import { cp, readFile } from 'node:fs/promises'
export const Files = {
PDF: 'test/assets/AES.pdf',

View File

@@ -6,17 +6,22 @@ import { getFileChecksum } from './files'
const exec = promisify(execFile)
type CreatePage = {
text?: string
files?: string[]
views?: number
expiration?: number
error?: string
password?: string
}
export async function createNote(page: Page, options: CreatePage): Promise<string> {
} & (
| {
text: string
}
| {
files: string[]
}
)
async function createNote(page: Page, options: CreatePage): Promise<void> {
await page.goto('/')
if (options.text) {
if ('text' in options) {
await page.getByTestId('text-field').fill(options.text)
} else if (options.files) {
await page.getByTestId('switch-file').click()
@@ -31,7 +36,8 @@ export async function createNote(page: Page, options: CreatePage): Promise<strin
if (options.views || options.expiration || options.password) await page.getByTestId('switch-advanced').click()
if (options.views) {
await page.getByTestId('field-views').fill(options.views.toString())
} else if (options.expiration) {
}
if (options.expiration) {
await page.getByTestId('switch-advanced-toggle').click()
await page.getByTestId('field-expiration').fill(options.expiration.toString())
}
@@ -41,15 +47,18 @@ export async function createNote(page: Page, options: CreatePage): Promise<strin
}
await page.locator('button:has-text("create")').click()
}
if (options.error) {
await expect(page.locator('.error-text')).toContainText(options.error, { timeout: 60_000 })
}
// Return share link
export async function createNoteSuccessfully(page: Page, options: CreatePage): Promise<string> {
await createNote(page, options)
return await page.getByTestId('share-link').inputValue()
}
export async function createNoteError(page: Page, options: CreatePage, error: string): Promise<void> {
await createNote(page, options)
await expect(page.locator('._toastContainer')).toContainText(error)
}
type CheckLinkBase = {
link: string
text: string
@@ -69,7 +78,7 @@ export async function checkLinkForDownload(page: Page, options: CheckLinkBase &
const path = await download.path()
if (!path) throw new Error('Download failed')
const cs = await getFileChecksum(path)
await expect(cs).toBe(options.checksum)
expect(cs).toBe(options.checksum)
}
export async function checkLinkForText(page: Page, options: CheckLinkBase) {
@@ -78,7 +87,7 @@ export async function checkLinkForText(page: Page, options: CheckLinkBase) {
if (options.password) await page.getByTestId('show-note-password').fill(options.password)
await page.getByTestId('show-note-button').click()
const text = await page.getByTestId('result').locator('.note').innerText()
await expect(text).toContain(options.text)
expect(text).toContain(options.text)
}
export async function checkLinkDoesNotExist(page: Page, link: string) {

View File

@@ -1,12 +1,12 @@
import { test } from '@playwright/test'
import { Files, getFileChecksum } from '../../files'
import { checkLinkForDownload, createNote } from '../../utils'
import { checkLinkForDownload, createNoteSuccessfully } from '../../utils'
test.describe('@web', () => {
test('multiple', async ({ page }) => {
const files = [Files.PDF, Files.Image]
const checksums = await Promise.all(files.map(getFileChecksum))
const link = await createNote(page, { files, views: 2 })
const link = await createNoteSuccessfully(page, { files, views: 2 })
await checkLinkForDownload(page, { link, text: 'image.jpg', checksum: checksums[1] })
await checkLinkForDownload(page, { link, text: 'AES.pdf', checksum: checksums[0] })
})

View File

@@ -1,11 +1,11 @@
import { test } from '@playwright/test'
import { Files, getFileChecksum } from '../../files'
import { checkLinkDoesNotExist, checkLinkForDownload, checkLinkForText, createNote } from '../../utils'
import { checkLinkDoesNotExist, checkLinkForDownload, checkLinkForText, createNoteSuccessfully } from '../../utils'
test.describe('@web', () => {
test('simple pdf', async ({ page }) => {
const files = [Files.PDF]
const link = await createNote(page, { files })
const link = await createNoteSuccessfully(page, { files })
await checkLinkForText(page, { link, text: 'AES.pdf' })
await checkLinkDoesNotExist(page, link)
})
@@ -13,21 +13,21 @@ test.describe('@web', () => {
test('pdf content', async ({ page }) => {
const files = [Files.PDF]
const checksum = await getFileChecksum(files[0])
const link = await createNote(page, { files })
const link = await createNoteSuccessfully(page, { files })
await checkLinkForDownload(page, { link, text: 'AES.pdf', checksum })
})
test('image content', async ({ page }) => {
const files = [Files.Image]
const checksum = await getFileChecksum(files[0])
const link = await createNote(page, { files })
const link = await createNoteSuccessfully(page, { files })
await checkLinkForDownload(page, { link, text: 'image.jpg', checksum })
})
test('simple pdf with password', async ({ page }) => {
const files = [Files.PDF]
const password = 'password'
const link = await createNote(page, { files, password })
const link = await createNoteSuccessfully(page, { files, password })
await checkLinkForText(page, { link, text: 'AES.pdf', password })
await checkLinkDoesNotExist(page, link)
})

View File

@@ -1,10 +1,10 @@
import { test } from '@playwright/test'
import { createNote } from '../../utils'
import { Files } from '../../files'
import { createNoteError } from '../../utils'
test.describe('@web', () => {
test('to big zip', async ({ page }) => {
test.skip('to big zip', async ({ page }) => {
const files = [Files.Zip]
const link = await createNote(page, { files, error: 'note is to big' })
await createNoteError(page, { files }, 'could not create note. note is too big')
})
})

View File

@@ -1,13 +1,14 @@
import { test } from '@playwright/test'
import { checkLinkDoesNotExist, checkLinkForText, createNote } from '../../utils'
import { checkLinkDoesNotExist, checkLinkForText, createNoteSuccessfully } from '../../utils'
test.describe('@web', () => {
test('1 minute', async ({ page }) => {
test('1 minute', async ({ page, browserName }) => {
test.skip(browserName === 'webkit')
const text = `Virtues value ascetic revaluation sea dead strong burying.`
const minutes = 1
const timeout = minutes * 60_000
test.setTimeout(timeout * 2)
const link = await createNote(page, { text, expiration: minutes })
const link = await createNoteSuccessfully(page, { text, expiration: minutes })
await checkLinkForText(page, { link, text })
await checkLinkForText(page, { link, text })
await page.waitForTimeout(timeout)

View File

@@ -1,17 +1,17 @@
import { test } from '@playwright/test'
import { checkLinkForText, createNote } from '../../utils'
import { checkLinkForText, createNoteSuccessfully } from '../../utils'
test.describe('@web', () => {
test('simple', async ({ page }) => {
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of deceive play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.`
const link = await createNote(page, { text })
const link = await createNoteSuccessfully(page, { text })
await checkLinkForText(page, { link, text })
})
test('simple with password', async ({ page }) => {
const text = 'Foo bar'
const password = '123'
const shareLink = await createNote(page, { text, password })
const shareLink = await createNoteSuccessfully(page, { text, password })
await checkLinkForText(page, { link: shareLink, text, password })
})
})

View File

@@ -1,17 +1,17 @@
import { test } from '@playwright/test'
import { checkLinkDoesNotExist, checkLinkForText, createNote } from '../../utils'
import { checkLinkDoesNotExist, checkLinkForText, createNoteSuccessfully } from '../../utils'
test.describe('@web', () => {
test('only shown once', async ({ page }) => {
const text = `Christian victorious reason suicide dead. Right ultimate gains god hope truth burying selfish society joy. Ultimate.`
const link = await createNote(page, { text })
const link = await createNoteSuccessfully(page, { text })
await checkLinkForText(page, { link, text })
await checkLinkDoesNotExist(page, link)
})
test('view 3 times', async ({ page }) => {
const text = `Justice holiest overcome fearful strong ultimate holiest christianity.`
const link = await createNote(page, { text, views: 3 })
const link = await createNoteSuccessfully(page, { text, views: 3 })
await checkLinkForText(page, { link, text })
await checkLinkForText(page, { link, text })
await checkLinkForText(page, { link, text })