* move to packages

* update deps

* update deps

* actions maintenance

* don't use blob

* cli

* fix default import

* use synthetic default imports

* remove comment

* cli packaging

* node 18 guard

* packages

* build system

* testing

* test pipeline

* pipelines

* changelog

* version bump

* update locales

* update deps

* update deps

* update dependecies
This commit is contained in:
2023-05-14 13:52:47 +02:00
committed by GitHub
parent 13dfd933af
commit d7e5a34b14
69 changed files with 2839 additions and 1293 deletions

View File

@@ -1 +0,0 @@
engine-strict=true

View File

@@ -41,7 +41,7 @@
},
"explanation": "pulse abajo para mostrar y borrar la nota si el contador ha llegado a su límite",
"show_note": "mostrar nota",
"warning_will_not_see_again": " <b>no</b> tendrás la oportunidad de volver a ver la nota.",
"warning_will_not_see_again": "<b>no</b> tendrás la oportunidad de volver a ver la nota.",
"download_all": "descargar todo"
},
"file_upload": {

View File

@@ -41,7 +41,7 @@
},
"explanation": "clicca sotto per mostrare e cancellare la nota se il contatore ha raggiunto il suo limite",
"show_note": "mostra la nota",
"warning_will_not_see_again": " <b>non</b> avrete la possibilità di rivedere la nota.",
"warning_will_not_see_again": "<b>non</b> avrete la possibilità di rivedere la nota.",
"download_all": "scarica tutti"
},
"file_upload": {

View File

@@ -0,0 +1,51 @@
{
"common": {
"note": "新しいメモ",
"file": "ファイル",
"advanced": "アドバンスド",
"create": "作成",
"loading": "読み込み中",
"mode": "モード",
"views": "{n, plural, =0 {表示可能な時間} =1 { 1 ビュー} other {#ビュー}}",
"minutes": "{n, plural, =0 {有効期間(分)} =1 {1 分} other {# 分}}",
"max": "マックス",
"share_link": "共有リンク",
"copy_clipboard": "クリップボードにコピーする",
"copied_to_clipboard": "クリップボードにコピーされました",
"encrypting": "暗号化",
"decrypting": "復号化",
"uploading": "アップロード中",
"downloading": "ダウンロード中",
"qr_code": "QRコード"
},
"home": {
"intro": "<i>完全に暗号化された</i> 、安全なメモやファイルをワンクリックで簡単に送信できます。メモを作成してリンクを共有するだけです。",
"explanation": "メモは{type}後に期限切れになり、破棄されます。",
"new_note": "新しいメモ",
"new_note_notice": "<b>可用性: </b> <br />すべてが RAM に保持されるため、メモが保存されるとは限りません。いっぱいになると、最も古いメモが削除されます。 <br /> (大丈夫だと思いますが、ご了承ください。)",
"errors": {
"note_to_big": "メモを作成できませんでした。メモが大きすぎる",
"note_error": "メモを作成できませんでした。もう一度お試しください。",
"max": "最大ファイルサイズ: {n}",
"empty_content": "メモは空です。"
},
"messages": {
"note_created": "メモが作成されました。"
}
},
"show": {
"errors": {
"not_found": "メモが見つからないか、既に削除されています。",
"decryption_failed": "パスワードエラー!不完全なリンクを貼り付けたり、暗号を解読しようとしたりしている可能性があります!しかし、いずれにしても、この暗号は破棄されました!",
"unsupported_type": "サポートされていないメモ タイプです。"
},
"explanation": "カウンターが上限に達した場合、ノートの表示と削除を行うには、以下をクリックします。",
"show_note": "メモを表示",
"warning_will_not_see_again": "あなた <b>できません</b> このノートをもう一度見る",
"download_all": "すべてダウンロード"
},
"file_upload": {
"selected_files": "選択したファイル",
"no_files_selected": "ファイルが選択されていません"
}
}

View File

@@ -1,51 +1,51 @@
{
"common": {
"note": "заметка",
"file": "файл",
"advanced": "расширенные",
"create": "создать",
"loading": "загрузка",
"mode": "режим",
"views": "{n, plural, =0 {просмотры} =1 {1 просмотр} other {# просмотры}}",
"minutes": "{n, plural, =0 {минут} =1 {1 минута} other {# минуты}}",
"max": "макс",
"share_link": "поделиться ссылкой",
"copy_clipboard": "скопировать в буфер обмена",
"copied_to_clipboard": "скопировано в буфер обмена",
"encrypting": "шифрование",
"decrypting": "расшифровка",
"uploading": "загрузка",
"downloading": "скачивание",
"qr_code": "qr код"
},
"home": {
"intro": "Легко отправляйте <i>полностью зашифрованные</i> защищенные заметки или файлы одним щелчком мыши. Просто создайте заметку и поделитесь ссылкой.",
"explanation": "заметка истечет и будет уничтожена после {type}.",
"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": "нельзя создать новую заметку. заметка слишком большая",
"note_error": "нельзя создать новую заметку. пожалйста попробуйте позднее.",
"max": "макс: {n}",
"empty_content": "пустая заметка."
},
"messages": {
"note_created": "заметка создана."
}
},
"show": {
"errors": {
"not_found": "заметка не найдена или была удалена.",
"decryption_failed": "неправильный пароль. не смог расшифровать. возможно ссылка битая. записка уничтожена.",
"unsupported_type": "неподдерживаемый тип заметки."
},
"explanation": "щелкните ниже, чтобы показать и удалить примечание, если счетчик достиг предела",
"show_note": "показать заметку",
"warning_will_not_see_again": "вы <b>не сможете</b> больше просмотреть заметку.",
"download_all": "скачать всё"
},
"file_upload": {
"selected_files": "Выбранные файлы",
"no_files_selected": "Файлы не выбраны"
}
}
{
"common": {
"note": "заметка",
"file": "файл",
"advanced": "расширенные",
"create": "создать",
"loading": "загрузка",
"mode": "режим",
"views": "{n, plural, =0 {просмотры} =1 {1 просмотр} other {# просмотры}}",
"minutes": "{n, plural, =0 {минут} =1 {1 минута} other {# минуты}}",
"max": "макс",
"share_link": "поделиться ссылкой",
"copy_clipboard": "скопировать в буфер обмена",
"copied_to_clipboard": "скопировано в буфер обмена",
"encrypting": "шифрование",
"decrypting": "расшифровка",
"uploading": "загрузка",
"downloading": "скачивание",
"qr_code": "qr код"
},
"home": {
"intro": "Легко отправляйте <i>полностью зашифрованные</i> защищенные заметки или файлы одним щелчком мыши. Просто создайте заметку и поделитесь ссылкой.",
"explanation": "заметка истечет и будет уничтожена после {type}.",
"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": "нельзя создать новую заметку. заметка слишком большая",
"note_error": "нельзя создать новую заметку. пожалйста попробуйте позднее.",
"max": "макс: {n}",
"empty_content": "пустая заметка."
},
"messages": {
"note_created": "заметка создана."
}
},
"show": {
"errors": {
"not_found": "заметка не найдена или была удалена.",
"decryption_failed": "неправильный пароль. не смог расшифровать. возможно ссылка битая. записка уничтожена.",
"unsupported_type": "неподдерживаемый тип заметки."
},
"explanation": "щелкните ниже, чтобы показать и удалить примечание, если счетчик достиг предела",
"show_note": "показать заметку",
"warning_will_not_see_again": "вы <b>не сможете</b> больше просмотреть заметку.",
"download_all": "скачать всё"
},
"file_upload": {
"selected_files": "Выбранные файлы",
"no_files_selected": "Файлы не выбраны"
}
}

View File

@@ -48,4 +48,4 @@
"selected_files": "已选中的文件",
"no_files_selected": "没有文件被选中"
}
}
}

View File

@@ -1,6 +1,8 @@
{
"private": true,
"name": "@cryptgeon/web",
"scripts": {
"postinstall": "svelte-kit sync",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
@@ -11,29 +13,29 @@
},
"type": "module",
"devDependencies": {
"@lokalise/node-api": "^9.5.0",
"@sveltejs/adapter-static": "^1.0.2",
"@sveltejs/kit": "^1.1.0",
"@types/dompurify": "^2.4.0",
"@lokalise/node-api": "^9.8.0",
"@sveltejs/adapter-static": "^2.0.2",
"@sveltejs/kit": "^1.16.3",
"@types/dompurify": "^3.0.2",
"@types/file-saver": "^2.0.5",
"@zerodevx/svelte-toast": "^0.7.2",
"@zerodevx/svelte-toast": "^0.9.3",
"adm-zip": "^0.5.10",
"dotenv": "^16.0.3",
"svelte": "^3.55.1",
"svelte-check": "^2.10.3",
"svelte-intl-precompile": "^0.10.1",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.4.1",
"typescript": "^4.9.4",
"vite": "^4.0.4"
"svelte": "^3.59.1",
"svelte-check": "^3.3.2",
"svelte-intl-precompile": "^0.12.1",
"tslib": "^2.5.0",
"typescript": "^5.0.4",
"vite": "^4.3.5"
},
"dependencies": {
"@cryptgeon/shared": "workspace:*",
"@fontsource/fira-mono": "^4.5.10",
"copy-to-clipboard": "^3.3.3",
"dompurify": "^2.4.3",
"dompurify": "^3.0.3",
"file-saver": "^2.0.5",
"occulto": "2.0.0",
"pretty-bytes": "^6.0.0",
"occulto": "^2.0.1",
"pretty-bytes": "^6.1.0",
"qrious": "^4.0.2"
}
}

View File

@@ -5,10 +5,15 @@ import https from 'https'
dotenv.config()
function exit(msg) {
console.error(msg)
process.exit(1)
}
const apiKey = process.env.LOKALISE_API_KEY
const project_id = process.env.LOKALISE_PROJECT
if (!apiKey) throw new Error('No API Key set for Lokalize! Set with "LOKALISE_API_KEY"')
if (!project_id) throw new Error('No project id set for Lokalize! Set with "LOKALISE_PROJECT"')
if (!apiKey) exit('No API Key set for Lokalize! Set with "LOKALISE_API_KEY"')
if (!project_id) exit('No project id set for Lokalize! Set with "LOKALISE_PROJECT"')
const client = new LokaliseApi({ apiKey })
const WGet = (url) =>

9
packages/frontend/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}

View File

@@ -1,3 +0,0 @@
/// <reference types="@sveltejs/kit" />
/// <reference types="svelte" />
/// <reference types="vite/client" />

View File

@@ -1,60 +0,0 @@
import { AES, Bytes, type TypedArray } from 'occulto'
import type { EncryptedFileDTO, FileDTO } from './api'
abstract class CryptAdapter<T> {
abstract encrypt(plaintext: T, key: TypedArray): Promise<string>
abstract decrypt(ciphertext: string, key: TypedArray): Promise<T>
}
class CryptTextAdapter implements CryptAdapter<string> {
async encrypt(plaintext: string, key: TypedArray) {
return await AES.encrypt(Bytes.encode(plaintext), key)
}
async decrypt(ciphertext: string, key: TypedArray) {
return Bytes.decode(await AES.decrypt(ciphertext, key))
}
}
class CryptBlobAdapter implements CryptAdapter<Blob> {
async encrypt(plaintext: Blob, key: TypedArray) {
return await AES.encrypt(new Uint8Array(await plaintext.arrayBuffer()), key)
}
async decrypt(ciphertext: string, key: TypedArray) {
const plaintext = await AES.decrypt(ciphertext, key)
return new Blob([plaintext], { type: 'application/octet-stream' })
}
}
class CryptFilesAdapter implements CryptAdapter<FileDTO[]> {
async encrypt(plaintext: FileDTO[], key: TypedArray) {
const adapter = new CryptBlobAdapter()
const data: Promise<EncryptedFileDTO>[] = plaintext.map(async (file) => ({
name: file.name,
size: file.size,
type: file.type,
contents: await adapter.encrypt(file.contents, key),
}))
return JSON.stringify(await Promise.all(data))
}
async decrypt(ciphertext: string, key: TypedArray) {
const adapter = new CryptBlobAdapter()
const data: EncryptedFileDTO[] = JSON.parse(ciphertext)
const files: FileDTO[] = await Promise.all(
data.map(async (file) => ({
name: file.name,
size: file.size,
type: file.type,
contents: await adapter.decrypt(file.contents, key),
}))
)
return files
}
}
export const Adapters = {
Text: new CryptTextAdapter(),
Blob: new CryptBlobAdapter(),
Files: new CryptFilesAdapter(),
}

View File

@@ -1,78 +0,0 @@
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: Blob
}
export type EncryptedFileDTO = Omit<FileDTO, 'contents'> & {
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
}

View File

@@ -1,24 +1,8 @@
import { call } from '$lib/api'
import { status as getStatus, type Status } from '@cryptgeon/shared'
import { writable } from 'svelte/store'
export type Status = {
version: string
max_size: number
max_views: number
max_expiration: number
allow_advanced: boolean
theme_image: string
theme_text: string
theme_favicon: string
theme_page_title: string
}
export const status = writable<null | Status>(null)
export async function init() {
const data = await call({
url: 'status/',
method: 'get',
})
status.set(data)
status.set(await getStatus())
}

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { t } from 'svelte-intl-precompile'
import type { Note } from '$lib/api'
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'
export let note: Note
export let timeExpiration = false

View File

@@ -1,4 +1,5 @@
<script lang="ts">
// @ts-ignore
import QR from 'qrious'
import { t } from 'svelte-intl-precompile'

View File

@@ -1,26 +1,27 @@
<script lang="ts">
import { t } from 'svelte-intl-precompile'
import type { FileDTO } from '$lib/api'
import Button from '$lib/ui/Button.svelte'
import MaxSize from '$lib/ui/MaxSize.svelte'
import type { FileDTO } from '@cryptgeon/shared'
export let label: string = ''
export let files: FileDTO[] = []
function fileToDTO(file: File): FileDTO {
async function fileToDTO(file: File): Promise<FileDTO> {
return {
name: file.name,
size: file.size,
type: file.type,
contents: file,
contents: new Uint8Array(await file.arrayBuffer()),
}
}
async function onInput(e: Event) {
const input = e.target as HTMLInputElement
if (input?.files?.length) {
files = [...files, ...Array.from(input.files).map(fileToDTO)]
const toAdd = await Promise.all(Array.from(input.files).map(fileToDTO))
files = [...files, ...toAdd]
}
}

View File

@@ -8,9 +8,9 @@
import prettyBytes from 'pretty-bytes'
import { t } from 'svelte-intl-precompile'
import type { FileDTO, NotePublic } from '$lib/api'
import Button from '$lib/ui/Button.svelte'
import { copy } from '$lib/utils'
import type { FileDTO, NotePublic } from '@cryptgeon/shared'
export let note: DecryptedNote

View File

@@ -3,9 +3,6 @@
import { t } from 'svelte-intl-precompile'
import { blur } from 'svelte/transition'
import { Adapters } from '$lib/adapters'
import type { FileDTO, Note } from '$lib/api'
import { create, PayloadToLargeError } from '$lib/api'
import { status } from '$lib/stores/status'
import { notify } from '$lib/toast'
import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte'
@@ -16,6 +13,8 @@
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, create, PayloadToLargeError } from '@cryptgeon/shared'
let note: Note = {
contents: '',
@@ -59,7 +58,7 @@
loading = $t('common.encrypting')
const key = await AES.generateKey()
const password = await Hex.encode(key)
const password = Hex.encode(key)
const data: Note = {
contents: '',

View File

@@ -3,11 +3,10 @@
import { onMount } from 'svelte'
import { t } from 'svelte-intl-precompile'
import { Adapters } from '$lib/adapters'
import { get, info } from '$lib/api'
import Button from '$lib/ui/Button.svelte'
import Loader from '$lib/ui/Loader.svelte'
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
import { Adapters, get, info } from '@cryptgeon/shared'
import type { PageData } from './$types'
export let data: PageData
@@ -43,7 +42,7 @@
loading = $t('common.downloading')
const data = await get(id)
loading = $t('common.decrypting')
const key = await Hex.decode(password)
const key = Hex.decode(password)
switch (data.meta.type) {
case 'text':
note = {

View File

@@ -1,9 +1,9 @@
import adapter from '@sveltejs/adapter-static'
import preprocess from 'svelte-preprocess'
import precompileIntl from 'svelte-intl-precompile/sveltekit-plugin'
import { vitePreprocess } from '@sveltejs/kit/vite'
export default {
preprocess: preprocess(),
preprocess: vitePreprocess([precompileIntl('locales')]),
kit: {
adapter: adapter({
fallback: 'index.html',