alpha release
update v0.8.0
This commit is contained in:
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# EditorConfig is awesome: https://editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = tab
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = spaces
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_style = tab
|
||||||
|
trim_trailing_whitespace = false
|
||||||
52
.gitignore
vendored
Normal file
52
.gitignore
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn setups files
|
||||||
|
.yarn
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# twtkpr data file directory
|
||||||
|
.data
|
||||||
|
|
||||||
|
# Other files
|
||||||
|
NOTES.md
|
||||||
|
TODO.md
|
||||||
|
*.bak
|
||||||
11
.prettierrc.json
Normal file
11
.prettierrc.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"embeddedLanguageFormatting": "auto",
|
||||||
|
"quoteProps": "as-needed"
|
||||||
|
}
|
||||||
38
.vscode/settings.json
vendored
Normal file
38
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"prettier.enable": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
},
|
||||||
|
"prettier.requireConfig": true,
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact"
|
||||||
|
],
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.unusualLineTerminators": "off",
|
||||||
|
"cSpell.words": [
|
||||||
|
"blakejs",
|
||||||
|
"dotenv",
|
||||||
|
"eslintcache",
|
||||||
|
"itsericwoodward",
|
||||||
|
"jspm",
|
||||||
|
"nodenext",
|
||||||
|
"noframe",
|
||||||
|
"noopener",
|
||||||
|
"noreferrer",
|
||||||
|
"pgpkey",
|
||||||
|
"pids",
|
||||||
|
"TWTKPR",
|
||||||
|
"twts",
|
||||||
|
"Twttr",
|
||||||
|
"twtxt",
|
||||||
|
"uuidv",
|
||||||
|
"wscript"
|
||||||
|
],
|
||||||
|
"[html]": {
|
||||||
|
"editor.defaultFormatter": "vscode.html-language-features"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
.yarnrc.yml
Normal file
3
.yarnrc.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-4.13.0.cjs
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Eric Woodward [https://www.itsericwoodward.com]
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Twtkpr
|
||||||
|
|
||||||
|
An [ExpressJS](https://expressjs.com/) router for serving, sharing, and updating a
|
||||||
|
[`twtxt.txt` file](https://twtxt.dev/).
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> **STILL IN ALPHA**: Although this plugin lacks documentation, examples, tests, installation
|
||||||
|
> flexibility, and polish, it's still fully-functional and actively deployed to at least one site.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Uses JWT (with refresh) for security.
|
||||||
|
- Allows for adding new twts and directly editing a `twtxt.txt` file from within a browser.
|
||||||
|
- Includes a ull-featured GET API for your `twtxt.txt` file backed by an in-memory cache.
|
||||||
|
- Supports optional drag-and-drop file upload handling with automatic linking.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add express-twtkpr
|
||||||
|
```
|
||||||
|
|
||||||
|
More to come!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Eric Woodward, released under the
|
||||||
|
[MIT License](https://www.itsericwoodward.com/licenses/mit/).
|
||||||
81
dist/package.json
vendored
Normal file
81
dist/package.json
vendored
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"name": "express-twtkpr",
|
||||||
|
"version": "0.8.0",
|
||||||
|
"description": "An express library for hosting and maintaining a twtxt.txt file.",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": {
|
||||||
|
"name": "Eric Woodward",
|
||||||
|
"email": "hey@itsericwoodward.com",
|
||||||
|
"url": "https://www.itsericwoodward.com"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.itsericwoodward.com/eric/express-twtkp"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/src/index.js",
|
||||||
|
"module": "./dist/src/index.js",
|
||||||
|
"types": "./dist/src/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/src/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"start": "node --env-file=.env dist/index-app.js",
|
||||||
|
"build": "tsc && cp -r src/client dist/src",
|
||||||
|
"dev": "DEBUG='twtkpr:*' tsx watch --env-file=.env src/index-app.ts",
|
||||||
|
"get:hash": "tsx --env-file=.env src/cli.ts get-hash",
|
||||||
|
"lint": "eslint --fix src test",
|
||||||
|
"prepublishOnly": "yarn build",
|
||||||
|
"set:user": "tsx --env-file=.env src/cli.ts set-user",
|
||||||
|
"test": "vitest",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@cacheable/node-cache": "^2.0.2",
|
||||||
|
"@exodus/blakejs": "^1.1.1-exodus.0",
|
||||||
|
"base32.js": "^0.1.0",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
|
"dayjs": "^1.11.20",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"express": "^5.2.1",
|
||||||
|
"express-rate-limit": "^8.3.1",
|
||||||
|
"express-session": "^1.19.0",
|
||||||
|
"express-slow-down": "^3.1.0",
|
||||||
|
"formidable": "^3.5.4",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
"link": "^2.1.2",
|
||||||
|
"session-file-store": "^1.5.0",
|
||||||
|
"twtxt-lib": "^0.9.4",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
|
"zod": "^4.3.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cookie-parser": "^1.4.10",
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/debug": "^4.1.12",
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
|
"@types/express-session": "^1.18.2",
|
||||||
|
"@types/formidable": "^3.5.0",
|
||||||
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
|
"@types/morgan": "^1.9.10",
|
||||||
|
"@types/node": "^25.5.0",
|
||||||
|
"@types/supertest": "^7.2.0",
|
||||||
|
"eslint": "^10.0.3",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
|
"eslint-plugin-security": "^4.0.0",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"supertest": "^7.2.2",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vitest": "^4.1.0"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.13.0"
|
||||||
|
}
|
||||||
506
dist/src/client/script.js
vendored
Normal file
506
dist/src/client/script.js
vendored
Normal file
@@ -0,0 +1,506 @@
|
|||||||
|
const DEBUG_ON = true;
|
||||||
|
|
||||||
|
// served from same path as TWTXT file
|
||||||
|
const TWTXT_FILE_URL = window.location.pathname;
|
||||||
|
const REMEMBER_LOGIN_STORAGE_KEY = 'rememberLogin';
|
||||||
|
const ACCESS_TOKEN_COOKIE_KEY = 'accessToken';
|
||||||
|
|
||||||
|
const debug = (...vals) => {
|
||||||
|
if (DEBUG_ON) console.log(...vals);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (async () => {
|
||||||
|
/* DOM Elements */
|
||||||
|
const twtForm = document.getElementById('twtForm'),
|
||||||
|
loginForm = document.getElementById('loginControls-form'),
|
||||||
|
fileBox = document.getElementById('fileBox'),
|
||||||
|
fileContentsSection = document.getElementById('fileContentsSection'),
|
||||||
|
toastContainer = document.getElementById('toast-container'),
|
||||||
|
twtControlsContentInput = document.getElementById(
|
||||||
|
'twtControlsContentInput'
|
||||||
|
),
|
||||||
|
twtSubmitButton = document.querySelector('.twtControls-submitButton'),
|
||||||
|
twtLogoutButton = document.getElementById('twtControlsLogoutButton'),
|
||||||
|
twtFileEditButton = document.getElementById('twtControlsEditButton'),
|
||||||
|
menuCheckbox = document.getElementById('hamburgerToggleCheckbox'),
|
||||||
|
twtxtEditFormText = document.getElementById('twtxtEditFormText'),
|
||||||
|
uploadInputs = document.querySelectorAll('.twtControls-uploadInput'),
|
||||||
|
rememberToggle = document.getElementById('loginControls-rememberToggle');
|
||||||
|
|
||||||
|
const lastModifiedDates = {};
|
||||||
|
let isEditing = false,
|
||||||
|
cookie,
|
||||||
|
fileText,
|
||||||
|
token;
|
||||||
|
|
||||||
|
const showToast = (message, type = 'success') => {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.classList.add('toast');
|
||||||
|
if (type === 'error') toast.classList.add('error');
|
||||||
|
toast.textContent = message;
|
||||||
|
|
||||||
|
toastContainer.appendChild(toast);
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.style.animation = 'fadeOut 0.5s forwards';
|
||||||
|
setTimeout(() => toast.remove(), 500);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const beginEditMode = () => {
|
||||||
|
isEditing = true;
|
||||||
|
|
||||||
|
if (menuCheckbox) menuCheckbox.checked = false;
|
||||||
|
if (twtSubmitButton) twtSubmitButton.setAttribute('disabled', 'disabled');
|
||||||
|
document.body.classList.add('js-editMode');
|
||||||
|
};
|
||||||
|
|
||||||
|
const endEditMode = () => {
|
||||||
|
isEditing = false;
|
||||||
|
|
||||||
|
if (twtSubmitButton) twtSubmitButton.removeAttribute('disabled');
|
||||||
|
document.body.classList.remove('js-editMode');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCookie = (name) => {
|
||||||
|
const cookies = document.cookie.split('; ');
|
||||||
|
for (const cookie of cookies) {
|
||||||
|
const [key, value] = cookie.split('=');
|
||||||
|
if (key === name) return decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCookie = (name, value, expireDays) => {
|
||||||
|
const isSecure = window.location.protocol === 'https';
|
||||||
|
const expireDate = new Date(); // current date
|
||||||
|
expireDate.setTime(
|
||||||
|
expireDate.getTime() + (expireDays ?? 0) * 24 * 60 * 60 * 1000
|
||||||
|
);
|
||||||
|
let expires =
|
||||||
|
expireDays !== undefined ? `expires=${expireDate.toUTCString()}; ` : '';
|
||||||
|
document.cookie = `${name}=${encodeURIComponent(value)}; ${expires}${isSecure ? 'Secure; ' : ''}SameSite=Strict; Path=/`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadTwtxtFile = async (filePath = TWTXT_FILE_URL) => {
|
||||||
|
debug('loadTwtxtFile start');
|
||||||
|
let response;
|
||||||
|
const fileURL = `${filePath.charAt(0) !== '/' ? '/' : ''}${filePath}`;
|
||||||
|
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set(
|
||||||
|
'Accept',
|
||||||
|
'application/twtxt+txt,text/twtxt,text/plain;q=0.9,*/*;q=0.8'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lastModifiedDates[fileURL]) {
|
||||||
|
headers['If-Modified-Since'] = lastModifiedDates[fileURL];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await fetch(fileURL, {
|
||||||
|
method: 'GET',
|
||||||
|
headers,
|
||||||
|
mode: 'same-origin',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.body) fileText = await new Response(response.body).text();
|
||||||
|
|
||||||
|
if (response?.headers.get('Last-Modified')) {
|
||||||
|
lastModifiedDates[fileURL] = response?.headers.get('Last-Modified');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileText && fileBox) {
|
||||||
|
fileBox.textContent = fileText;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showToast('Unable to load file, please try again later.', 'error');
|
||||||
|
console.error('Error loading file', err);
|
||||||
|
}
|
||||||
|
debug('loadTwtxtFile end');
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshToken = async (hideToast = false) => {
|
||||||
|
debug('refreshToken start', hideToast);
|
||||||
|
const rememberToggleVal =
|
||||||
|
localStorage.getItem(REMEMBER_LOGIN_STORAGE_KEY) === 'true';
|
||||||
|
|
||||||
|
const res = await fetch(`${TWTXT_FILE_URL}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: new URLSearchParams({
|
||||||
|
rememberToggle: rememberToggleVal,
|
||||||
|
type: 'refresh',
|
||||||
|
}),
|
||||||
|
credentials: 'include', // Include cookies
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok && res?.body) {
|
||||||
|
token = await new Response(res.body).text();
|
||||||
|
if (rememberToggleVal) {
|
||||||
|
debug('refreshToken set new accessToken cookie');
|
||||||
|
setCookie(ACCESS_TOKEN_COOKIE_KEY, token);
|
||||||
|
}
|
||||||
|
debug('refreshToken end OK');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle refresh failure
|
||||||
|
if (!hideToast)
|
||||||
|
showToast('Unable to refresh token, please try again later.', 'error');
|
||||||
|
token = undefined;
|
||||||
|
document.body.classList.remove('js-authorized');
|
||||||
|
debug('refreshToken end error');
|
||||||
|
throw new Error('Failed to refresh token');
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFiles = async (files, uploadRoute, secondAttempt = false) => {
|
||||||
|
if (!uploadRoute) return;
|
||||||
|
|
||||||
|
debug('uploadFiles', token, files, uploadRoute, secondAttempt);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
formData.append('files', files[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(uploadRoute, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast(`File${files.length !== 1 ? 's' : ''} uploaded`);
|
||||||
|
|
||||||
|
const filePath = await res.text();
|
||||||
|
twtControlsContentInput.value += filePath
|
||||||
|
.split('\n')
|
||||||
|
.map((currFilePath) =>
|
||||||
|
[
|
||||||
|
' ',
|
||||||
|
location.protocol,
|
||||||
|
'//',
|
||||||
|
location.hostname,
|
||||||
|
location.protocol !== 'https' && location.port !== 80
|
||||||
|
? ':' + location.port
|
||||||
|
: '',
|
||||||
|
currFilePath,
|
||||||
|
].join('')
|
||||||
|
)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secondAttempt) {
|
||||||
|
await refreshToken();
|
||||||
|
return uploadFiles(files, uploadRoute, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
`Unable to upload image${files.length !== 1 ? 's' : ''} refresh token, please try again later.`,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Handlers */
|
||||||
|
|
||||||
|
const dragOverHandler = (ev) => {
|
||||||
|
const files = [...ev.dataTransfer.items].filter(
|
||||||
|
(item) => item.kind === 'file'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dragOverWindowHandler = (ev) => {
|
||||||
|
const files = [...ev.dataTransfer.items].filter(
|
||||||
|
(item) => item.kind === 'file'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!twtControlsContentInput.contains(ev.target)) {
|
||||||
|
ev.dataTransfer.dropEffect = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dropHandler = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!uploadInputs.length) return;
|
||||||
|
|
||||||
|
const files = [...ev.dataTransfer.items]
|
||||||
|
.map((item) => item.getAsFile())
|
||||||
|
.filter((file) => file);
|
||||||
|
|
||||||
|
debug('dropHandler', files);
|
||||||
|
uploadFiles(files, uploadInputs[0].getAttribute('data-route'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const dropWindowHandler = (ev) => {
|
||||||
|
if ([...ev.dataTransfer.items].some((item) => item.kind === 'file')) {
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editClickHandler = () => {
|
||||||
|
if (isEditing) return;
|
||||||
|
|
||||||
|
if (twtxtEditFormText) twtxtEditFormText.value = fileText;
|
||||||
|
|
||||||
|
beginEditMode();
|
||||||
|
};
|
||||||
|
|
||||||
|
const editResetHandler = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!isEditing && !confirm('Do you want to quit editing?')) return;
|
||||||
|
|
||||||
|
if (fileText && fileBox) {
|
||||||
|
fileBox.textContent = fileText;
|
||||||
|
}
|
||||||
|
|
||||||
|
endEditMode();
|
||||||
|
};
|
||||||
|
|
||||||
|
const editSubmitHandler = async (ev, secondAttempt = false) => {
|
||||||
|
ev?.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newFileText = twtxtEditFormText.value;
|
||||||
|
|
||||||
|
debug('edit file submit', { newFileText });
|
||||||
|
if (!newFileText) return;
|
||||||
|
|
||||||
|
const res = await fetch(`${TWTXT_FILE_URL}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: new URLSearchParams({
|
||||||
|
fileContents: newFileText,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok && !secondAttempt) {
|
||||||
|
await refreshToken();
|
||||||
|
return editSubmitHandler(ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('File updated');
|
||||||
|
|
||||||
|
if (newFileText && fileBox) {
|
||||||
|
fileText = newFileText;
|
||||||
|
fileBox.textContent = newFileText;
|
||||||
|
}
|
||||||
|
|
||||||
|
endEditMode();
|
||||||
|
|
||||||
|
await loadTwtxtFile();
|
||||||
|
} catch (err) {
|
||||||
|
if (!secondAttempt) {
|
||||||
|
await refreshToken();
|
||||||
|
return twtFormSubmitHandler(ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('Unable to update file, please try again later.', 'error');
|
||||||
|
console.error('Error updating file', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginFormSubmitHandler = async (ev) => {
|
||||||
|
ev?.preventDefault();
|
||||||
|
let response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginData = new URLSearchParams(new FormData(loginForm));
|
||||||
|
debug('loginForm submit', { loginData });
|
||||||
|
|
||||||
|
response = await fetch(TWTXT_FILE_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: loginData,
|
||||||
|
mode: 'same-origin',
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
debug('loginForm submit', { response });
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(response.statusText);
|
||||||
|
showToast('Login complete');
|
||||||
|
} catch (err) {
|
||||||
|
showToast('Unable to login, please try again later.', 'error');
|
||||||
|
console.error('Error logging in', err);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response?.body) token = await new Response(response.body).text();
|
||||||
|
|
||||||
|
if (token) document.body.classList.add('js-authorized');
|
||||||
|
|
||||||
|
if (token && rememberToggle.checked)
|
||||||
|
setCookie(ACCESS_TOKEN_COOKIE_KEY, token, 7);
|
||||||
|
|
||||||
|
debug('loginForm submit', { cookie, token, response });
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const logoutHandler = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${TWTXT_FILE_URL}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: new URLSearchParams({
|
||||||
|
type: 'logout',
|
||||||
|
}),
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error();
|
||||||
|
|
||||||
|
document.cookie = `${ACCESS_TOKEN_COOKIE_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||||
|
|
||||||
|
if (menuCheckbox) menuCheckbox.checked = false;
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
} catch {
|
||||||
|
showToast('Unable to logout, please try again later.', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rememberToggleHandler = (ev) => {
|
||||||
|
if (ev.target.checked) {
|
||||||
|
localStorage.setItem(REMEMBER_LOGIN_STORAGE_KEY, 'true');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.removeItem(REMEMBER_LOGIN_STORAGE_KEY);
|
||||||
|
};
|
||||||
|
|
||||||
|
const twtContentKeyupHandler = (ev) => {
|
||||||
|
const hasContent =
|
||||||
|
(twtControlsContentInput.value?.trim() ?? '').length !== 0;
|
||||||
|
|
||||||
|
if (twtControlsContentInput) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// Sync with browser repaint
|
||||||
|
twtControlsContentInput.style.height = 'auto';
|
||||||
|
twtControlsContentInput.style.height = `${Math.max(twtControlsContentInput.scrollHeight, 80)}px`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditing || !hasContent)
|
||||||
|
twtSubmitButton.setAttribute('disabled', 'disabled');
|
||||||
|
else twtSubmitButton.removeAttribute('disabled');
|
||||||
|
|
||||||
|
if (hasContent && ev?.key === 'Enter' && ev?.ctrlKey)
|
||||||
|
twtFormSubmitHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
const twtFormSubmitHandler = async (ev, secondAttempt = false) => {
|
||||||
|
ev?.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const twtData = new FormData(twtForm);
|
||||||
|
const twtContent = twtData.get('content').trim();
|
||||||
|
|
||||||
|
debug('twtForm submit data', { twtData });
|
||||||
|
if (!twtContent) return;
|
||||||
|
|
||||||
|
twtData.set('content', twtContent.replaceAll('\n', '\u2028'));
|
||||||
|
const twtBody = new URLSearchParams(twtData);
|
||||||
|
debug('twtForm submit body', { twtBody });
|
||||||
|
|
||||||
|
const res = await fetch(TWTXT_FILE_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: twtBody,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok && !secondAttempt) {
|
||||||
|
debug('twtForm submit - not OK, trying refresh');
|
||||||
|
await refreshToken();
|
||||||
|
return twtFormSubmitHandler(ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('Twt sent');
|
||||||
|
|
||||||
|
twtForm.reset();
|
||||||
|
await loadTwtxtFile();
|
||||||
|
|
||||||
|
// scroll to bottom of file to show update
|
||||||
|
fileContentsSection.scrollTop = fileBox.scrollHeight;
|
||||||
|
} catch (err) {
|
||||||
|
debug('twtForm submit - error, trying refresh', err, !secondAttempt);
|
||||||
|
if (!secondAttempt) {
|
||||||
|
await refreshToken();
|
||||||
|
return twtFormSubmitHandler(ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast('Unable to twt, please try again later.', 'error');
|
||||||
|
console.error('Error POSTing twt', err);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadChangeHandler = (ev) => {
|
||||||
|
uploadFiles(ev.target.files, ev.target.getAttribute('data-route'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Attach Handlers to Listeners */
|
||||||
|
|
||||||
|
Array.from(uploadInputs).forEach((uploadInput) => {
|
||||||
|
uploadInput.addEventListener('change', uploadChangeHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
loginForm.addEventListener('submit', loginFormSubmitHandler);
|
||||||
|
|
||||||
|
twtForm.addEventListener('submit', twtFormSubmitHandler);
|
||||||
|
|
||||||
|
twtForm.addEventListener('keyup', twtContentKeyupHandler);
|
||||||
|
|
||||||
|
twtControlsContentInput.addEventListener('drop', dropHandler);
|
||||||
|
|
||||||
|
twtControlsContentInput.addEventListener('dragover', dragOverHandler);
|
||||||
|
|
||||||
|
twtLogoutButton.addEventListener('click', logoutHandler);
|
||||||
|
|
||||||
|
twtFileEditButton.addEventListener('click', editClickHandler);
|
||||||
|
|
||||||
|
twtxtEditForm.addEventListener('reset', editResetHandler);
|
||||||
|
|
||||||
|
twtxtEditForm.addEventListener('submit', editSubmitHandler);
|
||||||
|
|
||||||
|
window.addEventListener('dragover', dragOverWindowHandler);
|
||||||
|
|
||||||
|
window.addEventListener('drop', dropWindowHandler);
|
||||||
|
|
||||||
|
rememberToggle.addEventListener('change', rememberToggleHandler);
|
||||||
|
|
||||||
|
/* Start App*/
|
||||||
|
|
||||||
|
loadTwtxtFile().catch(() => {});
|
||||||
|
|
||||||
|
token = getCookie(ACCESS_TOKEN_COOKIE_KEY);
|
||||||
|
if (token) document.body.classList.add('js-authorized');
|
||||||
|
|
||||||
|
debug('client loaded');
|
||||||
|
})();
|
||||||
717
dist/src/client/styles.css
vendored
Normal file
717
dist/src/client/styles.css
vendored
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
/**
|
||||||
|
* Taken from Normalize.css v12.1.1 / https://csstools.github.io/normalize.css/
|
||||||
|
*/
|
||||||
|
:where(html) {
|
||||||
|
line-height: 1.15;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
:where(b, strong) {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
:where(code, kbd, pre, samp) {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
:where(button, input, select) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
:where(button) {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
:where(
|
||||||
|
button,
|
||||||
|
input:is([type='button' i], [type='reset' i], [type='submit' i])
|
||||||
|
) {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
appearance: button;
|
||||||
|
}
|
||||||
|
:where(select) {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
:where(textarea) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
:where(input[type='search' i]) {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
::-webkit-input-placeholder {
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.54;
|
||||||
|
}
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
:where(
|
||||||
|
button,
|
||||||
|
input:is(
|
||||||
|
[type='button' i],
|
||||||
|
[type='color' i],
|
||||||
|
[type='reset' i],
|
||||||
|
[type='submit' i]
|
||||||
|
)
|
||||||
|
)::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
:where(
|
||||||
|
button,
|
||||||
|
input:is(
|
||||||
|
[type='button' i],
|
||||||
|
[type='color' i],
|
||||||
|
[type='reset' i],
|
||||||
|
[type='submit' i]
|
||||||
|
)
|
||||||
|
)::-moz-focusring {
|
||||||
|
outline: 1px dotted ButtonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local styles
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
/* #0a0a14 / Vulcan*/
|
||||||
|
--bg: rgb(10, 10, 20);
|
||||||
|
--bg-al: rgb(10, 10, 20, 0.6);
|
||||||
|
|
||||||
|
/* #1b1b27 / Steel Gray */
|
||||||
|
--bg-hl: rgb(27, 27, 39);
|
||||||
|
|
||||||
|
/* #6e6e81 / Storm Gray */
|
||||||
|
--fg: rgb(110, 110, 129);
|
||||||
|
|
||||||
|
--fg-hl: #ccc;
|
||||||
|
|
||||||
|
/* #9f9fc1 / Logan */
|
||||||
|
--link: rgb(159, 159, 193);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: var(--link);
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--fg-hl);
|
||||||
|
background-color: var(--bg-hl);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
color: var(--fg-hl);
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
background-color: var(--bg-hl);
|
||||||
|
border-color: var(--fg);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input[type='reset'],
|
||||||
|
input[type='submit'] {
|
||||||
|
background-color: var(--bg-hl);
|
||||||
|
border-color: var(--link);
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled,
|
||||||
|
input:disabled {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appInfo {
|
||||||
|
font-size: smaller;
|
||||||
|
font-style: italic;
|
||||||
|
margin: 0.5rem auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: var(--bg-hl);
|
||||||
|
border: 1px solid var(--link);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: var(--link);
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileContentsSection {
|
||||||
|
margin-bottom: 7rem;
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileContentsSection-fileBox {
|
||||||
|
margin: 0;
|
||||||
|
max-height: 1000rem;
|
||||||
|
overflow-y: hidden;
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburgerToggle {
|
||||||
|
grid-area: menuButton;
|
||||||
|
max-width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburgerToggle-icon,
|
||||||
|
.hamburgerToggle-icon:after,
|
||||||
|
.hamburgerToggle-icon:before {
|
||||||
|
background-color: var(--fg);
|
||||||
|
height: 0.25rem;
|
||||||
|
position: absolute;
|
||||||
|
transition-duration: 0.5s;
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburgerToggle-icon {
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburgerToggle-icon:before {
|
||||||
|
content: '';
|
||||||
|
top: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburgerToggle-icon:after {
|
||||||
|
content: '';
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburgerToggle-label {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
height: 1.75rem;
|
||||||
|
left: 0;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
transition-duration: 0.5s;
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hamburgerToggleCheckbox {
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hamburgerToggleCheckbox:checked
|
||||||
|
+ .hamburgerToggle-label
|
||||||
|
.hamburgerToggle-icon {
|
||||||
|
transition-duration: 0.5s;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hamburgerToggleCheckbox:checked
|
||||||
|
+ .hamburgerToggle-label
|
||||||
|
.hamburgerToggle-icon:before {
|
||||||
|
transform: rotateZ(45deg) scaleX(1.25) translate(8px, 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hamburgerToggleCheckbox:checked
|
||||||
|
+ .hamburgerToggle-label
|
||||||
|
.hamburgerToggle-icon:after {
|
||||||
|
transform: rotateZ(-45deg) scaleX(1.25) translate(8px, -8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hamburgerToggleCheckbox:checked ~ .popupMenu {
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls {
|
||||||
|
max-height: 100rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-fields-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-input {
|
||||||
|
max-width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls input:focus {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-label {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-row {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-toggle {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8rm;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-toggle-checkbox {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-toggle-checkbox:not([disabled]):active
|
||||||
|
+ .loginControls-toggle-track,
|
||||||
|
.loginControls-toggle-checkbox:not([disabled]):focus
|
||||||
|
+ .loginControls-toggle-track {
|
||||||
|
/* border: 1px solid transparent; */
|
||||||
|
box-shadow: 0px 0px 0px 2px #333;
|
||||||
|
}
|
||||||
|
.loginControls-toggle-checkbox:disabled + .loginControls-toggle-track {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-toggle-track {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--fg);
|
||||||
|
border-radius: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
height: 1.5rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-toggle-indicator {
|
||||||
|
align-items: center;
|
||||||
|
background: var(--bg-hl);
|
||||||
|
border: 1px solid var(--fg);
|
||||||
|
border-radius: 1rem;
|
||||||
|
bottom: 0.1rem;
|
||||||
|
display: flex;
|
||||||
|
height: 1.25rem;
|
||||||
|
justify-content: center;
|
||||||
|
left: 0.1rem;
|
||||||
|
outline: solid 2px transparent;
|
||||||
|
position: absolute;
|
||||||
|
transition: 0.25s;
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-toggle-checkMark {
|
||||||
|
fill: var(--fg);
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-toggle-checkbox:checked
|
||||||
|
+ .loginControls-toggle-track
|
||||||
|
.loginControls-toggle-indicator {
|
||||||
|
transform: translateX(1.4rem);
|
||||||
|
}
|
||||||
|
.loginControls-toggle-checkbox:checked
|
||||||
|
+ .loginControls-toggle-track
|
||||||
|
.loginControls-toggle-indicator
|
||||||
|
.loginControls-toggle-checkMark {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginControls-submitButton {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
background-color: var(--bg-al);
|
||||||
|
backdrop-filter: blur(5px) saturate(70%);
|
||||||
|
left: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupMenu {
|
||||||
|
background-color: var(--bg-hl);
|
||||||
|
border: 1px solid var(--fg);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
right: -10rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
position: fixed;
|
||||||
|
top: -14rem;
|
||||||
|
transition: all 0.5s;
|
||||||
|
width: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupMenu > * {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupMenu > *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupMenu-appInfo {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
animation:
|
||||||
|
slideIn 0.5s,
|
||||||
|
fadeOut 0.5s 3s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.error {
|
||||||
|
background-color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastContainer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 8rem;
|
||||||
|
right: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-appAuthor {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-appInfo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-contentInput {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
resize: none;
|
||||||
|
transition: max-height 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-contentLabel {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
grid-area: textarea;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-formRow {
|
||||||
|
align-items: center;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 0.5rem;
|
||||||
|
grid-template-areas:
|
||||||
|
'textarea menuButton'
|
||||||
|
'textarea postButton';
|
||||||
|
grid-template-columns: 1fr 3rem;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-gitLink {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-uploadInputLabel {
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-uploadInputLabel-normal {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-uploadInputLabel-small {
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-uploadInputLabel:hover {
|
||||||
|
background-color: var(--bg-hl);
|
||||||
|
color: var(--fg-hl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-uploadInput {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-submitButton {
|
||||||
|
background-color: var(--bg-hl);
|
||||||
|
border: 1px solid var(--link);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: var(--link);
|
||||||
|
grid-area: postButton;
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtxtEditForm {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.5s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtxtEditForm-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: end;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtxtEditForm-textarea {
|
||||||
|
font-size: large;
|
||||||
|
height: 80vh;
|
||||||
|
min-height: 6rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media-based Overrides */
|
||||||
|
|
||||||
|
@media (min-width: 24rem) {
|
||||||
|
.loginControls-input {
|
||||||
|
max-width: 10rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 28rem) {
|
||||||
|
.loginControls-input {
|
||||||
|
max-width: 12rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
#hamburgerToggleCheckbox:checked ~ .popupMenu {
|
||||||
|
left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupMenu {
|
||||||
|
left: -10rem;
|
||||||
|
top: -6.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-appInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-area: appInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-submitButton {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-formRow {
|
||||||
|
grid-template-areas: 'menuButton appInfo textarea postButton';
|
||||||
|
grid-template-columns: 3.5rem 3.5rem 1fr 3.5rem;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) {
|
||||||
|
.popupMenu-appInfo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-appAuthor {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-appInfo {
|
||||||
|
display: block;
|
||||||
|
grid-area: appInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-contentInput {
|
||||||
|
min-height: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-formRow {
|
||||||
|
grid-template-areas: 'menuButton appInfo uploadButton textarea postButton';
|
||||||
|
grid-template-columns: 3.5rem 1fr 5rem 1fr 3.5rem;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-gitLink {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-uploadInputLabel-normal {
|
||||||
|
display: block;
|
||||||
|
grid-area: uploadButton;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twtControls-uploadInputLabel-small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.fileContentsSection {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-top: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
top: 0;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupMenu {
|
||||||
|
left: -10rem;
|
||||||
|
top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastContainer {
|
||||||
|
bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (-ms-high-contrast: active) {
|
||||||
|
.loginControls-toggle-track {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* State-Based Overrides */
|
||||||
|
|
||||||
|
.js-authorized .loginControls {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-authorized .twtControls {
|
||||||
|
max-height: 100rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-editMode .fileContentsSection-fileBox {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-editMode .fileContentsSection-twtxtEditForm {
|
||||||
|
max-height: 100rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Fix */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
/* #6e6e81 / Storm Gray */
|
||||||
|
--bg: #ccc;
|
||||||
|
|
||||||
|
--bg-al: rgb(204, 204, 204, 0.6);
|
||||||
|
|
||||||
|
--bg-hl: #b6b6c0;
|
||||||
|
|
||||||
|
/* #0a0a14 / Vulcan*/
|
||||||
|
--fg: rgb(10, 10, 20);
|
||||||
|
|
||||||
|
/* #1b1b27 / Steel Gray */
|
||||||
|
--fg-hl: rgb(27, 27, 39);
|
||||||
|
|
||||||
|
--link: #35353e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
dist/src/index.d.ts
vendored
Normal file
1
dist/src/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "./plugin.js";
|
||||||
2
dist/src/index.js
vendored
Normal file
2
dist/src/index.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./plugin.js";
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
dist/src/index.js.map
vendored
Normal file
1
dist/src/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
||||||
14
dist/src/lib/arrayDB.d.ts
vendored
Normal file
14
dist/src/lib/arrayDB.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* File-backed, in-memory database consisting of arrays of strings (ex: tokens) indexed by other
|
||||||
|
* strings (ex: usernames).
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function arrayDB(name: string, directory: string): Promise<{
|
||||||
|
get: (key?: string) => string[];
|
||||||
|
getObject: () => Record<string, string[]>;
|
||||||
|
remove: (key?: string) => void;
|
||||||
|
set: (key?: string, value?: string[]) => string[];
|
||||||
|
}>;
|
||||||
73
dist/src/lib/arrayDB.js
vendored
Normal file
73
dist/src/lib/arrayDB.js
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { loadObjectFromJson, saveToJson } from './utils.js';
|
||||||
|
const debug = Debug('twtkpr:arrayDB');
|
||||||
|
/**
|
||||||
|
* File-backed, in-memory database consisting of arrays of strings (ex: tokens) indexed by other
|
||||||
|
* strings (ex: usernames).
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function arrayDB(name, directory) {
|
||||||
|
let theName;
|
||||||
|
let dataObject;
|
||||||
|
const get = (key = '') => {
|
||||||
|
debug('get', { key });
|
||||||
|
if (!theName || !dataObject)
|
||||||
|
throw new Error('DB must be initialized first');
|
||||||
|
key = key?.trim();
|
||||||
|
if (!key)
|
||||||
|
throw new Error('a valid key must be provided');
|
||||||
|
return dataObject[key];
|
||||||
|
};
|
||||||
|
const getObject = () => dataObject;
|
||||||
|
const initialize = async (dbName = '') => {
|
||||||
|
debug('initialize starting', { dbName });
|
||||||
|
dbName = dbName?.trim();
|
||||||
|
if (!dbName)
|
||||||
|
throw new Error('a valid name must be provided');
|
||||||
|
try {
|
||||||
|
dataObject = await loadObjectFromJson(join(directory, `${dbName}.json`));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
debug('initialize read error', { err });
|
||||||
|
if (err.code === 'ENOENT')
|
||||||
|
dataObject = {};
|
||||||
|
else
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// only initialize (and set name) if everything passes
|
||||||
|
theName = dbName;
|
||||||
|
debug('initialize complete', { dataObject, name: theName });
|
||||||
|
};
|
||||||
|
const remove = (key = '') => {
|
||||||
|
debug('remove', { key });
|
||||||
|
if (!theName || !dataObject)
|
||||||
|
throw new Error('DB must be initialized first');
|
||||||
|
key = key?.trim();
|
||||||
|
if (!key)
|
||||||
|
throw new Error('a valid key must be provided');
|
||||||
|
delete dataObject[key];
|
||||||
|
};
|
||||||
|
const set = (key = '', value = []) => {
|
||||||
|
debug('set', { key });
|
||||||
|
if (!theName || !dataObject)
|
||||||
|
throw new Error('DB must be initialized first');
|
||||||
|
key = key?.trim();
|
||||||
|
if (!key)
|
||||||
|
throw new Error('a valid key must be provided');
|
||||||
|
dataObject[key] = value;
|
||||||
|
saveToJson(dataObject, join(directory, `${name}.json`));
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
await initialize(name);
|
||||||
|
return {
|
||||||
|
get,
|
||||||
|
getObject,
|
||||||
|
remove,
|
||||||
|
set,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=arrayDB.js.map
|
||||||
1
dist/src/lib/arrayDB.js.map
vendored
Normal file
1
dist/src/lib/arrayDB.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"arrayDB.js","sourceRoot":"","sources":["../../../src/lib/arrayDB.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,KAAK,GAAG,KAAK,CAAC,gBAAgB,CAAC,CAAC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,SAAiB;IACpE,IAAI,OAAe,CAAC;IACpB,IAAI,UAAoC,CAAC;IAEzC,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE;QACxB,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtB,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU;YAC1B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAEjD,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAE1D,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC;IAEnC,MAAM,UAAU,GAAG,KAAK,EAAE,MAAM,GAAG,EAAE,EAAE,EAAE;QACxC,KAAK,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAEzC,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAE9D,IAAI,CAAC;YACJ,UAAU,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACvB,KAAK,CAAC,uBAAuB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YAExC,IAAK,GAAwB,CAAC,IAAI,KAAK,QAAQ;gBAAE,UAAU,GAAG,EAAE,CAAC;;gBAC5D,MAAM,GAAG,CAAC;QAChB,CAAC;QAED,sDAAsD;QACtD,OAAO,GAAG,MAAM,CAAC;QACjB,KAAK,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE;QAC3B,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAEzB,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU;YAC1B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAEjD,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAE1D,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,QAAkB,EAAE,EAAE,EAAE;QAC9C,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtB,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU;YAC1B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAEjD,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAE1D,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACxB,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;QAExD,OAAO,KAAK,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IAEvB,OAAO;QACN,GAAG;QACH,SAAS;QACT,MAAM;QACN,GAAG;KACH,CAAC;AACH,CAAC"}
|
||||||
20
dist/src/lib/constants.d.ts
vendored
Normal file
20
dist/src/lib/constants.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export declare const DEFAULT_PRIVATE_DIRECTORY = ".data";
|
||||||
|
export declare const DEFAULT_PUBLIC_DIRECTORY = "public";
|
||||||
|
export declare const DEFAULT_TWTXT_FILENAME = "twtxt.txt";
|
||||||
|
export declare const DEFAULT_ROUTE = "/twtxt.txt";
|
||||||
|
export declare const DEFAULT_POST_LIMITER_ACTIVE = true;
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_APP = "app";
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_CSS = "css";
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_FOLLOWING = "following";
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_JS = "js";
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_LOGOUT = "logout";
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_METADATA = "metadata";
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_TWT = "twt";
|
||||||
|
export declare const DEFAULT_QUERY_PARAMETER_TWTS = "twts";
|
||||||
|
export declare const DEFAULT_UPLOAD_ACTIVE = true;
|
||||||
|
export declare const DEFAULT_UPLOAD_ALLOWED_MIME_TYPES = "";
|
||||||
|
export declare const DEFAULT_UPLOAD_ROUTE = "files";
|
||||||
|
export declare const DEFAULT_UPLOAD_ENCODING = "utf-8";
|
||||||
|
export declare const DEFAULT_UPLOAD_HASH_ALGORITHM = "sha256";
|
||||||
|
export declare const DEFAULT_UPLOAD_KEEP_EXTENSIONS = true;
|
||||||
|
export declare const __dirname: string;
|
||||||
24
dist/src/lib/constants.js
vendored
Normal file
24
dist/src/lib/constants.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
export const DEFAULT_PRIVATE_DIRECTORY = '.data';
|
||||||
|
export const DEFAULT_PUBLIC_DIRECTORY = 'public';
|
||||||
|
export const DEFAULT_TWTXT_FILENAME = 'twtxt.txt';
|
||||||
|
export const DEFAULT_ROUTE = `/${DEFAULT_TWTXT_FILENAME}`;
|
||||||
|
export const DEFAULT_POST_LIMITER_ACTIVE = true;
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_APP = 'app';
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_CSS = 'css';
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_FOLLOWING = 'following';
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_JS = 'js';
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_LOGOUT = 'logout';
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_METADATA = 'metadata';
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_TWT = 'twt';
|
||||||
|
export const DEFAULT_QUERY_PARAMETER_TWTS = 'twts';
|
||||||
|
export const DEFAULT_UPLOAD_ACTIVE = true;
|
||||||
|
export const DEFAULT_UPLOAD_ALLOWED_MIME_TYPES = '';
|
||||||
|
export const DEFAULT_UPLOAD_ROUTE = 'files';
|
||||||
|
export const DEFAULT_UPLOAD_ENCODING = 'utf-8';
|
||||||
|
// optional in zod
|
||||||
|
export const DEFAULT_UPLOAD_HASH_ALGORITHM = 'sha256';
|
||||||
|
export const DEFAULT_UPLOAD_KEEP_EXTENSIONS = true;
|
||||||
|
export const __dirname = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
//# sourceMappingURL=constants.js.map
|
||||||
1
dist/src/lib/constants.js.map
vendored
Normal file
1
dist/src/lib/constants.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAC;AACjD,MAAM,CAAC,MAAM,wBAAwB,GAAG,QAAQ,CAAC;AACjD,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAC;AAClD,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,sBAAsB,EAAE,CAAC;AAE1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;AAEhD,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,iCAAiC,GAAG,WAAW,CAAC;AAC7D,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAC/C,MAAM,CAAC,MAAM,8BAA8B,GAAG,QAAQ,CAAC;AACvD,MAAM,CAAC,MAAM,gCAAgC,GAAG,UAAU,CAAC;AAC3D,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC;AAEnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,CAAC;AACpD,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAC5C,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAO,CAAC;AAE/C,kBAAkB;AAClB,MAAM,CAAC,MAAM,6BAA6B,GAAG,QAAQ,CAAC;AACtD,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;AAEnD,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC"}
|
||||||
56
dist/src/lib/env.d.ts
vendored
Normal file
56
dist/src/lib/env.d.ts
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { z } from 'zod/v4';
|
||||||
|
export declare const env: {
|
||||||
|
NODE_ENV: "development" | "production" | "test";
|
||||||
|
TWTKPR_REFRESH_SECRET: string;
|
||||||
|
TWTKPR_ACCESS_SECRET: string;
|
||||||
|
TWTKPR_DEFAULT_ROUTE: string;
|
||||||
|
TWTKPR_PRIVATE_DIRECTORY: string;
|
||||||
|
TWTKPR_PUBLIC_DIRECTORY: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_APP: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_CSS: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_FOLLOWING: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_JS: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_LOGOUT: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_METADATA: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_TWT: string;
|
||||||
|
TWTKPR_QUERY_PARAMETER_TWTS: string;
|
||||||
|
TWTKPR_TWTXT_FILENAME: string;
|
||||||
|
TWTKPR_POST_LIMITER_ACTIVE: boolean;
|
||||||
|
TWTKPR_UPLOAD_ACTIVE: boolean;
|
||||||
|
TWTKPR_UPLOAD_ALLOWED_MIME_TYPES: string | string[];
|
||||||
|
TWTKPR_UPLOAD_ROUTE: string;
|
||||||
|
TWTKPR_UPLOAD_ENCODING: string;
|
||||||
|
TWTKPR_UPLOAD_HASH_ALGORITHM: string | boolean;
|
||||||
|
TWTKPR_UPLOAD_KEEP_EXTENSIONS: boolean;
|
||||||
|
TWTKPR_POST_LIMITER_WINDOW_MS?: number | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_LIMIT?: number | z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_MESSAGE?: any;
|
||||||
|
TWTKPR_POST_LIMITER_STATUS_CODE?: number | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_HANDLER?: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_LEGACY_HEADERS?: boolean | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_STANDARD_HEADERS?: string | boolean | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_IDENTIFIER?: string | z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_STORE?: any;
|
||||||
|
TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR?: boolean | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_KEY_GENERATOR?: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_IPV6_SUBNET?: number | boolean | z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME?: string | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_SKIP?: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS?: boolean | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS?: boolean | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL?: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_POST_LIMITER_VALIDATE?: boolean | Record<string, never> | undefined;
|
||||||
|
TWTKPR_UPLOAD_ALLOW_EMPTY_FILES?: boolean | undefined;
|
||||||
|
TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS?: boolean | undefined;
|
||||||
|
TWTKPR_UPLOAD_DIRECTORY?: string | undefined;
|
||||||
|
TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER?: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_UPLOAD_FILENAME?: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_UPLOAD_FILTER?: z.core.$InferOuterFunctionType<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut> | undefined;
|
||||||
|
TWTKPR_UPLOAD_MAX_FIELDS?: number | undefined;
|
||||||
|
TWTKPR_UPLOAD_MAX_FIELDS_SIZE?: number | undefined;
|
||||||
|
TWTKPR_UPLOAD_MAX_FILE_SIZE?: number | undefined;
|
||||||
|
TWTKPR_UPLOAD_MAX_FILES?: number | undefined;
|
||||||
|
TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE?: number | undefined;
|
||||||
|
TWTKPR_UPLOAD_MIN_FILE_SIZE?: number | undefined;
|
||||||
|
};
|
||||||
|
export declare const __dirname: string;
|
||||||
206
dist/src/lib/env.js
vendored
Normal file
206
dist/src/lib/env.js
vendored
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { z } from 'zod/v4';
|
||||||
|
import { DEFAULT_POST_LIMITER_ACTIVE, DEFAULT_PRIVATE_DIRECTORY, DEFAULT_PUBLIC_DIRECTORY, DEFAULT_QUERY_PARAMETER_APP, DEFAULT_QUERY_PARAMETER_CSS, DEFAULT_QUERY_PARAMETER_FOLLOWING, DEFAULT_QUERY_PARAMETER_JS, DEFAULT_QUERY_PARAMETER_LOGOUT, DEFAULT_QUERY_PARAMETER_METADATA, DEFAULT_QUERY_PARAMETER_TWT, DEFAULT_QUERY_PARAMETER_TWTS, DEFAULT_ROUTE, DEFAULT_TWTXT_FILENAME, DEFAULT_UPLOAD_ACTIVE, DEFAULT_UPLOAD_ALLOWED_MIME_TYPES, DEFAULT_UPLOAD_ENCODING, DEFAULT_UPLOAD_HASH_ALGORITHM, DEFAULT_UPLOAD_KEEP_EXTENSIONS, DEFAULT_UPLOAD_ROUTE, } from './constants.js';
|
||||||
|
/*
|
||||||
|
The following keys are expected to exist in `process.env`, either as listed, or without the
|
||||||
|
`TWTKPR_` prefix
|
||||||
|
|
||||||
|
We only have listed default values for our keys, anything for other plugins (like formidable or
|
||||||
|
express-rate-limit) fall back to their own defaults (and thus are optional).
|
||||||
|
*/
|
||||||
|
const envSchema = z.object({
|
||||||
|
NODE_ENV: z
|
||||||
|
.enum(['development', 'production', 'test'])
|
||||||
|
.default('development'),
|
||||||
|
// required vars - MUST be passed via ENV
|
||||||
|
TWTKPR_REFRESH_SECRET: z.string().default(''),
|
||||||
|
TWTKPR_ACCESS_SECRET: z.string().default(''),
|
||||||
|
// vars with default values
|
||||||
|
TWTKPR_DEFAULT_ROUTE: z.string().default(DEFAULT_ROUTE),
|
||||||
|
TWTKPR_PRIVATE_DIRECTORY: z.string().default(DEFAULT_PRIVATE_DIRECTORY),
|
||||||
|
TWTKPR_PUBLIC_DIRECTORY: z.string().default(DEFAULT_PUBLIC_DIRECTORY),
|
||||||
|
TWTKPR_QUERY_PARAMETER_APP: z.string().default(DEFAULT_QUERY_PARAMETER_APP),
|
||||||
|
TWTKPR_QUERY_PARAMETER_CSS: z.string().default(DEFAULT_QUERY_PARAMETER_CSS),
|
||||||
|
TWTKPR_QUERY_PARAMETER_FOLLOWING: z
|
||||||
|
.string()
|
||||||
|
.default(DEFAULT_QUERY_PARAMETER_FOLLOWING),
|
||||||
|
TWTKPR_QUERY_PARAMETER_JS: z.string().default(DEFAULT_QUERY_PARAMETER_JS),
|
||||||
|
TWTKPR_QUERY_PARAMETER_LOGOUT: z
|
||||||
|
.string()
|
||||||
|
.default(DEFAULT_QUERY_PARAMETER_LOGOUT),
|
||||||
|
TWTKPR_QUERY_PARAMETER_METADATA: z
|
||||||
|
.string()
|
||||||
|
.default(DEFAULT_QUERY_PARAMETER_METADATA),
|
||||||
|
TWTKPR_QUERY_PARAMETER_TWT: z.string().default(DEFAULT_QUERY_PARAMETER_TWT),
|
||||||
|
TWTKPR_QUERY_PARAMETER_TWTS: z.string().default(DEFAULT_QUERY_PARAMETER_TWTS),
|
||||||
|
TWTKPR_TWTXT_FILENAME: z.string().default(DEFAULT_TWTXT_FILENAME),
|
||||||
|
/**
|
||||||
|
* Post limiter plugin
|
||||||
|
*/
|
||||||
|
// var with default value
|
||||||
|
TWTKPR_POST_LIMITER_ACTIVE: z.boolean().default(DEFAULT_POST_LIMITER_ACTIVE),
|
||||||
|
// optional vars
|
||||||
|
TWTKPR_POST_LIMITER_WINDOW_MS: z.optional(z.number()),
|
||||||
|
TWTKPR_POST_LIMITER_LIMIT: z.optional(z.union([z.number(), z.function()])),
|
||||||
|
TWTKPR_POST_LIMITER_MESSAGE: z.optional(z.any()),
|
||||||
|
TWTKPR_POST_LIMITER_STATUS_CODE: z.optional(z.number()),
|
||||||
|
TWTKPR_POST_LIMITER_HANDLER: z.optional(z.function()),
|
||||||
|
TWTKPR_POST_LIMITER_LEGACY_HEADERS: z.optional(z.boolean()),
|
||||||
|
TWTKPR_POST_LIMITER_STANDARD_HEADERS: z.optional(z.union([z.boolean(), z.string()])),
|
||||||
|
TWTKPR_POST_LIMITER_IDENTIFIER: z.optional(z.union([z.string(), z.function()])),
|
||||||
|
TWTKPR_POST_LIMITER_STORE: z.optional(z.any()),
|
||||||
|
TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR: z.optional(z.boolean()),
|
||||||
|
TWTKPR_POST_LIMITER_KEY_GENERATOR: z.optional(z.function()),
|
||||||
|
TWTKPR_POST_LIMITER_IPV6_SUBNET: z.optional(z.union([z.number(), z.function(), z.boolean()])),
|
||||||
|
TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME: z.optional(z.string()),
|
||||||
|
TWTKPR_POST_LIMITER_SKIP: z.optional(z.function()),
|
||||||
|
TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS: z.optional(z.boolean()),
|
||||||
|
TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS: z.optional(z.boolean()),
|
||||||
|
TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL: z.optional(z.function()),
|
||||||
|
TWTKPR_POST_LIMITER_VALIDATE: z.optional(z.union([z.boolean(), z.object()])),
|
||||||
|
/**
|
||||||
|
* Upload plugin
|
||||||
|
*/
|
||||||
|
// vars with default values
|
||||||
|
TWTKPR_UPLOAD_ACTIVE: z.boolean().default(DEFAULT_UPLOAD_ACTIVE),
|
||||||
|
TWTKPR_UPLOAD_ALLOWED_MIME_TYPES: z
|
||||||
|
.union([z.string(), z.array(z.string())])
|
||||||
|
.default(DEFAULT_UPLOAD_ALLOWED_MIME_TYPES),
|
||||||
|
TWTKPR_UPLOAD_ROUTE: z.string().default(DEFAULT_UPLOAD_ROUTE),
|
||||||
|
// optional vars
|
||||||
|
TWTKPR_UPLOAD_ALLOW_EMPTY_FILES: z.optional(z.boolean()),
|
||||||
|
TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS: z.optional(z.boolean()),
|
||||||
|
TWTKPR_UPLOAD_DIRECTORY: z.optional(z.string()),
|
||||||
|
TWTKPR_UPLOAD_ENCODING: z.string().default(DEFAULT_UPLOAD_ENCODING),
|
||||||
|
TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER: z.optional(z.function()),
|
||||||
|
TWTKPR_UPLOAD_FILENAME: z.optional(z.function()),
|
||||||
|
TWTKPR_UPLOAD_FILTER: z.optional(z.function()),
|
||||||
|
TWTKPR_UPLOAD_HASH_ALGORITHM: z
|
||||||
|
.union([z.boolean(), z.string()])
|
||||||
|
.default(DEFAULT_UPLOAD_HASH_ALGORITHM),
|
||||||
|
TWTKPR_UPLOAD_KEEP_EXTENSIONS: z
|
||||||
|
.boolean()
|
||||||
|
.default(DEFAULT_UPLOAD_KEEP_EXTENSIONS),
|
||||||
|
TWTKPR_UPLOAD_MAX_FIELDS: z.optional(z.number()),
|
||||||
|
TWTKPR_UPLOAD_MAX_FIELDS_SIZE: z.optional(z.number()),
|
||||||
|
TWTKPR_UPLOAD_MAX_FILE_SIZE: z.optional(z.number()),
|
||||||
|
TWTKPR_UPLOAD_MAX_FILES: z.optional(z.number()),
|
||||||
|
TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE: z.optional(z.number()),
|
||||||
|
TWTKPR_UPLOAD_MIN_FILE_SIZE: z.optional(z.number()),
|
||||||
|
});
|
||||||
|
const parseEnv = () => {
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* there's probably an easier way to do this, spreading the keys from above and accepting either
|
||||||
|
* the app (bare) or library (`TWTKPR_`-prefixed) version of said key.
|
||||||
|
* But this should work for now.
|
||||||
|
*/
|
||||||
|
const parsedEnv = envSchema.parse({
|
||||||
|
NODE_ENV: process.env.TWTKPR_NODE_ENV || process.env.NODE_ENV,
|
||||||
|
TWTKPR_ACCESS_SECRET: process.env.TWTKPR_ACCESS_SECRET || process.env.ACCESS_SECRET,
|
||||||
|
TWTKPR_DEFAULT_ROUTE: process.env.TWTKPR_DEFAULT_ROUTE || process.env.DEFAULT_ROUTE,
|
||||||
|
TWTKPR_PRIVATE_DIRECTORY: process.env.TWTKPR_PRIVATE_DIRECTORY || process.env.PRIVATE_DIRECTORY,
|
||||||
|
TWTKPR_PUBLIC_DIRECTORY: process.env.TWTKPR_PUBLIC_DIRECTORY || process.env.PUBLIC_DIRECTORY,
|
||||||
|
TWTKPR_REFRESH_SECRET: process.env.TWTKPR_REFRESH_SECRET || process.env.REFRESH_SECRET,
|
||||||
|
TWTKPR_TWTXT_FILENAME: process.env.TWTKPR_TWTXT_FILENAME || process.env.TWTXT_FILENAME,
|
||||||
|
TWTKPR_POST_LIMITER_ACTIVE: process.env.TWTKPR_POST_LIMITER_ACTIVE ||
|
||||||
|
process.env.POST_LIMITER_ACTIVE,
|
||||||
|
TWTKPR_POST_LIMITER_WINDOW_MS: process.env.TWTKPR_POST_LIMITER_WINDOW_MS ||
|
||||||
|
process.env.POST_LIMITER_WINDOW_MS,
|
||||||
|
TWTKPR_POST_LIMITER_LIMIT: process.env.TWTKPR_POST_LIMITER_LIMIT || process.env.POST_LIMITER_LIMIT,
|
||||||
|
TWTKPR_POST_LIMITER_MESSAGE: process.env.TWTKPR_POST_LIMITER_MESSAGE ||
|
||||||
|
process.env.POST_LIMITER_MESSAGE,
|
||||||
|
TWTKPR_POST_LIMITER_STATUS_CODE: process.env.TWTKPR_POST_LIMITER_STATUS_CODE ||
|
||||||
|
process.env.POST_LIMITER_STATUS_CODE,
|
||||||
|
TWTKPR_POST_LIMITER_HANDLER: process.env.TWTKPR_POST_LIMITER_HANDLER ||
|
||||||
|
process.env.POST_LIMITER_HANDLER,
|
||||||
|
TWTKPR_POST_LIMITER_LEGACY_HEADERS: process.env.TWTKPR_POST_LIMITER_LEGACY_HEADERS ||
|
||||||
|
process.env.POST_LIMITER_LEGACY_HEADERS,
|
||||||
|
TWTKPR_POST_LIMITER_STANDARD_HEADERS: process.env.TWTKPR_POST_LIMITER_STANDARD_HEADERS ||
|
||||||
|
process.env.POST_LIMITER_STANDARD_HEADERS,
|
||||||
|
TWTKPR_POST_LIMITER_IDENTIFIER: process.env.TWTKPR_POST_LIMITER_IDENTIFIER ||
|
||||||
|
process.env.POST_LIMITER_IDENTIFIER,
|
||||||
|
TWTKPR_POST_LIMITER_STORE: process.env.TWTKPR_POST_LIMITER_STORE || process.env.POST_LIMITER_STORE,
|
||||||
|
TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR: process.env.TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR ||
|
||||||
|
process.env.POST_LIMITER_PASS_ON_STORE_ERROR,
|
||||||
|
TWTKPR_POST_LIMITER_KEY_GENERATOR: process.env.TWTKPR_POST_LIMITER_KEY_GENERATOR ||
|
||||||
|
process.env.POST_LIMITER_KEY_GENERATOR,
|
||||||
|
TWTKPR_POST_LIMITER_IPV6_SUBNET: process.env.TWTKPR_POST_LIMITER_IPV6_SUBNET ||
|
||||||
|
process.env.POST_LIMITER_IPV6_SUBNET,
|
||||||
|
TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME: process.env.TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME ||
|
||||||
|
process.env.POST_LIMITER_REQUEST_PROPERTY_NAME,
|
||||||
|
TWTKPR_POST_LIMITER_SKIP: process.env.TWTKPR_POST_LIMITER_SKIP || process.env.POST_LIMITER_SKIP,
|
||||||
|
TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS: process.env.TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS ||
|
||||||
|
process.env.POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS,
|
||||||
|
TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS: process.env.TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS ||
|
||||||
|
process.env.POST_LIMITER_SKIP_FAILED_REQUESTS,
|
||||||
|
TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL: process.env.TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL ||
|
||||||
|
process.env.POST_LIMITER_REQUEST_WAS_SUCCESSFUL,
|
||||||
|
TWTKPR_POST_LIMITER_VALIDATE: process.env.TWTKPR_POST_LIMITER_VALIDATE ||
|
||||||
|
process.env.POST_LIMITER_VALIDATE,
|
||||||
|
TWTKPR_QUERY_PARAMETER_APP: process.env.TWTKPR_QUERY_PARAMETER_APP ||
|
||||||
|
process.env.QUERY_PARAMETER_APP,
|
||||||
|
TWTKPR_QUERY_PARAMETER_CSS: process.env.TWTKPR_QUERY_PARAMETER_CSS ||
|
||||||
|
process.env.QUERY_PARAMETER_CSS,
|
||||||
|
TWTKPR_QUERY_PARAMETER_FOLLOWING: process.env.TWTKPR_QUERY_PARAMETER_FOLLOWING ||
|
||||||
|
process.env.QUERY_PARAMETER_FOLLOWING,
|
||||||
|
TWTKPR_QUERY_PARAMETER_JS: process.env.TWTKPR_QUERY_PARAMETER_JS || process.env.QUERY_PARAMETER_JS,
|
||||||
|
TWTKPR_QUERY_PARAMETER_LOGOUT: process.env.TWTKPR_QUERY_PARAMETER_LOGOUT ||
|
||||||
|
process.env.QUERY_PARAMETER_LOGOUT,
|
||||||
|
TWTKPR_QUERY_PARAMETER_METADATA: process.env.TWTKPR_QUERY_PARAMETER_METADATA ||
|
||||||
|
process.env.QUERY_PARAMETER_METADATA,
|
||||||
|
TWTKPR_QUERY_PARAMETER_TWT: process.env.TWTKPR_QUERY_PARAMETER_TWT ||
|
||||||
|
process.env.QUERY_PARAMETER_TWT,
|
||||||
|
TWTKPR_QUERY_PARAMETER_TWTS: process.env.TWTKPR_QUERY_PARAMETER_TWTS ||
|
||||||
|
process.env.QUERY_PARAMETER_TWTS,
|
||||||
|
TWTKPR_UPLOAD_ACTIVE: process.env.TWTKPR_UPLOAD_ACTIVE || process.env.UPLOAD_ACTIVE,
|
||||||
|
TWTKPR_UPLOAD_ALLOW_EMPTY_FILES: process.env.TWTKPR_UPLOAD_ALLOW_EMPTY_FILES ||
|
||||||
|
process.env.UPLOAD_ALLOW_EMPTY_FILES,
|
||||||
|
TWTKPR_UPLOAD_ALLOWED_MIME_TYPES: process.env.TWTKPR_UPLOAD_ALLOWED_MIME_TYPES ||
|
||||||
|
process.env.UPLOAD_ALLOWED_MIME_TYPES,
|
||||||
|
TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS: process.env.TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS ||
|
||||||
|
process.env.UPLOAD_CREATE_DIRS_FROM_UPLOADS,
|
||||||
|
TWTKPR_UPLOAD_DIRECTORY: process.env.TWTKPR_UPLOAD_DIRECTORY ||
|
||||||
|
process.env.UPLOAD_DIRECTORY ||
|
||||||
|
process.env.UPLOAD_DIR,
|
||||||
|
TWTKPR_UPLOAD_ENCODING: process.env.TWTKPR_UPLOAD_ENCODING || process.env.UPLOAD_ENCODING,
|
||||||
|
TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER: process.env.TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER ||
|
||||||
|
process.env.UPLOAD_FILE_WRITE_STREAM_HANDLER,
|
||||||
|
TWTKPR_UPLOAD_FILENAME: process.env.TWTKPR_UPLOAD_FILENAME || process.env.UPLOAD_FILENAME,
|
||||||
|
TWTKPR_UPLOAD_FILTER: process.env.TWTKPR_UPLOAD_FILTER || process.env.UPLOAD_FILTER,
|
||||||
|
TWTKPR_UPLOAD_HASH_ALGORITHM: process.env.TWTKPR_UPLOAD_HASH_ALGORITHM ||
|
||||||
|
process.env.UPLOAD_HASH_ALGORITHM,
|
||||||
|
TWTKPR_UPLOAD_KEEP_EXTENSIONS: process.env.TWTKPR_UPLOAD_KEEP_EXTENSIONS ||
|
||||||
|
process.env.UPLOAD_KEEP_EXTENSIONS,
|
||||||
|
TWTKPR_UPLOAD_MAX_FIELDS: process.env.TWTKPR_UPLOAD_MAX_FIELDS || process.env.UPLOAD_MAX_FIELDS,
|
||||||
|
TWTKPR_UPLOAD_MAX_FIELDS_SIZE: process.env.TWTKPR_UPLOAD_MAX_FIELDS_SIZE ||
|
||||||
|
process.env.UPLOAD_MAX_FIELDS_SIZE,
|
||||||
|
TWTKPR_UPLOAD_MAX_FILE_SIZE: process.env.TWTKPR_UPLOAD_MAX_FILE_SIZE ||
|
||||||
|
process.env.UPLOAD_MAX_FILE_SIZE,
|
||||||
|
TWTKPR_UPLOAD_MAX_FILES: process.env.TWTKPR_UPLOAD_MAX_FILES || process.env.UPLOAD_MAX_FILES,
|
||||||
|
TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE: process.env.TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE ||
|
||||||
|
process.env.UPLOAD_MAX_TOTAL_FILE_SIZE,
|
||||||
|
TWTKPR_UPLOAD_MIN_FILE_SIZE: process.env.TWTKPR_UPLOAD_MIN_FILE_SIZE ||
|
||||||
|
process.env.UPLOAD_MIN_FILE_SIZE,
|
||||||
|
TWTKPR_UPLOAD_ROUTE: process.env.TWTKPR_UPLOAD_ROUTE || process.env.UPLOAD_ROUTE,
|
||||||
|
});
|
||||||
|
if (!parsedEnv.TWTKPR_ACCESS_SECRET)
|
||||||
|
throw new Error('Either ACCESS_SECRET or TWTKPR_ACCESS_SECRET must be provided');
|
||||||
|
if (!parsedEnv.TWTKPR_REFRESH_SECRET)
|
||||||
|
throw new Error('Either REFRESH_SECRET or TWTKPR_REFRESH_SECRET must be provided');
|
||||||
|
return parsedEnv;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
console.error('Missing environment variables:', error.issues.flatMap((issue) => `${issue.path} or TWTKPR_${issue.path}`));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const env = parseEnv();
|
||||||
|
export const __dirname = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
//# sourceMappingURL=env.js.map
|
||||||
1
dist/src/lib/env.js.map
vendored
Normal file
1
dist/src/lib/env.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
dist/src/lib/getConfiguration.d.ts
vendored
Normal file
7
dist/src/lib/getConfiguration.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../types.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param initialConfiguration
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function getConfiguration(initialConfiguration: TwtKprPluginConfiguration): TwtKprConfiguration;
|
||||||
103
dist/src/lib/getConfiguration.js
vendored
Normal file
103
dist/src/lib/getConfiguration.js
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { env } from './env.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param allowedMimeTypes
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getDestinationByMimeTypeConfiguration = (allowedMimeTypes) => {
|
||||||
|
const fallback = {
|
||||||
|
audio: {
|
||||||
|
directory: 'audio',
|
||||||
|
rename: false,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
directory: 'images',
|
||||||
|
rename: true,
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
directory: 'videos',
|
||||||
|
rename: true,
|
||||||
|
},
|
||||||
|
'*': {
|
||||||
|
directory: 'files',
|
||||||
|
rename: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mimeTypeArrayReducer = (acc, curr) => {
|
||||||
|
if (fallback[curr])
|
||||||
|
acc[curr] = fallback[curr];
|
||||||
|
else
|
||||||
|
acc[curr] = {
|
||||||
|
directory: `${curr}s`,
|
||||||
|
rename: false,
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
};
|
||||||
|
if (!allowedMimeTypes)
|
||||||
|
return fallback;
|
||||||
|
if (typeof allowedMimeTypes === 'string')
|
||||||
|
return allowedMimeTypes
|
||||||
|
.split(',')
|
||||||
|
.map((val) => val.trim())
|
||||||
|
.reduce(mimeTypeArrayReducer, {});
|
||||||
|
if (Array.isArray(allowedMimeTypes))
|
||||||
|
return allowedMimeTypes.reduce(mimeTypeArrayReducer, {});
|
||||||
|
if (typeof allowedMimeTypes === 'object')
|
||||||
|
return allowedMimeTypes;
|
||||||
|
return fallback;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param initialConfiguration
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function getConfiguration(initialConfiguration) {
|
||||||
|
const { mainRoute = env.TWTKPR_DEFAULT_ROUTE, privateDirectory = env.TWTKPR_PRIVATE_DIRECTORY, publicDirectory = env.TWTKPR_PUBLIC_DIRECTORY, twtxtFilename = env.TWTKPR_TWTXT_FILENAME, postLimiterConfiguration, queryParameters, uploadConfiguration, } = initialConfiguration ?? {};
|
||||||
|
const { active: postLimiterActive = env.TWTKPR_POST_LIMITER_ACTIVE, ...otherPostLimiterProps } = postLimiterConfiguration ?? {};
|
||||||
|
const { app = env.TWTKPR_QUERY_PARAMETER_APP, css = env.TWTKPR_QUERY_PARAMETER_CSS, following = env.TWTKPR_QUERY_PARAMETER_FOLLOWING, js = env.TWTKPR_QUERY_PARAMETER_JS, logout = env.TWTKPR_QUERY_PARAMETER_LOGOUT, metadata = env.TWTKPR_QUERY_PARAMETER_METADATA, twt = env.TWTKPR_QUERY_PARAMETER_TWT, twts = env.TWTKPR_QUERY_PARAMETER_TWTS, } = queryParameters ?? {};
|
||||||
|
const { active: uploadActive = env.TWTKPR_UPLOAD_ACTIVE, allowEmptyFiles = env.TWTKPR_UPLOAD_ALLOW_EMPTY_FILES, allowedMimeTypes = env.TWTKPR_UPLOAD_ALLOWED_MIME_TYPES, createDirsFromUploads = env.TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS, directory = env.TWTKPR_UPLOAD_DIRECTORY, encoding = env.TWTKPR_UPLOAD_ENCODING, fileWriteStreamHandler, filter = () => true, hashAlgorithm = env.TWTKPR_UPLOAD_HASH_ALGORITHM, keepExtensions = env.TWTKPR_UPLOAD_KEEP_EXTENSIONS, maxFields = env.TWTKPR_UPLOAD_MAX_FIELDS, maxFileSize = env.TWTKPR_UPLOAD_MAX_FIELDS_SIZE, maxFiles = env.TWTKPR_UPLOAD_MAX_FILES, maxTotalFileSize = env.TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE, minFileSize = env.TWTKPR_UPLOAD_MIN_FILE_SIZE, route = env.TWTKPR_UPLOAD_ROUTE, } = uploadConfiguration ?? {};
|
||||||
|
return {
|
||||||
|
// secrets cannot be provided through configuration file, must use ENV / .env
|
||||||
|
accessSecret: env.TWTKPR_ACCESS_SECRET,
|
||||||
|
refreshSecret: env.TWTKPR_REFRESH_SECRET,
|
||||||
|
mainRoute,
|
||||||
|
privateDirectory,
|
||||||
|
publicDirectory,
|
||||||
|
twtxtFilename,
|
||||||
|
postLimiterConfiguration: {
|
||||||
|
active: postLimiterActive,
|
||||||
|
...(otherPostLimiterProps ?? {}),
|
||||||
|
},
|
||||||
|
queryParameters: {
|
||||||
|
...queryParameters,
|
||||||
|
app,
|
||||||
|
css,
|
||||||
|
following,
|
||||||
|
js,
|
||||||
|
logout,
|
||||||
|
metadata,
|
||||||
|
twt,
|
||||||
|
twts,
|
||||||
|
},
|
||||||
|
uploadConfiguration: {
|
||||||
|
...uploadConfiguration,
|
||||||
|
active: uploadActive,
|
||||||
|
allowEmptyFiles,
|
||||||
|
allowedMimeTypes: getDestinationByMimeTypeConfiguration(allowedMimeTypes),
|
||||||
|
createDirsFromUploads,
|
||||||
|
directory,
|
||||||
|
encoding,
|
||||||
|
fileWriteStreamHandler,
|
||||||
|
filter,
|
||||||
|
hashAlgorithm: hashAlgorithm,
|
||||||
|
keepExtensions,
|
||||||
|
maxFields,
|
||||||
|
maxFileSize,
|
||||||
|
maxFiles,
|
||||||
|
maxTotalFileSize,
|
||||||
|
minFileSize,
|
||||||
|
route,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=getConfiguration.js.map
|
||||||
1
dist/src/lib/getConfiguration.js.map
vendored
Normal file
1
dist/src/lib/getConfiguration.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"getConfiguration.js","sourceRoot":"","sources":["../../../src/lib/getConfiguration.ts"],"names":[],"mappings":"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"}
|
||||||
13
dist/src/lib/refreshTokensDB.d.ts
vendored
Normal file
13
dist/src/lib/refreshTokensDB.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export interface RefreshTokensDB {
|
||||||
|
cleanUp: () => void;
|
||||||
|
get: (key: string) => string[];
|
||||||
|
getObject: () => Record<string, string[]>;
|
||||||
|
remove: (key?: string) => void;
|
||||||
|
set: (key?: string, value?: string[]) => string[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function refreshTokensDB(directory: string): Promise<RefreshTokensDB>;
|
||||||
40
dist/src/lib/refreshTokensDB.js
vendored
Normal file
40
dist/src/lib/refreshTokensDB.js
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import arrayDB from './arrayDB.js';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
const debug = Debug('twtkpr:simpleDB');
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function refreshTokensDB(directory) {
|
||||||
|
const refreshTokensDB = await arrayDB('refreshTokens', directory);
|
||||||
|
const get = (key) => {
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
debug('get', key, currentTime);
|
||||||
|
return (refreshTokensDB.get(key) ?? []).filter((token) => {
|
||||||
|
const val = jwt.decode(token);
|
||||||
|
return val && (val.exp ?? 0) >= currentTime;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const cleanUp = () => {
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
const tokenListByUserId = refreshTokensDB.getObject();
|
||||||
|
debug('cleanup', currentTime);
|
||||||
|
Object.keys(tokenListByUserId).forEach((userId) => {
|
||||||
|
const tokens = refreshTokensDB.get(userId).filter((token) => {
|
||||||
|
const val = jwt.decode(token);
|
||||||
|
return val && (val.exp ?? 0) >= currentTime;
|
||||||
|
});
|
||||||
|
debug(`setting tokens for ${userId}`, tokens);
|
||||||
|
refreshTokensDB.set(userId, tokens);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
cleanUp();
|
||||||
|
return {
|
||||||
|
...refreshTokensDB,
|
||||||
|
cleanUp,
|
||||||
|
get,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=refreshTokensDB.js.map
|
||||||
1
dist/src/lib/refreshTokensDB.js.map
vendored
Normal file
1
dist/src/lib/refreshTokensDB.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"refreshTokensDB.js","sourceRoot":"","sources":["../../../src/lib/refreshTokensDB.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,cAAc,CAAC;AAU/B,MAAM,KAAK,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC;AAEvC;;;;GAIG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,eAAe,CAAC,SAAiB;IAC9D,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAElE,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,EAAE;QAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAClD,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QAE/B,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACxD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9B,OAAO,GAAG,IAAI,CAAE,GAAsB,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,WAAW,CAAC;QACjE,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,GAAG,EAAE;QACpB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAClD,MAAM,iBAAiB,GAAG,eAAe,CAAC,SAAS,EAAE,CAAC;QAEtD,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAE9B,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;YACjD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC9B,OAAO,GAAG,IAAI,CAAE,GAAsB,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,WAAW,CAAC;YACjE,CAAC,CAAC,CAAC;YAEH,KAAK,CAAC,sBAAsB,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;YAE9C,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,EAAE,CAAC;IAEV,OAAO;QACN,GAAG,eAAe;QAClB,OAAO;QACP,GAAG;KACgB,CAAC;AACtB,CAAC"}
|
||||||
12
dist/src/lib/simpleDB.d.ts
vendored
Normal file
12
dist/src/lib/simpleDB.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function simpleDB(name: string, directory: string): Promise<{
|
||||||
|
get: (key?: string) => string;
|
||||||
|
getObject: () => Record<string, string>;
|
||||||
|
remove: (key?: string) => void;
|
||||||
|
set: (key?: string, value?: string) => string;
|
||||||
|
}>;
|
||||||
71
dist/src/lib/simpleDB.js
vendored
Normal file
71
dist/src/lib/simpleDB.js
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { loadObjectFromJson, saveToJson } from './utils.js';
|
||||||
|
const debug = Debug('twtkpr:simpleDB');
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function simpleDB(name, directory) {
|
||||||
|
let theName;
|
||||||
|
let dataObject;
|
||||||
|
const get = (key = '') => {
|
||||||
|
debug('get', { key });
|
||||||
|
if (!theName || !dataObject)
|
||||||
|
throw new Error('DB must be initialized first');
|
||||||
|
key = key?.trim();
|
||||||
|
if (!key)
|
||||||
|
throw new Error('a valid key must be provided');
|
||||||
|
return dataObject[key];
|
||||||
|
};
|
||||||
|
const getObject = () => dataObject;
|
||||||
|
const initialize = async (dbName = '') => {
|
||||||
|
debug('initialize starting', { dbName });
|
||||||
|
dbName = dbName?.trim();
|
||||||
|
if (!dbName)
|
||||||
|
throw new Error('a valid name must be provided');
|
||||||
|
try {
|
||||||
|
dataObject = await loadObjectFromJson(path.join(directory, `${dbName}.json`));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
debug('initialize read error', { err });
|
||||||
|
if (err.code === 'ENOENT')
|
||||||
|
dataObject = {};
|
||||||
|
else
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// only initialize (and set name) if everything passes
|
||||||
|
theName = dbName;
|
||||||
|
debug('initialize complete', { dataObject, name: theName });
|
||||||
|
};
|
||||||
|
const remove = (key = '') => {
|
||||||
|
debug('remove', { key });
|
||||||
|
if (!theName || !dataObject)
|
||||||
|
throw new Error('DB must be initialized first');
|
||||||
|
key = key?.trim();
|
||||||
|
if (!key)
|
||||||
|
throw new Error('a valid key must be provided');
|
||||||
|
delete dataObject[key];
|
||||||
|
};
|
||||||
|
const set = (key = '', value = '') => {
|
||||||
|
debug('set', { key });
|
||||||
|
if (!theName || !dataObject)
|
||||||
|
throw new Error('DB must be initialized first');
|
||||||
|
key = key?.trim();
|
||||||
|
if (!key)
|
||||||
|
throw new Error('a valid key must be provided');
|
||||||
|
dataObject[key] = value;
|
||||||
|
saveToJson(dataObject, path.join(directory, `${name}.json`));
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
await initialize(name);
|
||||||
|
return {
|
||||||
|
get,
|
||||||
|
getObject,
|
||||||
|
remove,
|
||||||
|
set,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=simpleDB.js.map
|
||||||
1
dist/src/lib/simpleDB.js.map
vendored
Normal file
1
dist/src/lib/simpleDB.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"simpleDB.js","sourceRoot":"","sources":["../../../src/lib/simpleDB.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,KAAK,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,SAAiB;IACrE,IAAI,OAAe,CAAC;IACpB,IAAI,UAAkC,CAAC;IAEvC,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE;QACxB,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtB,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU;YAC1B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAEjD,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAE1D,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC;IAEnC,MAAM,UAAU,GAAG,KAAK,EAAE,MAAM,GAAG,EAAE,EAAE,EAAE;QACxC,KAAK,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAEzC,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAE9D,IAAI,CAAC;YACJ,UAAU,GAAG,MAAM,kBAAkB,CACpC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,CACtC,CAAC;QACH,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACvB,KAAK,CAAC,uBAAuB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YAExC,IAAK,GAAwB,CAAC,IAAI,KAAK,QAAQ;gBAAE,UAAU,GAAG,EAAE,CAAC;;gBAC5D,MAAM,GAAG,CAAC;QAChB,CAAC;QAED,sDAAsD;QACtD,OAAO,GAAG,MAAM,CAAC;QACjB,KAAK,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE;QAC3B,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAEzB,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU;YAC1B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAEjD,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAE1D,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,KAAK,GAAG,EAAE,EAAE,EAAE;QACpC,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtB,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU;YAC1B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAEjD,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAE1D,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACxB,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;QAE7D,OAAO,KAAK,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IAEvB,OAAO;QACN,GAAG;QACH,SAAS;QACT,MAAM;QACN,GAAG;KACH,CAAC;AACH,CAAC"}
|
||||||
11
dist/src/lib/twtxtCache.d.ts
vendored
Normal file
11
dist/src/lib/twtxtCache.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { NodeCache } from '@cacheable/node-cache';
|
||||||
|
import { TwtKprConfiguration } from '../types.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function twtxtCache({ publicDirectory, twtxtFilename, }: Pick<TwtKprConfiguration, 'publicDirectory' | 'twtxtFilename'>): {
|
||||||
|
cache: NodeCache<unknown>;
|
||||||
|
reloadCache: () => Promise<void>;
|
||||||
|
};
|
||||||
31
dist/src/lib/twtxtCache.js
vendored
Normal file
31
dist/src/lib/twtxtCache.js
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import fsp from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { NodeCache } from '@cacheable/node-cache';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import { parseTwtxt } from 'twtxt-lib';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function twtxtCache({ publicDirectory, twtxtFilename, }) {
|
||||||
|
let isLoaded = false;
|
||||||
|
const debug = Debug('twtkpr:twtxtCache');
|
||||||
|
const cache = new NodeCache();
|
||||||
|
const reloadCache = async () => {
|
||||||
|
const fileText = await fsp.readFile(path.join(publicDirectory, twtxtFilename), 'utf8');
|
||||||
|
const parsedFile = parseTwtxt(fileText);
|
||||||
|
Object.keys(parsedFile).forEach((key) => {
|
||||||
|
cache.set(key, parsedFile[key]); // 10 seconds
|
||||||
|
});
|
||||||
|
cache.set('source', fileText);
|
||||||
|
debug(`cache ${isLoaded ? 're' : ''}loaded`);
|
||||||
|
isLoaded = true;
|
||||||
|
};
|
||||||
|
reloadCache();
|
||||||
|
return {
|
||||||
|
cache,
|
||||||
|
reloadCache,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=twtxtCache.js.map
|
||||||
1
dist/src/lib/twtxtCache.js.map
vendored
Normal file
1
dist/src/lib/twtxtCache.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"twtxtCache.js","sourceRoot":"","sources":["../../../src/lib/twtxtCache.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,kBAAkB,CAAC;AACnC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAClC,eAAe,EACf,aAAa,GACmD;IAChE,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,KAAK,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,IAAI,SAAS,EAAE,CAAC;IAE9B,MAAM,WAAW,GAAG,KAAK,IAAI,EAAE;QAC9B,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,QAAQ,CAClC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,EACzC,MAAM,CACN,CAAC;QAEF,MAAM,UAAU,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACvC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,GAA8B,CAAC,CAAC,CAAC,CAAC,aAAa;QAC1E,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9B,KAAK,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE7C,QAAQ,GAAG,IAAI,CAAC;IACjB,CAAC,CAAC;IAEF,WAAW,EAAE,CAAC;IAEd,OAAO;QACN,KAAK;QACL,WAAW;KACX,CAAC;AACH,CAAC"}
|
||||||
12
dist/src/lib/userDB.d.ts
vendored
Normal file
12
dist/src/lib/userDB.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export interface UserDB {
|
||||||
|
get: (key?: string) => string;
|
||||||
|
getObject: () => Record<string, string>;
|
||||||
|
remove: (key?: string) => void;
|
||||||
|
set: (key?: string, value?: string) => string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function userDB(directory: string): Promise<UserDB>;
|
||||||
10
dist/src/lib/userDB.js
vendored
Normal file
10
dist/src/lib/userDB.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import simpleDB from './simpleDB.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param directory
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function userDB(directory) {
|
||||||
|
return simpleDB('user', directory);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=userDB.js.map
|
||||||
1
dist/src/lib/userDB.js.map
vendored
Normal file
1
dist/src/lib/userDB.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"userDB.js","sourceRoot":"","sources":["../../../src/lib/userDB.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;AASrC;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,SAAiB;IAC/C,OAAO,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAoB,CAAC;AACvD,CAAC"}
|
||||||
45
dist/src/lib/utils.d.ts
vendored
Normal file
45
dist/src/lib/utils.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param secret
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare const generateAccessToken: (userId: string, secret?: string) => string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param val
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare const generateEtag: (val: string) => string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param secret
|
||||||
|
* @param extendRefresh
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare const generateRefreshToken: (userId: string, secret?: string, extendRefresh?: boolean) => string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare const getQueryParameterArray: (value?: unknown | unknown[]) => string[];
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare const getValueOrFirstEntry: (value: string | string[]) => string | string[];
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare const loadObjectFromJson: (filePath: string) => Promise<any>;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param contents
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
export declare const saveToJson: (contents: object | string, filePath: string) => Promise<void>;
|
||||||
67
dist/src/lib/utils.js
vendored
Normal file
67
dist/src/lib/utils.js
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import crypto from 'node:crypto';
|
||||||
|
import { readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param secret
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const generateAccessToken = (userId, secret = '') => jwt.sign({ id: userId }, secret, { expiresIn: '10m' });
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param val
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const generateEtag = (val) => crypto.createHash('sha256').update(val).digest('hex');
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @param secret
|
||||||
|
* @param extendRefresh
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const generateRefreshToken = (userId, secret = '', extendRefresh = false) => {
|
||||||
|
const tokenId = uuidv4(); // unique ID for the refresh token
|
||||||
|
const token = jwt.sign({ id: userId, tokenId }, secret, {
|
||||||
|
expiresIn: extendRefresh ? '7d' : '1h',
|
||||||
|
});
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getQueryParameterArray = (value = []) => Array.isArray(value)
|
||||||
|
? value.map((val) => `${val}`.trim())
|
||||||
|
: [`${value}`.trim()];
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getValueOrFirstEntry = (value) => Array.isArray(value) && value.length ? value[0] : value;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const loadObjectFromJson = async (filePath) => {
|
||||||
|
const contents = await readFile(filePath, { encoding: 'utf8' });
|
||||||
|
return JSON.parse(contents);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param contents
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
export const saveToJson = async (contents, filePath) => {
|
||||||
|
const stringContents = typeof contents === 'string' ? contents : JSON.stringify(contents, null, 2);
|
||||||
|
await writeFile(filePath, stringContents, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
flag: 'w',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=utils.js.map
|
||||||
1
dist/src/lib/utils.js.map
vendored
Normal file
1
dist/src/lib/utils.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAc,EAAE,MAAM,GAAG,EAAE,EAAE,EAAE,CAClE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AAExD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,EAAE,CAC3C,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CACnC,MAAc,EACd,MAAM,GAAG,EAAE,EACX,aAAa,GAAG,KAAK,EACpB,EAAE;IACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC,kCAAkC;IAE5D,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE;QACvD,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;KACtC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACd,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,QAA6B,EAAE,EAAE,EAAE,CACzE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;IACnB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAExB;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAwB,EAAE,EAAE,CAChE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC5D,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC7B,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAC9B,QAAyB,EACzB,QAAgB,EACf,EAAE;IACH,MAAM,cAAc,GACnB,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE7E,MAAM,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE;QACzC,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,GAAG;KACT,CAAC,CAAC;AACJ,CAAC,CAAC"}
|
||||||
9
dist/src/middlewares/authCheckJWT.d.ts
vendored
Normal file
9
dist/src/middlewares/authCheckJWT.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Request } from 'express';
|
||||||
|
import { TwtKprConfiguration } from '../types.js';
|
||||||
|
/**
|
||||||
|
* Checks for a valid JWT, and returns a boolean indicating the result
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function authCheckJWT(req: Request, config: TwtKprConfiguration): Promise<boolean>;
|
||||||
32
dist/src/middlewares/authCheckJWT.js
vendored
Normal file
32
dist/src/middlewares/authCheckJWT.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
const debug = Debug('twtkpr:authCheckJWT');
|
||||||
|
/**
|
||||||
|
* Checks for a valid JWT, and returns a boolean indicating the result
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function authCheckJWT(req, config) {
|
||||||
|
debug('beginning');
|
||||||
|
const token = req.header('Authorization')?.split(' ')[1];
|
||||||
|
if (!token) {
|
||||||
|
debug('no token');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
debug('token present');
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.accessSecret);
|
||||||
|
debug({ decoded });
|
||||||
|
if (!decoded.id)
|
||||||
|
return false;
|
||||||
|
req.username = decoded.id;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
debug('invalid token');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
debug('token good');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=authCheckJWT.js.map
|
||||||
1
dist/src/middlewares/authCheckJWT.js.map
vendored
Normal file
1
dist/src/middlewares/authCheckJWT.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"authCheckJWT.js","sourceRoot":"","sources":["../../../src/middlewares/authCheckJWT.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,cAAc,CAAC;AAI/B,MAAM,KAAK,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,YAAY,CACzC,GAAY,EACZ,MAA2B;IAE3B,KAAK,CAAC,WAAW,CAAC,CAAC;IAEnB,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,KAAK,CAAC,UAAU,CAAC,CAAC;QAClB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,CAAC;IAEvB,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,CAAmB,CAAC;QACzE,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAEnB,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;QAC9B,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACR,KAAK,CAAC,eAAe,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACb,CAAC"}
|
||||||
1
dist/src/middlewares/csrfProtection.d.ts
vendored
Normal file
1
dist/src/middlewares/csrfProtection.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
27
dist/src/middlewares/csrfProtection.js
vendored
Normal file
27
dist/src/middlewares/csrfProtection.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export {};
|
||||||
|
/*
|
||||||
|
import { doubleCsrf } from "csrf-csrf";
|
||||||
|
|
||||||
|
const {
|
||||||
|
invalidCsrfTokenError, // This is just for convenience if you plan on making your own middleware.
|
||||||
|
generateCsrfToken, // Use this in your routes to provide a CSRF token.
|
||||||
|
validateRequest, // Also a convenience if you plan on making your own middleware.
|
||||||
|
doubleCsrfProtection, // This is the default CSRF protection middleware.
|
||||||
|
} = doubleCsrf({
|
||||||
|
getSecret: (req) => 'return some cryptographically pseudorandom secret here',
|
||||||
|
getSessionIdentifier: (req) => req.session.id // return the requests unique identifier
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const csrfTokenRoute = (req, res) => {
|
||||||
|
const csrfToken = generateCsrfToken(req, res);
|
||||||
|
// You could also pass the token into the context of a HTML response.
|
||||||
|
res.json({ csrfToken });
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
csrfTokenRoute,
|
||||||
|
doubleCsrfProtection,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
//# sourceMappingURL=csrfProtection.js.map
|
||||||
1
dist/src/middlewares/csrfProtection.js.map
vendored
Normal file
1
dist/src/middlewares/csrfProtection.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"csrfProtection.js","sourceRoot":"","sources":["../../../src/middlewares/csrfProtection.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;EAwBE"}
|
||||||
4
dist/src/middlewares/index.d.ts
vendored
Normal file
4
dist/src/middlewares/index.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { default as authCheck } from './authCheckJWT.js';
|
||||||
|
export { default as memoryCache } from './postHandler/memoryCache.js';
|
||||||
|
export { default as postHandler } from './postHandler/index.js';
|
||||||
|
export { default as queryHandler } from './queryHandler/index.js';
|
||||||
5
dist/src/middlewares/index.js
vendored
Normal file
5
dist/src/middlewares/index.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export { default as authCheck } from './authCheckJWT.js';
|
||||||
|
export { default as memoryCache } from './postHandler/memoryCache.js';
|
||||||
|
export { default as postHandler } from './postHandler/index.js';
|
||||||
|
export { default as queryHandler } from './queryHandler/index.js';
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
dist/src/middlewares/index.js.map
vendored
Normal file
1
dist/src/middlewares/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,yBAAyB,CAAC"}
|
||||||
1
dist/src/middlewares/postHandler/index.d.ts
vendored
Normal file
1
dist/src/middlewares/postHandler/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "./postHandler.js";
|
||||||
2
dist/src/middlewares/postHandler/index.js
vendored
Normal file
2
dist/src/middlewares/postHandler/index.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./postHandler.js";
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
dist/src/middlewares/postHandler/index.js.map
vendored
Normal file
1
dist/src/middlewares/postHandler/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC"}
|
||||||
10
dist/src/middlewares/postHandler/login.d.ts
vendored
Normal file
10
dist/src/middlewares/postHandler/login.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
* Handles login request and (if successful) returns the JWT access token wile setting the refresh n the
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function loginHandler(req: Request, res: Response, config: TwtKprConfiguration): Promise<void>;
|
||||||
67
dist/src/middlewares/postHandler/login.js
vendored
Normal file
67
dist/src/middlewares/postHandler/login.js
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import bcrypt from 'bcryptjs';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import { env } from '../../lib/env.js';
|
||||||
|
import refreshTokensDB from '../../lib/refreshTokensDB.js';
|
||||||
|
import userDB from '../../lib/userDB.js';
|
||||||
|
import { generateAccessToken, generateEtag, generateRefreshToken, } from '../../lib/utils.js';
|
||||||
|
const debug = Debug('twtkpr:login');
|
||||||
|
/**
|
||||||
|
* Handles login request and (if successful) returns the JWT access token wile setting the refresh n the
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function loginHandler(req, res, config) {
|
||||||
|
const { accessSecret, privateDirectory, refreshSecret } = config;
|
||||||
|
debug('starting');
|
||||||
|
try {
|
||||||
|
const tokens = await refreshTokensDB(privateDirectory);
|
||||||
|
const users = await userDB(privateDirectory);
|
||||||
|
const { username, password, rememberToggle } = req.body;
|
||||||
|
if (!username || !password || !users.get(username)) {
|
||||||
|
debug('no values found', username);
|
||||||
|
res.status(401).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isMatch = await bcrypt.compare(password, users.get(username));
|
||||||
|
if (!isMatch) {
|
||||||
|
privateDirectory;
|
||||||
|
debug('no match');
|
||||||
|
res.status(401).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug('generating tokens');
|
||||||
|
const accessToken = generateAccessToken(username, accessSecret);
|
||||||
|
debug(`access token: ${accessToken}`);
|
||||||
|
const refreshToken = generateRefreshToken(username, refreshSecret, !!rememberToggle);
|
||||||
|
debug(`refresh token: ${refreshToken}`);
|
||||||
|
debug('setting tokens');
|
||||||
|
tokens.set(username, (tokens.get(username) || []).concat([refreshToken]));
|
||||||
|
debug('setting refreshToken cookie');
|
||||||
|
res.cookie('refreshToken', refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
|
// 1 hour or 7 days
|
||||||
|
maxAge: (rememberToggle ? 1 : 7 * 24) * 60 * 60 * 1000,
|
||||||
|
});
|
||||||
|
if (rememberToggle) {
|
||||||
|
debug('setting accessToken cookie');
|
||||||
|
/*
|
||||||
|
res.cookie('accessToken', accessToken, {
|
||||||
|
httpOnly: false,
|
||||||
|
secure: env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
debug('setting response');
|
||||||
|
res.set('etag', generateEtag(accessToken)).status(200).send(accessToken);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=login.js.map
|
||||||
1
dist/src/middlewares/postHandler/login.js.map
vendored
Normal file
1
dist/src/middlewares/postHandler/login.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/login.ts"],"names":[],"mappings":"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"}
|
||||||
10
dist/src/middlewares/postHandler/logout.d.ts
vendored
Normal file
10
dist/src/middlewares/postHandler/logout.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
* Handles logout request and clears the token cookies
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function logoutHandler(req: Request, res: Response, config: TwtKprConfiguration): Promise<void>;
|
||||||
20
dist/src/middlewares/postHandler/logout.js
vendored
Normal file
20
dist/src/middlewares/postHandler/logout.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
const debug = Debug('twtkpr:logout');
|
||||||
|
/**
|
||||||
|
* Handles logout request and clears the token cookies
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function logoutHandler(req, res, config) {
|
||||||
|
const { mainRoute } = config;
|
||||||
|
debug('logging out');
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.clearCookie('refreshToken')
|
||||||
|
.clearCookie('accessToken')
|
||||||
|
.redirect(mainRoute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=logout.js.map
|
||||||
1
dist/src/middlewares/postHandler/logout.js.map
vendored
Normal file
1
dist/src/middlewares/postHandler/logout.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/logout.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,KAAK,GAAG,KAAK,CAAC,eAAe,CAAC,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,aAAa,CAC1C,GAAY,EACZ,GAAa,EACb,MAA2B;IAE3B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAC7B,KAAK,CAAC,aAAa,CAAC,CAAC;IAErB,GAAG;SACD,MAAM,CAAC,GAAG,CAAC;SACX,WAAW,CAAC,cAAc,CAAC;SAC3B,WAAW,CAAC,aAAa,CAAC;SAC1B,QAAQ,CAAC,SAAS,CAAC,CAAC;IAEtB,OAAO;AACR,CAAC"}
|
||||||
12
dist/src/middlewares/postHandler/memoryCache.d.ts
vendored
Normal file
12
dist/src/middlewares/postHandler/memoryCache.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { NextFunction, Request, Response } from 'express';
|
||||||
|
import NodeCache from '@cacheable/node-cache';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
* @param cache
|
||||||
|
* @param reloadCache
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function memoryCache(req: Request, res: Response, next: NextFunction, cache: NodeCache<unknown>, reloadCache: () => Promise<void>): Promise<void>;
|
||||||
25
dist/src/middlewares/postHandler/memoryCache.js
vendored
Normal file
25
dist/src/middlewares/postHandler/memoryCache.js
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
const debug = Debug('twtkpr:memoryCache');
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
* @param cache
|
||||||
|
* @param reloadCache
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function memoryCache(req, res, next, cache, reloadCache) {
|
||||||
|
if (cache.keys().length && !['DELETE', 'POST', 'PUT'].includes(req.method)) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reloadCache()
|
||||||
|
.then(() => {
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=memoryCache.js.map
|
||||||
1
dist/src/middlewares/postHandler/memoryCache.js.map
vendored
Normal file
1
dist/src/middlewares/postHandler/memoryCache.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"memoryCache.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/memoryCache.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,KAAK,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,WAAW,CACxC,GAAY,EACZ,GAAa,EACb,IAAkB,EAClB,KAAyB,EACzB,WAAgC;IAEhC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,IAAI,EAAE,CAAC;QACP,OAAO;IACR,CAAC;IAED,WAAW,EAAE;SACX,IAAI,CAAC,GAAG,EAAE;QACV,IAAI,EAAE,CAAC;IACR,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACd,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
||||||
7
dist/src/middlewares/postHandler/postHandler.d.ts
vendored
Normal file
7
dist/src/middlewares/postHandler/postHandler.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function postHandler(config: TwtKprConfiguration): import("express-serve-static-core").Router;
|
||||||
60
dist/src/middlewares/postHandler/postHandler.js
vendored
Normal file
60
dist/src/middlewares/postHandler/postHandler.js
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import express from 'express';
|
||||||
|
import rateLimit from 'express-rate-limit';
|
||||||
|
import authCheck from '../../middlewares/authCheckJWT.js';
|
||||||
|
import login from './login.js';
|
||||||
|
import logout from './logout.js';
|
||||||
|
import refresh from './refresh.js';
|
||||||
|
import twt from './twt.js';
|
||||||
|
import editFile from '../putHandler/editFile.js';
|
||||||
|
const debug = Debug('twtkpr:postHandler');
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function postHandler(config) {
|
||||||
|
const { postLimiterConfiguration } = config;
|
||||||
|
const { active: isLimiterActive, ...otherLimiterProps } = postLimiterConfiguration ?? {};
|
||||||
|
const postLimiter = isLimiterActive
|
||||||
|
? rateLimit({
|
||||||
|
...otherLimiterProps,
|
||||||
|
})
|
||||||
|
: (req, res, next) => {
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
const { mainRoute } = config;
|
||||||
|
const router = express.Router();
|
||||||
|
router.post('/', postLimiter, async (req, res, next) => {
|
||||||
|
const { content, type } = req.body ?? {};
|
||||||
|
debug('post', { type, path: req.path });
|
||||||
|
if (type === 'logout') {
|
||||||
|
debug('logging out');
|
||||||
|
res.clearCookie('refreshToken');
|
||||||
|
res.clearCookie('accessToken');
|
||||||
|
res.redirect(mainRoute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === 'login')
|
||||||
|
return login(req, res, config);
|
||||||
|
if (type === 'logout')
|
||||||
|
return logout(req, res, config);
|
||||||
|
if (type === 'refresh')
|
||||||
|
return refresh(req, res, config);
|
||||||
|
debug('checking auth');
|
||||||
|
const isLoggedIn = await authCheck(req, config);
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
debug('auth check failed');
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug('auth check succeeded');
|
||||||
|
if (type === 'twt' || content)
|
||||||
|
return twt(req, res, config);
|
||||||
|
if (type === 'editFile')
|
||||||
|
return editFile(req, res, config);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=postHandler.js.map
|
||||||
1
dist/src/middlewares/postHandler/postHandler.js.map
vendored
Normal file
1
dist/src/middlewares/postHandler/postHandler.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"postHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/postHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,OAA4C,MAAM,SAAS,CAAC;AACnE,OAAO,SAAS,MAAM,oBAAoB,CAAC;AAE3C,OAAO,SAAS,MAAM,mCAAmC,CAAC;AAE1D,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,QAAQ,MAAM,2BAA2B,CAAC;AAEjD,MAAM,KAAK,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAA2B;IAC9D,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,CAAC;IAC5C,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,iBAAiB,EAAE,GACtD,wBAAwB,IAAI,EAAE,CAAC;IAEhC,MAAM,WAAW,GAAG,eAAe;QAClC,CAAC,CAAC,SAAS,CAAC;YACV,GAAG,iBAAiB;SACpB,CAAC;QACH,CAAC,CAAC,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;YACpD,IAAI,EAAE,CAAC;QACR,CAAC,CAAC;IAEJ,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAE7B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACtD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QACzC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAExC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvB,KAAK,CAAC,aAAa,CAAC,CAAC;YACrB,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;YAChC,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;YAC/B,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;QAED,IAAI,IAAI,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QACrD,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QACvD,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAEzD,KAAK,CAAC,eAAe,CAAC,CAAC;QACvB,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,EAAE,CAAC;YACjB,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAC3B,IAAI,EAAE,CAAC;YACP,OAAO;QACR,CAAC;QACD,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAE9B,IAAI,IAAI,KAAK,KAAK,IAAI,OAAO;YAAE,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAC5D,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAE3D,IAAI,EAAE,CAAC;IACR,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AACf,CAAC"}
|
||||||
9
dist/src/middlewares/postHandler/refresh.d.ts
vendored
Normal file
9
dist/src/middlewares/postHandler/refresh.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
* Issues a new JWT and updates the refresh token in the cookie
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export default function refresh(req: Request, res: Response, config: TwtKprConfiguration): Promise<void>;
|
||||||
78
dist/src/middlewares/postHandler/refresh.js
vendored
Normal file
78
dist/src/middlewares/postHandler/refresh.js
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import { env } from '../../lib/env.js';
|
||||||
|
import refreshTokensDB from '../../lib/refreshTokensDB.js';
|
||||||
|
import { generateAccessToken, generateEtag, generateRefreshToken, } from '../../lib/utils.js';
|
||||||
|
const debug = Debug('twtkpr:refresh');
|
||||||
|
/**
|
||||||
|
* Issues a new JWT and updates the refresh token in the cookie
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export default async function refresh(req, res, config) {
|
||||||
|
const send401 = (message) => {
|
||||||
|
debug(message);
|
||||||
|
res
|
||||||
|
.clearCookie('accessToken')
|
||||||
|
.clearCookie('refreshToken')
|
||||||
|
.status(401)
|
||||||
|
.send(message ?? 'Unauthorized');
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const tokens = await refreshTokensDB(config.privateDirectory);
|
||||||
|
const oldToken = req.cookies.refreshToken;
|
||||||
|
debug(oldToken);
|
||||||
|
if (!oldToken)
|
||||||
|
return send401('Unauthorized');
|
||||||
|
let decoded = { id: '' };
|
||||||
|
try {
|
||||||
|
decoded = jwt.verify(oldToken, config.refreshSecret);
|
||||||
|
debug({ decoded });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return send401('Refresh token invalid');
|
||||||
|
}
|
||||||
|
const username = req.username ?? decoded.id;
|
||||||
|
if (!username)
|
||||||
|
return send401('Missing username');
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
// cleanup tokens on load
|
||||||
|
const validTokens = (tokens.get(decoded.id) ?? []).filter((token) => {
|
||||||
|
const val = jwt.decode(token);
|
||||||
|
return val && (val.exp ?? 0) >= currentTime;
|
||||||
|
});
|
||||||
|
// If token is invalid or not the latest one
|
||||||
|
if (!validTokens.includes(oldToken)) {
|
||||||
|
debug('token missing from list');
|
||||||
|
return send401('Invalid refresh token');
|
||||||
|
}
|
||||||
|
debug('generating new tokens');
|
||||||
|
const newAccessToken = generateAccessToken(req.username || decoded.id, config.accessSecret);
|
||||||
|
const newRefreshToken = generateRefreshToken(req.username || decoded.id, config.refreshSecret);
|
||||||
|
debug('updating token list');
|
||||||
|
tokens.set(req.username || decoded.id, validTokens
|
||||||
|
.filter((token) => token !== oldToken)
|
||||||
|
.concat([newRefreshToken]));
|
||||||
|
debug('setting httpOnly cookie with new refresh token');
|
||||||
|
res.cookie('refreshToken', newRefreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'strict',
|
||||||
|
// 1 hour or 7 days
|
||||||
|
maxAge: (!!req.query.rememberToggle ? 1 : 7 * 24) * 60 * 60 * 1000,
|
||||||
|
});
|
||||||
|
// Return the new access token in body
|
||||||
|
debug('generating response');
|
||||||
|
res
|
||||||
|
.set('etag', generateEtag(newAccessToken))
|
||||||
|
.status(200)
|
||||||
|
.send(newAccessToken);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=refresh.js.map
|
||||||
1
dist/src/middlewares/postHandler/refresh.js.map
vendored
Normal file
1
dist/src/middlewares/postHandler/refresh.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/refresh.ts"],"names":[],"mappings":"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"}
|
||||||
9
dist/src/middlewares/postHandler/twt.d.ts
vendored
Normal file
9
dist/src/middlewares/postHandler/twt.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
* Creates a new twt, appending it to the bottom of the TWTXT file
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export default function twt(req: Request, res: Response, config: TwtKprConfiguration): void;
|
||||||
21
dist/src/middlewares/postHandler/twt.js
vendored
Normal file
21
dist/src/middlewares/postHandler/twt.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
/**
|
||||||
|
* Creates a new twt, appending it to the bottom of the TWTXT file
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export default function twt(req, res, config) {
|
||||||
|
const { content } = req.body ?? {};
|
||||||
|
const date = dayjs().format();
|
||||||
|
const twt = `${date}\t${content.trim()}\n`;
|
||||||
|
const stream = fs.createWriteStream(join(config.publicDirectory, config.twtxtFilename), {
|
||||||
|
flags: 'a',
|
||||||
|
});
|
||||||
|
stream.write(twt);
|
||||||
|
stream.end();
|
||||||
|
res.status(200).send(twt);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=twt.js.map
|
||||||
1
dist/src/middlewares/postHandler/twt.js.map
vendored
Normal file
1
dist/src/middlewares/postHandler/twt.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"twt.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/twt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,GAAG,CAC1B,GAAY,EACZ,GAAa,EACb,MAA2B;IAE3B,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IAEnC,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;IAE3C,MAAM,MAAM,GAAG,EAAE,CAAC,iBAAiB,CAClC,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,CAAC,EAClD;QACC,KAAK,EAAE,GAAG;KACV,CACD,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,MAAM,CAAC,GAAG,EAAE,CAAC;IAEb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC"}
|
||||||
9
dist/src/middlewares/putHandler/editFile.d.ts
vendored
Normal file
9
dist/src/middlewares/putHandler/editFile.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Request, Response } from "express";
|
||||||
|
import { TwtKprConfiguration } from "../../types.js";
|
||||||
|
/**
|
||||||
|
* Creates a new twt, appending it to the bottom of the TWTXT file
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export default function editFile(req: Request, res: Response, config: TwtKprConfiguration): void;
|
||||||
23
dist/src/middlewares/putHandler/editFile.js
vendored
Normal file
23
dist/src/middlewares/putHandler/editFile.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
/**
|
||||||
|
* Creates a new twt, appending it to the bottom of the TWTXT file
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export default function editFile(req, res, config) {
|
||||||
|
const { fileContents } = req.body ?? {};
|
||||||
|
if (!fileContents) {
|
||||||
|
res.status(400).send("Missing fileContents");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stream = fs.createWriteStream(path.join(config.publicDirectory, config.twtxtFilename), {
|
||||||
|
flags: "w",
|
||||||
|
start: 0,
|
||||||
|
});
|
||||||
|
stream.write(fileContents);
|
||||||
|
stream.end();
|
||||||
|
res.type("text").status(200).send(fileContents);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=editFile.js.map
|
||||||
1
dist/src/middlewares/putHandler/editFile.js.map
vendored
Normal file
1
dist/src/middlewares/putHandler/editFile.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"editFile.js","sourceRoot":"","sources":["../../../../src/middlewares/putHandler/editFile.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAC/B,GAAY,EACZ,GAAa,EACb,MAA2B;IAE3B,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IAExC,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAC7C,OAAO;IACR,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,CAAC,iBAAiB,CAClC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,CAAC,EACvD;QACC,KAAK,EAAE,GAAG;QACV,KAAK,EAAE,CAAC;KACR,CACD,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,EAAE,CAAC;IAEb,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AACjD,CAAC"}
|
||||||
1
dist/src/middlewares/putHandler/index.d.ts
vendored
Normal file
1
dist/src/middlewares/putHandler/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "./putHandler.js";
|
||||||
2
dist/src/middlewares/putHandler/index.js
vendored
Normal file
2
dist/src/middlewares/putHandler/index.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./putHandler.js";
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
dist/src/middlewares/putHandler/index.js.map
vendored
Normal file
1
dist/src/middlewares/putHandler/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/putHandler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC"}
|
||||||
7
dist/src/middlewares/putHandler/putHandler.d.ts
vendored
Normal file
7
dist/src/middlewares/putHandler/putHandler.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function putHandler(config: TwtKprConfiguration): import("express-serve-static-core").Router;
|
||||||
26
dist/src/middlewares/putHandler/putHandler.js
vendored
Normal file
26
dist/src/middlewares/putHandler/putHandler.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Debug from 'debug';
|
||||||
|
import express from 'express';
|
||||||
|
import authCheck from '../../middlewares/authCheckJWT.js';
|
||||||
|
import editFile from './editFile.js';
|
||||||
|
const debug = Debug('twtkpr:putHandler');
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function putHandler(config) {
|
||||||
|
const router = express.Router();
|
||||||
|
router.put('/', (req, res, next) => {
|
||||||
|
debug('put', { path: req.path });
|
||||||
|
debug('checking auth');
|
||||||
|
if (!authCheck(req, config)) {
|
||||||
|
debug('auth check failed');
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug('auth check succeeded');
|
||||||
|
return editFile(req, res, config);
|
||||||
|
});
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=putHandler.js.map
|
||||||
1
dist/src/middlewares/putHandler/putHandler.js.map
vendored
Normal file
1
dist/src/middlewares/putHandler/putHandler.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"putHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/putHandler/putHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,SAAS,MAAM,mCAAmC,CAAC;AAE1D,OAAO,QAAQ,MAAM,eAAe,CAAC;AAErC,MAAM,KAAK,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,MAA2B;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAClC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAEjC,KAAK,CAAC,eAAe,CAAC,CAAC;QAEvB,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAC3B,IAAI,EAAE,CAAC;YACP,OAAO;QACR,CAAC;QAED,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAE9B,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AACf,CAAC"}
|
||||||
11
dist/src/middlewares/queryHandler/followingHandler.d.ts
vendored
Normal file
11
dist/src/middlewares/queryHandler/followingHandler.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import { QueryParameters } from '../../types.js';
|
||||||
|
import NodeCache from '@cacheable/node-cache';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param cache
|
||||||
|
* @param followingParameter
|
||||||
|
*/
|
||||||
|
export default function followingHandler(req: Request, res: Response, cache: NodeCache<unknown>, followingParameter: QueryParameters['following']): void;
|
||||||
39
dist/src/middlewares/queryHandler/followingHandler.js
vendored
Normal file
39
dist/src/middlewares/queryHandler/followingHandler.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { generateEtag, getQueryParameterArray, getValueOrFirstEntry, } from '../../lib/utils.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param cache
|
||||||
|
* @param followingParameter
|
||||||
|
*/
|
||||||
|
export default function followingHandler(req, res, cache, followingParameter) {
|
||||||
|
const followingsToMatch = getQueryParameterArray(req.query[followingParameter]);
|
||||||
|
const nicksToMatch = getQueryParameterArray(req.query.nick);
|
||||||
|
const urlsToMatch = getQueryParameterArray(req.query.url);
|
||||||
|
const searchTermsToMatch = [
|
||||||
|
...getQueryParameterArray(req.query.search),
|
||||||
|
...getQueryParameterArray(req.query.s),
|
||||||
|
];
|
||||||
|
const wantsJson = req.is('json') ||
|
||||||
|
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';
|
||||||
|
if (wantsJson)
|
||||||
|
res.set('content-type', 'application/json');
|
||||||
|
else
|
||||||
|
res.set('content-type', 'text/plain');
|
||||||
|
const matchedFollowing = cache.get('following').filter(({ nick, url }) => (!followingsToMatch.length ||
|
||||||
|
(followingsToMatch.length === 1 && followingsToMatch[0] === '') ||
|
||||||
|
followingsToMatch.includes(nick) ||
|
||||||
|
followingsToMatch.includes(`@${nick}`) ||
|
||||||
|
followingsToMatch.includes(url)) &&
|
||||||
|
(!nicksToMatch.length ||
|
||||||
|
nicksToMatch.includes(nick) ||
|
||||||
|
nicksToMatch.includes(`@${nick}`)) &&
|
||||||
|
(!urlsToMatch.length || urlsToMatch.includes(url)) &&
|
||||||
|
(!searchTermsToMatch.length ||
|
||||||
|
searchTermsToMatch.some((term) => nick.includes(term) || url.includes(term))));
|
||||||
|
const result = wantsJson
|
||||||
|
? JSON.stringify(matchedFollowing)
|
||||||
|
: matchedFollowing.map(({ nick, url }) => `@${nick} ${url}`).join('\n');
|
||||||
|
res.set('etag', generateEtag(result)).send(result);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=followingHandler.js.map
|
||||||
1
dist/src/middlewares/queryHandler/followingHandler.js.map
vendored
Normal file
1
dist/src/middlewares/queryHandler/followingHandler.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"followingHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/followingHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,oBAAoB,CAAC;AAI5B;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CACvC,GAAY,EACZ,GAAa,EACb,KAAyB,EACzB,kBAAgD;IAEhD,MAAM,iBAAiB,GAAG,sBAAsB,CAC/C,GAAG,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAC7B,CAAC;IAEF,MAAM,YAAY,GAAG,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,WAAW,GAAG,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE1D,MAAM,kBAAkB,GAAG;QAC1B,GAAG,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;QAC3C,GAAG,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;KACtC,CAAC;IAEF,MAAM,SAAS,GACd,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC;QACd,oBAAoB,CAAC,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC;IAC3E,IAAI,SAAS;QAAE,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;;QACtD,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;IAE3C,MAAM,gBAAgB,GAAI,KAAK,CAAC,GAAG,CAAC,WAAW,CAAa,CAAC,MAAM,CAClE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CACjB,CAAC,CAAC,iBAAiB,CAAC,MAAM;QACzB,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;QAC/D,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;QAChC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;QACtC,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC,CAAC,YAAY,CAAC,MAAM;YACpB,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC3B,YAAY,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACnC,CAAC,CAAC,WAAW,CAAC,MAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC,CAAC,kBAAkB,CAAC,MAAM;YAC1B,kBAAkB,CAAC,IAAI,CACtB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CACnD,CAAC,CACJ,CAAC;IAEF,MAAM,MAAM,GAAG,SAAS;QACvB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;QAClC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzE,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACpD,CAAC"}
|
||||||
1
dist/src/middlewares/queryHandler/index.d.ts
vendored
Normal file
1
dist/src/middlewares/queryHandler/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "./queryHandler.js";
|
||||||
2
dist/src/middlewares/queryHandler/index.js
vendored
Normal file
2
dist/src/middlewares/queryHandler/index.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./queryHandler.js";
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
dist/src/middlewares/queryHandler/index.js.map
vendored
Normal file
1
dist/src/middlewares/queryHandler/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC"}
|
||||||
17
dist/src/middlewares/queryHandler/metadataHandler.d.ts
vendored
Normal file
17
dist/src/middlewares/queryHandler/metadataHandler.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import NodeCache from '@cacheable/node-cache';
|
||||||
|
import { QueryParameters } from '../../types.js';
|
||||||
|
export interface MetadataHandler {
|
||||||
|
cache: NodeCache<unknown>;
|
||||||
|
metadataParameter: QueryParameters['metadata'];
|
||||||
|
req: Request;
|
||||||
|
res: Response;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param cache
|
||||||
|
* @param metadataParameter
|
||||||
|
*/
|
||||||
|
export default function metadataHandler(req: Request, res: Response, cache: NodeCache<unknown>, metadataParameter: QueryParameters['metadata']): void;
|
||||||
50
dist/src/middlewares/queryHandler/metadataHandler.js
vendored
Normal file
50
dist/src/middlewares/queryHandler/metadataHandler.js
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { generateEtag, getQueryParameterArray, getValueOrFirstEntry, } from '../../lib/utils.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param cache
|
||||||
|
* @param metadataParameter
|
||||||
|
*/
|
||||||
|
export default function metadataHandler(req, res, cache, metadataParameter) {
|
||||||
|
const metadataToMatch = getQueryParameterArray(req.query[metadataParameter]);
|
||||||
|
const searchTermsToMatch = [
|
||||||
|
...getQueryParameterArray(req.query.search),
|
||||||
|
...getQueryParameterArray(req.query.s),
|
||||||
|
];
|
||||||
|
const metadata = cache.get('metadata') ?? {};
|
||||||
|
const wantsJson = req.is('json') ||
|
||||||
|
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';
|
||||||
|
if (wantsJson)
|
||||||
|
res.set('content-type', 'application/json');
|
||||||
|
else
|
||||||
|
res.set('content-type', 'text/plain');
|
||||||
|
const matchedMetadata = Object.keys(metadata)
|
||||||
|
.filter((key) => (!metadataToMatch.length ||
|
||||||
|
(metadataToMatch.length === 1 && metadataToMatch[0] === '') ||
|
||||||
|
metadataToMatch.includes(key)) &&
|
||||||
|
(!searchTermsToMatch.length ||
|
||||||
|
searchTermsToMatch.some((term) => key.includes(term) || Array.isArray(metadata[key])
|
||||||
|
? metadata[key].some((val) => val.includes(term))
|
||||||
|
: metadata[key].includes(term))))
|
||||||
|
.reduce((acc, key) => {
|
||||||
|
const value = metadata[key];
|
||||||
|
acc[key] = Array.isArray(value)
|
||||||
|
? value.filter((value) => !searchTermsToMatch.length ||
|
||||||
|
searchTermsToMatch.some((term) => key.includes(term) || value.includes(term)))
|
||||||
|
: value;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const result = wantsJson
|
||||||
|
? JSON.stringify(matchedMetadata)
|
||||||
|
: Object.keys(matchedMetadata)
|
||||||
|
.map((key) => {
|
||||||
|
const value = matchedMetadata[key];
|
||||||
|
return Array.isArray(value)
|
||||||
|
? value.map((rowVal) => `${key}: ${rowVal}`).join('\n')
|
||||||
|
: `${key}: ${value}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
res.set('etag', generateEtag(result)).send(result);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=metadataHandler.js.map
|
||||||
1
dist/src/middlewares/queryHandler/metadataHandler.js.map
vendored
Normal file
1
dist/src/middlewares/queryHandler/metadataHandler.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"metadataHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/metadataHandler.ts"],"names":[],"mappings":"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"}
|
||||||
11
dist/src/middlewares/queryHandler/queryHandler.d.ts
vendored
Normal file
11
dist/src/middlewares/queryHandler/queryHandler.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import NodeCache from '@cacheable/node-cache';
|
||||||
|
import type { NextFunction, Request, Response } from 'express';
|
||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param cache
|
||||||
|
* @param verifyAuthRequest
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function queryHandler(config: TwtKprConfiguration, cache: NodeCache<unknown>, verifyAuthRequest: (r: Request) => Promise<boolean>): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||||
58
dist/src/middlewares/queryHandler/queryHandler.js
vendored
Normal file
58
dist/src/middlewares/queryHandler/queryHandler.js
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import Debug from 'debug';
|
||||||
|
import { __dirname } from '../../lib/constants.js';
|
||||||
|
import { generateEtag } from '../../lib/utils.js';
|
||||||
|
import renderApp from '../renderApp/index.js';
|
||||||
|
import followingHandler from './followingHandler.js';
|
||||||
|
import metadataHandler from './metadataHandler.js';
|
||||||
|
import twtHandler from './twtHandler.js';
|
||||||
|
const debug = Debug('twtkpr:queryHandler');
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param cache
|
||||||
|
* @param verifyAuthRequest
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function queryHandler(config, cache, verifyAuthRequest) {
|
||||||
|
const { mainRoute, queryParameters, uploadConfiguration } = config;
|
||||||
|
return async (req, res, next) => {
|
||||||
|
debug({ query: JSON.stringify(req.query) });
|
||||||
|
if (!Object.keys(req.query).length) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.query[queryParameters.app] !== undefined) {
|
||||||
|
const appContent = renderApp({ mainRoute, uploadConfiguration });
|
||||||
|
res.set('etag', generateEtag(appContent)).send(appContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.query[queryParameters.css] !== undefined) {
|
||||||
|
res.sendFile('styles.css', {
|
||||||
|
root: path.resolve(__dirname, 'client'),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.query[queryParameters.js] !== undefined) {
|
||||||
|
res.sendFile('script.js', {
|
||||||
|
root: path.resolve(__dirname, 'client'),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.query[queryParameters.following] !== undefined &&
|
||||||
|
cache.get('following')) {
|
||||||
|
return followingHandler(req, res, cache, queryParameters.following);
|
||||||
|
}
|
||||||
|
if (req.query[queryParameters.metadata] !== undefined &&
|
||||||
|
cache.get('metadata')) {
|
||||||
|
return metadataHandler(req, res, cache, queryParameters.metadata);
|
||||||
|
}
|
||||||
|
if ((req.query[queryParameters.twt] !== undefined ||
|
||||||
|
req.query[queryParameters.twts] !== undefined) &&
|
||||||
|
cache.get('twts')) {
|
||||||
|
return twtHandler(req, res, cache.get('twts'), queryParameters.twt, queryParameters.twts);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=queryHandler.js.map
|
||||||
1
dist/src/middlewares/queryHandler/queryHandler.js.map
vendored
Normal file
1
dist/src/middlewares/queryHandler/queryHandler.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"queryHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/queryHandler.ts"],"names":[],"mappings":"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"}
|
||||||
13
dist/src/middlewares/queryHandler/twtHandler.d.ts
vendored
Normal file
13
dist/src/middlewares/queryHandler/twtHandler.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import type { Twt } from 'twtxt-lib';
|
||||||
|
import { QueryParameters } from '../../types.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param twts
|
||||||
|
* @param twtParameter
|
||||||
|
* @param twtsParameter
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function twtHandler(req: Request, res: Response, twts: Twt[] | undefined, twtParameter: QueryParameters['twt'], twtsParameter: QueryParameters['twts']): void;
|
||||||
68
dist/src/middlewares/queryHandler/twtHandler.js
vendored
Normal file
68
dist/src/middlewares/queryHandler/twtHandler.js
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { generateEtag, getQueryParameterArray, getValueOrFirstEntry, } from '../../lib/utils.js';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import utc from 'dayjs/plugin/utc.js';
|
||||||
|
dayjs.extend(utc);
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param twts
|
||||||
|
* @param twtParameter
|
||||||
|
* @param twtsParameter
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function twtHandler(req, res, twts = [], twtParameter, twtsParameter) {
|
||||||
|
const twtsToMatch = getQueryParameterArray(req.query[twtsParameter]);
|
||||||
|
const showLastTwt = getQueryParameterArray(req.query[twtParameter]);
|
||||||
|
const hashesToMatch = getQueryParameterArray(req.query.hash);
|
||||||
|
const searchTermsToMatch = [
|
||||||
|
...getQueryParameterArray(req.query.search),
|
||||||
|
...getQueryParameterArray(req.query.s),
|
||||||
|
];
|
||||||
|
const createdDatesToMatch = getQueryParameterArray(req.query.created_date);
|
||||||
|
const createdUTCStartDatesToMatch = getQueryParameterArray(req.query.created_date_start).map((val) => dayjs.utc(val));
|
||||||
|
const createdUTCEndDatesToMatch = getQueryParameterArray(req.query.created_date_end).map((val) => dayjs.utc(val));
|
||||||
|
const wantsJson = req.is('json') ||
|
||||||
|
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';
|
||||||
|
if (wantsJson)
|
||||||
|
res.set('content-type', 'application/json');
|
||||||
|
else
|
||||||
|
res.set('content-type', 'text/plain');
|
||||||
|
if (showLastTwt.length === 1 && showLastTwt[0] === '') {
|
||||||
|
const lastTwt = twts.reduce((matched, curr) => matched?.createdUTC > curr.createdUTC ? matched : curr);
|
||||||
|
let result = 'No results';
|
||||||
|
if (lastTwt) {
|
||||||
|
result = wantsJson
|
||||||
|
? JSON.stringify(lastTwt)
|
||||||
|
: `${lastTwt?.created || ''}\t${lastTwt?.content || ''}\n`;
|
||||||
|
}
|
||||||
|
res.set('etag', generateEtag(result)).send(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const matchedTwts = twts.filter(({ content, created, createdUTC, hash }) => {
|
||||||
|
return ((!twtsToMatch.length ||
|
||||||
|
(twtsToMatch.length === 1 && twtsToMatch[0] === '') ||
|
||||||
|
twtsToMatch.includes(created) ||
|
||||||
|
(hash &&
|
||||||
|
(twtsToMatch.includes(hash) || twtsToMatch.includes(`#${hash}`)))) &&
|
||||||
|
(!hashesToMatch.length ||
|
||||||
|
(hash &&
|
||||||
|
(hashesToMatch.includes(hash) ||
|
||||||
|
hashesToMatch.includes(`#${hash}`)))) &&
|
||||||
|
(!createdDatesToMatch.length ||
|
||||||
|
createdDatesToMatch.some((date) => created.includes(date))) &&
|
||||||
|
(!createdUTCStartDatesToMatch.length ||
|
||||||
|
createdUTCStartDatesToMatch.some((date) => date.diff(createdUTC) < 0)) &&
|
||||||
|
(!createdUTCEndDatesToMatch.length ||
|
||||||
|
createdUTCEndDatesToMatch.some((date) => date.diff(createdUTC) > 0)) &&
|
||||||
|
(!searchTermsToMatch.length ||
|
||||||
|
searchTermsToMatch.some((term) => content.includes(term))));
|
||||||
|
});
|
||||||
|
const result = wantsJson
|
||||||
|
? JSON.stringify(matchedTwts)
|
||||||
|
: matchedTwts
|
||||||
|
.map(({ content, created }) => `${created}\t${content}`)
|
||||||
|
.join('\n');
|
||||||
|
res.set('etag', generateEtag(result)).send(result);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=twtHandler.js.map
|
||||||
1
dist/src/middlewares/queryHandler/twtHandler.js.map
vendored
Normal file
1
dist/src/middlewares/queryHandler/twtHandler.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"twtHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/twtHandler.ts"],"names":[],"mappings":"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"}
|
||||||
1
dist/src/middlewares/renderApp/index.d.ts
vendored
Normal file
1
dist/src/middlewares/renderApp/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "./renderApp.js";
|
||||||
2
dist/src/middlewares/renderApp/index.js
vendored
Normal file
2
dist/src/middlewares/renderApp/index.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./renderApp.js";
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
dist/src/middlewares/renderApp/index.js.map
vendored
Normal file
1
dist/src/middlewares/renderApp/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/renderApp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC"}
|
||||||
7
dist/src/middlewares/renderApp/renderApp.d.ts
vendored
Normal file
7
dist/src/middlewares/renderApp/renderApp.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { TwtKprConfiguration } from '../../types.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function renderApp({ mainRoute, uploadConfiguration, }: Pick<TwtKprConfiguration, 'mainRoute' | 'uploadConfiguration'>): string;
|
||||||
143
dist/src/middlewares/renderApp/renderApp.js
vendored
Normal file
143
dist/src/middlewares/renderApp/renderApp.js
vendored
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { version } from '../../packageInfo.js';
|
||||||
|
import renderUploadButton from './renderUploadButton.js';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function renderApp({ mainRoute, uploadConfiguration, }) {
|
||||||
|
return `<!doctype html>
|
||||||
|
<html class="no-js" lang="en" xmlns:fb="http://ogp.me/ns/fb#">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|
||||||
|
<!-- HTML head reference: https://github.com/joshbuchea/HEAD#recommended-minimum -->
|
||||||
|
<meta name="application-name" content="Application Name">
|
||||||
|
<meta name="theme-color" content="#6e6e81">
|
||||||
|
|
||||||
|
<title>TwtKpr</title>
|
||||||
|
<link rel="icon" href="favicon.ico" type="image/x-icon" />
|
||||||
|
<link rel="stylesheet" href="${mainRoute}?css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="toastContainer" id="toast-container"></div>
|
||||||
|
<div class="app" id="app">
|
||||||
|
<div class="menu" id="menu">
|
||||||
|
<div class="loginControls" id="loginControls">
|
||||||
|
<form method="POST" action="${mainRoute}" id="loginControls-form">
|
||||||
|
<input type="hidden" name="type" value="login" />
|
||||||
|
<div class="loginControls-row">
|
||||||
|
<div class="loginControls-fields">
|
||||||
|
<div class="loginControls-fields-row">
|
||||||
|
<label class="loginControls-label" for="loginControls-username">
|
||||||
|
Username
|
||||||
|
<input type="text" class="loginControls-input"
|
||||||
|
id="loginControls-username" name="username" value="" />
|
||||||
|
</label>
|
||||||
|
<label class="loginControls-label" for="loginControls-password">
|
||||||
|
Password
|
||||||
|
<input type="password" class="loginControls-input"
|
||||||
|
id="loginControls-password" name="password" value="" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="loginControls-toggle" for="loginControls-rememberToggle">
|
||||||
|
<input type="checkbox" class="loginControls-toggle-checkbox"
|
||||||
|
id="loginControls-rememberToggle" />
|
||||||
|
Stay Logged In
|
||||||
|
<span class="loginControls-toggle-track">
|
||||||
|
<span class="loginControls-toggle-indicator">
|
||||||
|
<span class="loginControls-toggle-checkMark">
|
||||||
|
<svg viewBox="0 0 24 24" id="loginControls-svg-check"
|
||||||
|
role="presentation" aria-hidden="true">
|
||||||
|
<path d="M9.86 18a1 1 0 01-.73-.32l-4.86-5.17a1.001 1.001 0
|
||||||
|
011.46-1.37l4.12 4.39 8.41-9.2a1 1 0 111.48 1.34l-9.14 10a1 1 0
|
||||||
|
01-.73.33h-.01z"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input class="loginControls-submitButton button" type="submit" value="Login" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="twtControls" id="twtControls">
|
||||||
|
<form class="twtControls-form" method="POST" action="${mainRoute}" id="twtForm">
|
||||||
|
<input type="hidden" name="type" value="twt" />
|
||||||
|
<div class="twtControls-formRow">
|
||||||
|
<div class="twtControls-appInfo appInfo">
|
||||||
|
TwtKpr v${version ?? 'Unknown'}
|
||||||
|
<div class="twtControls-appAuthor">
|
||||||
|
by Eric Woodward (<a href="https://itsericwoodward.com/twtxt.txt"
|
||||||
|
rel="noopener noreferrer" target="_blank">@itsericwoodward</a>)
|
||||||
|
</div>
|
||||||
|
<div class="twtControls-gitLink">
|
||||||
|
<a href="https://git.itsericwoodward.com/eric/express-twtkpr"
|
||||||
|
rel="noopener noreferrer">
|
||||||
|
https://git.itsericwoodward.com/eric/express-twtkpr
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${renderUploadButton(uploadConfiguration)}
|
||||||
|
<label class="twtControls-contentLabel" for="twtControlsContentInput">
|
||||||
|
<textarea class="twtControls-contentInput"
|
||||||
|
id="twtControlsContentInput" name="content"
|
||||||
|
placeholder="What do you want to say?"></textarea>
|
||||||
|
</label>
|
||||||
|
<div class="button hamburgerToggle">
|
||||||
|
<input type="checkbox" name="hamburgerToggleCheckbox"
|
||||||
|
id="hamburgerToggleCheckbox" aria-label="Toggle Navigation" />
|
||||||
|
<label class="hamburgerToggle-label" for="hamburgerToggleCheckbox">
|
||||||
|
<div class="hamburgerToggle-icon"></div>
|
||||||
|
</label>
|
||||||
|
<div class="popupMenu">
|
||||||
|
<div class="popupMenu-appInfo appInfo">
|
||||||
|
TwtKpr v${version ?? 'Unknown'}
|
||||||
|
</div>
|
||||||
|
${renderUploadButton(uploadConfiguration, 'small')}
|
||||||
|
<button class="twtControls-editButton button" id="twtControlsEditButton">
|
||||||
|
Edit File
|
||||||
|
</button>
|
||||||
|
<button class="twtControls-logoutButton button" id="twtControlsLogoutButton">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input class="twtControls-submitButton" disabled="disabled"
|
||||||
|
id="twtControlsSubmitButton" type="submit" value="Post\nMsg" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="fileContentsSection" id="fileContentsSection">
|
||||||
|
<pre class="fileContentsSection-fileBox" id="fileBox"></pre>
|
||||||
|
<form
|
||||||
|
action="/"
|
||||||
|
class="fileContentsSection-twtxtEditForm twtxtEditForm"
|
||||||
|
id="twtxtEditForm"
|
||||||
|
method="PUT"
|
||||||
|
>
|
||||||
|
<textarea class="twtxtEditForm-textarea" id="twtxtEditFormText"></textarea>
|
||||||
|
<div class="twtxtEditForm-controls">
|
||||||
|
<input class="button twtxtEditForm-button" type="reset" value="Cancel" />
|
||||||
|
<input class="button twtxtEditForm-button" type="submit" value="Update" />
|
||||||
|
<div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="${mainRoute}?js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=renderApp.js.map
|
||||||
1
dist/src/middlewares/renderApp/renderApp.js.map
vendored
Normal file
1
dist/src/middlewares/renderApp/renderApp.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"renderApp.js","sourceRoot":"","sources":["../../../../src/middlewares/renderApp/renderApp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,kBAAkB,MAAM,yBAAyB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EACjC,SAAS,EACT,mBAAmB,GAC6C;IAChE,OAAO;;;;;;;;;;;;;;mCAc2B,SAAS;;;;;;;;8CAQE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEAyCgB,SAAS;;;;sCAI1C,OAAO,IAAI,SAAS;;;;;;;;;;;;0BAYhC,kBAAkB,CAAC,mBAAmB,CAAC;;;;;;;;;;;;;;8CAcnB,OAAO,IAAI,SAAS;;kCAEhC,kBAAkB,CAAC,mBAAmB,EAAE,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCAiCjD,SAAS;;;;CAIzC,CAAC;AACF,CAAC"}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user