add deletion timer, other small fixes
Massets/default.css
| @@ -73,3 +73,16 @@ input[type=reset] { | |||
|---|---|---|---|
| 73 | 73 | max-width: 25rem; | |
| 74 | 74 | align-self: center; | |
| 75 | 75 | } | |
| 76 | + | ||
| 77 | + | ||
| 78 | + | dialog { | |
| 79 | + | background-color: #120c0e; | |
| 80 | + | color: #d9d8dc; | |
| 81 | + | border-color: #eb99a1; | |
| 82 | + | ||
| 83 | + | form { | |
| 84 | + | display: flex; | |
| 85 | + | flex-direction: column; | |
| 86 | + | align-items: center; | |
| 87 | + | } | |
| 88 | + | } | |
Mbun.lock
| @@ -5,8 +5,10 @@ | |||
|---|---|---|---|
| 5 | 5 | "name": "zigbin", | |
| 6 | 6 | "dependencies": { | |
| 7 | 7 | "@chneau/elysia-compression": "^1.0.11", | |
| 8 | + | "@elysiajs/cron": "^1.2.0", | |
| 9 | + | "@elysiajs/html": "^1.2.0", | |
| 8 | 10 | "@elysiajs/static": "^1.2.0", | |
| 9 | - | "elysia": "latest", | |
| 11 | + | "elysia": "^1.2.0", | |
| 10 | 12 | "file-type": "^20.0.1", | |
| 11 | 13 | "highlight.js": "^11.11.1", | |
| 12 | 14 | "pkcs7-padding": "^0.1.1", | |
| @@ -22,8 +24,16 @@ | |||
|---|---|---|---|
| 22 | 24 | "packages": { | |
| 23 | 25 | "@chneau/elysia-compression": ["@chneau/elysia-compression@1.0.11", "", { "dependencies": { "elysia": "^1.1.9" } }, "sha512-J4wfz5Qs68p/DNvkg5DXxo8sxJKSibzaUBP4W56uvqgs9NtHG0LC6tnyv7sAPEjNKzh21l6kinMoXbtVDp/KSQ=="], | |
| 24 | 26 | ||
| 27 | + | "@elysiajs/cron": ["@elysiajs/cron@1.2.0", "", { "dependencies": { "croner": "^6.0.3" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-Xg2bUCZNEp2zRzsYeNQ6BUP3uslF5BEPnuLHLnsytiAEqzFTHzS7813XbmHs8xNZKuUAkAnJNTVnQb1Jt5zTwg=="], | |
| 28 | + | ||
| 29 | + | "@elysiajs/html": ["@elysiajs/html@1.2.0", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-j/exB9TLn2/KOMWRfPcJ7MNOsmr85uHRjC+kKPLyb1lRtNDWzgab4deUQrxtIv4gt4PDSKmywNnhyD+GrdnH3w=="], | |
| 30 | + | ||
| 25 | 31 | "@elysiajs/static": ["@elysiajs/static@1.2.0", "", { "dependencies": { "node-cache": "^5.1.2" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-oLpAi8c+maPpA0XhhK3BELaIjIG+nXg/K9p8cFfW4q5ayRD59a3MOMOOGgpiXZkHJzLPWcouhhyyLAYtaANW4g=="], | |
| 26 | 32 | ||
| 33 | + | "@kitajs/html": ["@kitajs/html@4.2.7", "", { "dependencies": { "csstype": "^3.1.3" } }, "sha512-HMAHy0GLH7i7wY1o0W1TeugNhtWefUkhb9l6zx6v2fIkR1cPlUtQQdkTt/jkBA7723yO7uBERcXFPZpWT7HWPQ=="], | |
| 34 | + | ||
| 35 | + | "@kitajs/ts-html-plugin": ["@kitajs/ts-html-plugin@4.1.1", "", { "dependencies": { "chalk": "^4.1.2", "tslib": "^2.8.1", "yargs": "^17.7.2" }, "peerDependencies": { "@kitajs/html": "^4.2.5", "typescript": "^5.6.2" }, "bin": { "ts-html-plugin": "dist/cli.js", "xss-scan": "dist/cli.js" } }, "sha512-wmjyV8hmJmDOnUM/ZyPkc0UBYgUYmf32/93rkW8wr8h+HiHVMU0tEKFnmRdBjTcy9jwoC9Bnt2NuzS9l67lq5g=="], | |
| 36 | + | ||
| 27 | 37 | "@sinclair/typebox": ["@sinclair/typebox@0.34.15", "", {}, "sha512-xeIzl3h1Znn9w/LTITqpiwag0gXjA+ldi2ZkXIBxGEppGCW211Tza+eL6D4pKqs10bj5z2umBWk5WL6spQ2OCQ=="], | |
| 28 | 38 | ||
| 29 | 39 | "@tokenizer/inflate": ["@tokenizer/inflate@0.2.6", "", { "dependencies": { "debug": "^4.3.7", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-SdR/i05U7Xhnsq36iyIq/ZiGGw4PKzw4ww3bOq80Pjj4wyXpqyTcgrgdDdGlcatnlvzNJx8CQw3hp6QZvkUwhA=="], | |
| @@ -38,24 +48,50 @@ | |||
|---|---|---|---|
| 38 | 48 | ||
| 39 | 49 | "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], | |
| 40 | 50 | ||
| 51 | + | "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], | |
| 52 | + | ||
| 53 | + | "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], | |
| 54 | + | ||
| 41 | 55 | "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], | |
| 42 | 56 | ||
| 57 | + | "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], | |
| 58 | + | ||
| 59 | + | "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], | |
| 60 | + | ||
| 43 | 61 | "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], | |
| 44 | 62 | ||
| 63 | + | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], | |
| 64 | + | ||
| 65 | + | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], | |
| 66 | + | ||
| 45 | 67 | "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], | |
| 46 | 68 | ||
| 69 | + | "croner": ["croner@6.0.7", "", {}, "sha512-k3Xx3Rcclfr60Yx4TmvsF3Yscuiql8LSvYLaphTsaq5Hk8La4Z/udmUANMOTKpgGGroI2F6/XOr9cU9OFkYluQ=="], | |
| 70 | + | ||
| 71 | + | "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], | |
| 72 | + | ||
| 47 | 73 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], | |
| 48 | 74 | ||
| 49 | 75 | "elysia": ["elysia@1.2.10", "", { "dependencies": { "@sinclair/typebox": "^0.34.13", "cookie": "^1.0.2", "memoirist": "^0.2.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-QcNl2FjhHFRpKaqy1NoMpyCjJ7OcKBnHwLUkqGu09QwIV84PFb82ILvYJG4GS1RbGv76OA50luaqBLrM3SLZ2w=="], | |
| 50 | 76 | ||
| 77 | + | "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], | |
| 78 | + | ||
| 79 | + | "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], | |
| 80 | + | ||
| 51 | 81 | "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], | |
| 52 | 82 | ||
| 53 | 83 | "file-type": ["file-type@20.0.1", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-R80W+NZ+s1M8PsC8L7xn+J0N72vEyxstMH+4vwIyt5B7ojfG80h/nKu2Qw/NYJTIYyGUeaPNLnzfJd78SWnG3A=="], | |
| 54 | 84 | ||
| 85 | + | "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], | |
| 86 | + | ||
| 87 | + | "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], | |
| 88 | + | ||
| 55 | 89 | "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], | |
| 56 | 90 | ||
| 57 | 91 | "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], | |
| 58 | 92 | ||
| 93 | + | "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], | |
| 94 | + | ||
| 59 | 95 | "memoirist": ["memoirist@0.2.0", "", {}, "sha512-DA1V11OWsKmYjgYHfT1luus0FtTjUbILfI9s5M+ckK29tBLON6GDhH5GwxDz7E1ou4Bdzm9vhbeCaRAWxwG+0g=="], | |
| 60 | 96 | ||
| 61 | 97 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], | |
| @@ -70,10 +106,20 @@ | |||
|---|---|---|---|
| 70 | 106 | ||
| 71 | 107 | "pkcs7-padding": ["pkcs7-padding@0.1.1", "", {}, "sha512-tM/sUOL5FdH7x6gSLHTNDFi1bAut/EXGFp/Ih8uRkVd2IdXwb5QWnxBgQXn6buadj1pLi3CYKCnrxb+XoTz+Ww=="], | |
| 72 | 108 | ||
| 109 | + | "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], | |
| 110 | + | ||
| 111 | + | "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], | |
| 112 | + | ||
| 113 | + | "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], | |
| 114 | + | ||
| 73 | 115 | "strtok3": ["strtok3@10.2.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^6.1.0" } }, "sha512-S884oIGzokq3LkL/6jXw5c5oJXRiGt4jB42cuWdaooJdxFMSn99snaShh3cQmJx3jALV2eoyW3rV/TJxwOaBPA=="], | |
| 74 | 116 | ||
| 117 | + | "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], | |
| 118 | + | ||
| 75 | 119 | "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="], | |
| 76 | 120 | ||
| 121 | + | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], | |
| 122 | + | ||
| 77 | 123 | "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], | |
| 78 | 124 | ||
| 79 | 125 | "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="], | |
| @@ -81,5 +127,13 @@ | |||
|---|---|---|---|
| 81 | 127 | "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], | |
| 82 | 128 | ||
| 83 | 129 | "utf-8-validate": ["utf-8-validate@6.0.5", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA=="], | |
| 130 | + | ||
| 131 | + | "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], | |
| 132 | + | ||
| 133 | + | "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], | |
| 134 | + | ||
| 135 | + | "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], | |
| 136 | + | ||
| 137 | + | "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], | |
| 84 | 138 | } | |
| 85 | 139 | } | |
Mpackage.json
| @@ -8,8 +8,9 @@ | |||
|---|---|---|---|
| 8 | 8 | }, | |
| 9 | 9 | "dependencies": { | |
| 10 | 10 | "@chneau/elysia-compression": "^1.0.11", | |
| 11 | - | "@elysiajs/static": "^1.2.0", | |
| 11 | + | "@elysiajs/cron": "^1.2.0", | |
| 12 | 12 | "@elysiajs/html": "^1.2.0", | |
| 13 | + | "@elysiajs/static": "^1.2.0", | |
| 13 | 14 | "elysia": "^1.2.0", | |
| 14 | 15 | "file-type": "^20.0.1", | |
| 15 | 16 | "highlight.js": "^11.11.1", | |
Msrc/client-index.ts
| @@ -29,6 +29,7 @@ async function encrypt(content: Uint8Array, password: string): Promise<ArrayBuff | |||
|---|---|---|---|
| 29 | 29 | } | |
| 30 | 30 | ||
| 31 | 31 | async function uploadFile() { | |
| 32 | + | (document.getElementById("upload-dialog") as HTMLDialogElement | null)?.showModal() | |
| 32 | 33 | const passwordInput = document.getElementById("password") as HTMLInputElement | null | |
| 33 | 34 | const fileInput = document.getElementById("file") as HTMLInputElement | null | |
| 34 | 35 | const filenameInput = document.getElementById("filename") as HTMLInputElement | null | |
| @@ -65,12 +66,13 @@ async function uploadFile() { | |||
|---|---|---|---|
| 65 | 66 | form.submit() | |
| 66 | 67 | } | |
| 67 | 68 | ||
| 68 | - | (document.getElementById("uploadForm") as HTMLFormElement | null)?.reset() | |
| 69 | - | ||
| 70 | 69 | const jsguards = document.getElementsByClassName("jsguard") | |
| 71 | 70 | for (let i = 0; i < jsguards.length; i++) { | |
| 72 | 71 | jsguards[i].remove() | |
| 73 | 72 | } | |
| 74 | 73 | ||
| 74 | + | (document.getElementById("uploadForm") as HTMLFormElement | null)?.reset(); | |
| 75 | 75 | window.uploadFile = uploadFile | |
| 76 | - | ||
| 76 | + | //disable bfcache, otherwise dialog will stay open when navigating back | |
| 77 | + | window.addEventListener('unload', function () { }); | |
| 78 | + | window.addEventListener('beforeunload', function () { }); | |
Msrc/components.tsx
| @@ -6,7 +6,7 @@ import hljs from 'highlight.js' | |||
|---|---|---|---|
| 6 | 6 | import isValidUTF8 from 'utf-8-validate' | |
| 7 | 7 | import { humanFileSize } from './shared' | |
| 8 | 8 | ||
| 9 | - | const filetypes = ["none", "blob"].concat(hljs.listLanguages().sort()) | |
| 9 | + | export const filetypes = ["none", "blob"].concat(hljs.listLanguages().sort()) | |
| 10 | 10 | ||
| 11 | 11 | function Template(props: PropsWithChildren<{ css: string }>) { | |
| 12 | 12 | return ( | |
| @@ -38,6 +38,9 @@ export function NotFound() { | |||
|---|---|---|---|
| 38 | 38 | export function Index(hostname: string, withJs: boolean) { | |
| 39 | 39 | let body = ( | |
| 40 | 40 | <> | |
| 41 | + | <dialog id="upload-dialog"> | |
| 42 | + | File is being uploaded, please wait | |
| 43 | + | </dialog> | |
| 41 | 44 | {withJs ? | |
| 42 | 45 | <div style="text-align: center">JS version with client-side encryption, for the version without js an with server-side encryption, go <a href="/">here</a>.</div> | |
| 43 | 46 | : <div style="text-align: center">JS-less version with server-side encryption, for the version with client-side encryption, go to <a href="/js">/js</a>.</div> | |
| @@ -58,6 +61,9 @@ export function Index(hostname: string, withJs: boolean) { | |||
|---|---|---|---|
| 58 | 61 | )) | |
| 59 | 62 | } | |
| 60 | 63 | </select> | |
| 64 | + | <label for="delete-in">Delete in:</label> | |
| 65 | + | <small>Optional. If set, file will be deleted after the given number of minutes.</small> | |
| 66 | + | <input type="number" id="delete-in" name="delete_in_minutes" min="0" placeholder="0" /> | |
| 61 | 67 | <label for="password">Password:</label> | |
| 62 | 68 | <small>Optional. If set, file will be encrypted and requires the given password to open.</small> | |
| 63 | 69 | <input type="password" id="password" name="password" placeholder="Password" /> | |
| @@ -77,6 +83,8 @@ export function Index(hostname: string, withJs: boolean) { | |||
|---|---|---|---|
| 77 | 83 | # optional{"\n"} | |
| 78 | 84 | -F filename="file name" \{"\n"} | |
| 79 | 85 | # optional{"\n"} | |
| 86 | + | -F delete_in_minutes="60" \{"\n"} | |
| 87 | + | # optional{"\n"} | |
| 80 | 88 | -F password="mypassword" \{"\n"} | |
| 81 | 89 | # optional{"\n"} | |
| 82 | 90 | -F encrypted="on"</pre> | |
| @@ -137,7 +145,24 @@ export function WrongPassword() { | |||
|---|---|---|---|
| 137 | 145 | ) | |
| 138 | 146 | } | |
| 139 | 147 | ||
| 140 | - | export async function ShowFile(filename: string, uuid: string, content: Uint8Array | null, filetype: string) { | |
| 148 | + | function humanReadableTime(minutes: number) { | |
| 149 | + | const years = Math.floor(minutes / 525600); | |
| 150 | + | minutes %= 525600; | |
| 151 | + | const days = Math.floor(minutes / 1440); | |
| 152 | + | minutes %= 1440; | |
| 153 | + | const hours = Math.floor(minutes / 60); | |
| 154 | + | minutes %= 60; | |
| 155 | + | ||
| 156 | + | let result = ""; | |
| 157 | + | if (years > 0) result += `${years} year${years > 1 ? "s" : ""} ` | |
| 158 | + | if (days > 0) result += `${days} day${days > 1 ? "s" : ""} ` | |
| 159 | + | if (hours > 0) result += `${hours} hour${hours > 1 ? "s" : ""} ` | |
| 160 | + | if (minutes > 0) result += `${minutes} minute${minutes > 1 ? "s" : ""} ` | |
| 161 | + | if (result === "") result = "0 minutes" | |
| 162 | + | return result.trim(); | |
| 163 | + | } | |
| 164 | + | ||
| 165 | + | export async function ShowFile(filename: string, uuid: string, content: Uint8Array | null, filetype: string, delete_at: number | null) { | |
| 141 | 166 | //if content is null, this is for the js frontend | |
| 142 | 167 | let preview; | |
| 143 | 168 | if (!content) { | |
| @@ -183,6 +208,7 @@ export async function ShowFile(filename: string, uuid: string, content: Uint8Arr | |||
|---|---|---|---|
| 183 | 208 | <hr /> | |
| 184 | 209 | <p id="filesize">size: {content ? humanFileSize(content.byteLength) : "unknown"}</p> | |
| 185 | 210 | <p>declared type: {filetype}</p> | |
| 211 | + | {delete_at ? <p>delete at: {new Date(delete_at * 1000).toISOString()} (in {humanReadableTime(Math.floor((delete_at - (Date.now() / 1000)) / 60))})</p> : ""} | |
| 186 | 212 | <form id="download-form" action={`/raw/${uuid}`} method="get"> | |
| 187 | 213 | <button>Download</button> | |
| 188 | 214 | </form> | |
Msrc/index.ts
| @@ -4,13 +4,16 @@ import { randomUUIDv7 } from "bun" | |||
|---|---|---|---|
| 4 | 4 | import staticPlugin from "@elysiajs/static" | |
| 5 | 5 | import { html } from '@elysiajs/html' | |
| 6 | 6 | import crypto from 'crypto' | |
| 7 | - | import { DecryptFile, Index, NotFound, SetCookie, ShowFile, WrongPassword } from "./components" | |
| 7 | + | import { DecryptFile, filetypes, Index, NotFound, SetCookie, ShowFile, WrongPassword } from "./components" | |
| 8 | + | import cron from "@elysiajs/cron" | |
| 8 | 9 | ||
| 9 | 10 | ||
| 11 | + | //TODO: add deletion timer | |
| 12 | + | ||
| 10 | 13 | const db = new Database("./db.sqlite") | |
| 11 | 14 | db.exec("PRAGMA foreign_keys = ON") | |
| 12 | 15 | db.exec("PRAGMA journal_mode = WAL2") | |
| 13 | - | db.exec("CREATE TABLE IF NOT EXISTS files (uuid TEXT PRIMARY KEY, filename TEXT NOT NULL, content BLOB NOT NULL, filetype TEXT NOT NULL, encrypted INTEGER NOT NULL) STRICT") | |
| 16 | + | db.exec("CREATE TABLE IF NOT EXISTS files (uuid TEXT PRIMARY KEY, filename TEXT NOT NULL, content BLOB NOT NULL, filetype TEXT NOT NULL, encrypted INTEGER NOT NULL, delete_at INTEGER) STRICT") | |
| 14 | 17 | db.exec("PRAGMA optimize") | |
| 15 | 18 | ||
| 16 | 19 | ||
| @@ -32,6 +35,15 @@ function decrypt(encrypted_content: Uint8Array, password: string): Buffer { | |||
|---|---|---|---|
| 32 | 35 | return content | |
| 33 | 36 | } | |
| 34 | 37 | ||
| 38 | + | function stringArrayToEnum<T extends string>( | |
| 39 | + | arr: readonly T[] | |
| 40 | + | ): { [K in T]: K } { | |
| 41 | + | return arr.reduce((acc, key) => { | |
| 42 | + | acc[key] = key; | |
| 43 | + | return acc; | |
| 44 | + | }, Object.create(null)); | |
| 45 | + | } | |
| 46 | + | ||
| 35 | 47 | const app = new Elysia({ | |
| 36 | 48 | serve: { | |
| 37 | 49 | maxRequestBodySize: 1024 * 1024 * 1024 // 1GB | |
| @@ -39,6 +51,13 @@ const app = new Elysia({ | |||
|---|---|---|---|
| 39 | 51 | }) | |
| 40 | 52 | .use(staticPlugin({ assets: "./assets", prefix: "/" })) | |
| 41 | 53 | .use(html()) | |
| 54 | + | .use(cron({ | |
| 55 | + | name: "delete", | |
| 56 | + | pattern: "*/5 * * * * *", | |
| 57 | + | run() { | |
| 58 | + | db.exec("DELETE FROM files WHERE delete_at < strftime('%s', 'now')") | |
| 59 | + | } | |
| 60 | + | })) | |
| 42 | 61 | .get("/", ({ server }) => Index(server?.url.toString()!, false)) | |
| 43 | 62 | .get("/js", ({ server }) => Index(server?.url.toString()!, true)) | |
| 44 | 63 | .post("/upload", async ({ set, body, query }) => { | |
| @@ -46,13 +65,18 @@ const app = new Elysia({ | |||
|---|---|---|---|
| 46 | 65 | const uuid = randomUUIDv7() | |
| 47 | 66 | let content = Buffer.from(await body.file.bytes()) | |
| 48 | 67 | let encrypted = false; | |
| 68 | + | let delete_at: number | null = null | |
| 69 | + | if (body.delete_in_minutes) { | |
| 70 | + | delete_at = Math.floor(Date.now() / 1000) + Number(body.delete_in_minutes) * 60 | |
| 71 | + | } | |
| 49 | 72 | if (body.encrypted === "on") { | |
| 50 | 73 | encrypted = true | |
| 51 | 74 | } else if (body.password) { | |
| 52 | 75 | content = encrypt(content, body.password) | |
| 53 | 76 | encrypted = true | |
| 54 | 77 | } | |
| 55 | - | db.exec("INSERT INTO files (uuid, filename, content, filetype, encrypted) VALUES (?, ?, ?, ?, ?)", [uuid, body.filename || body.file.name, content, body.filetype, encrypted]) | |
| 78 | + | db.exec("INSERT INTO files (uuid, filename, content, filetype, encrypted, delete_at) VALUES (?, ?, ?, ?, ?, ?)", | |
| 79 | + | [uuid, body.filename || body.file.name, content, body.filetype, encrypted, delete_at]) | |
| 56 | 80 | set.status = StatusMap["See Other"] | |
| 57 | 81 | if (query.withJs) { | |
| 58 | 82 | set.headers["location"] = `/show-js/${uuid}` | |
| @@ -62,12 +86,21 @@ const app = new Elysia({ | |||
|---|---|---|---|
| 62 | 86 | return `Created with id: ${uuid}` | |
| 63 | 87 | ||
| 64 | 88 | }, { | |
| 65 | - | body: t.Object({ file: t.File(), filename: t.Optional(t.String()), filetype: t.String()/*todo:verifiy via t.Enum*/, password: t.Optional(t.String()), encrypted: t.Optional(t.String()) }), | |
| 89 | + | body: t.Object({ | |
| 90 | + | file: t.File(), | |
| 91 | + | filename: t.Optional(t.String()), | |
| 92 | + | //filetype: t.String()/*todo:verifiy via t.Enum*/, | |
| 93 | + | filetype: t.Enum(stringArrayToEnum(filetypes)), | |
| 94 | + | password: t.Optional(t.String()), | |
| 95 | + | encrypted: t.Optional(t.String()), | |
| 96 | + | delete_in_minutes: t.String({ format: "regex", pattern: "(^$|^[0-9]+$)" }) | |
| 97 | + | }), | |
| 66 | 98 | query: t.Object({ withJs: t.Optional(t.Boolean()) }) | |
| 67 | 99 | }) | |
| 68 | 100 | .get("/show/:uuid", async ({ set, params, cookie }) => { | |
| 69 | 101 | ||
| 70 | - | const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || null | |
| 102 | + | const result = db.prepare("SELECT filename, content, filetype, encrypted, delete_at FROM files WHERE uuid = ?").get(params.uuid) as | |
| 103 | + | { filename: string, content: Uint8Array, filetype: string, encrypted: boolean, delete_at: number | null } || null | |
| 71 | 104 | if (!result) { | |
| 72 | 105 | set.status = StatusMap["Not Found"] | |
| 73 | 106 | return NotFound() | |
| @@ -89,17 +122,18 @@ const app = new Elysia({ | |||
|---|---|---|---|
| 89 | 122 | } | |
| 90 | 123 | } | |
| 91 | 124 | } | |
| 92 | - | return ShowFile(result.filename, params.uuid, result.content, result.filetype) | |
| 125 | + | return ShowFile(result.filename, params.uuid, result.content, result.filetype, result.delete_at) | |
| 93 | 126 | ||
| 94 | 127 | }, { params: t.Object({ uuid: t.String() }) }) | |
| 95 | 128 | .get("/show-js/:uuid", async ({ set, params, cookie }) => { | |
| 96 | 129 | ||
| 97 | - | const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || null | |
| 130 | + | const result = db.prepare("SELECT filename, content, filetype, encrypted, delete_at FROM files WHERE uuid = ?").get(params.uuid) as | |
| 131 | + | { filename: string, content: Uint8Array, filetype: string, encrypted: boolean, delete_at: number | null } || null | |
| 98 | 132 | if (!result) { | |
| 99 | 133 | set.status = StatusMap["Not Found"] | |
| 100 | 134 | return NotFound() | |
| 101 | 135 | } | |
| 102 | - | return ShowFile(result.filename, params.uuid, null, result.filetype) | |
| 136 | + | return ShowFile(result.filename, params.uuid, null, result.filetype, result.delete_at) | |
| 103 | 137 | ||
| 104 | 138 | }, { params: t.Object({ uuid: t.String() }) }) | |
| 105 | 139 | .post("/set-cookie/:uuid", ({ set, body, params }) => { | |