Move plugins to express-twtkpr-core-plugins package

Add plugin structure
Fix stale cache after posting
Update to v0.9.0
This commit is contained in:
2026-05-12 23:43:26 -04:00
parent 298f267742
commit 8658a14200
103 changed files with 1632 additions and 1996 deletions

View File

@@ -1,3 +1,5 @@
nodeLinker: node-modules nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.13.0.cjs npmMinimalAgeGate: 7d
yarnPath: .yarn/releases/yarn-4.14.1.cjs

View File

@@ -5,7 +5,8 @@ An [ExpressJS](https://expressjs.com/) router for serving, sharing, and updating
> [!WARNING] > [!WARNING]
> **STILL IN ALPHA**: Although this is a fully-functional plugin which is actively deployed to at least one site (my > **STILL IN ALPHA**: Although this is a fully-functional plugin which is actively deployed to at least one site (my
> own), it currently lacks documentation, examples, tests, installation flexibility, or polish. > own), it currently lacks documentation, examples, tests, installation flexibility, or polish (and still has a couple
> of bugs that need to be fixed).
> _USE AT YOUR OWN RISK_ > _USE AT YOUR OWN RISK_
### Features ### Features
@@ -17,10 +18,56 @@ An [ExpressJS](https://expressjs.com/) router for serving, sharing, and updating
## Installing ## Installing
Add package:
```sh ```sh
yarn add express-twtkpr yarn add express-twtkpr
``` ```
Generate hash:
```sh
yarn run get:hash
```
Create username and password:
```sh
yarn run set:user
```
## Using
Use it in an express application:
```
import express, { Request, Response } from "express";
import twtkpr from "express-twtkpr";
// import other middleware
const app = express();
// add other middleware
app.use(
"/",
twtkpr(
{
// config options
},
),
);
// add error handler
export default app;
```
Starting with v0.9.0, extra features (like files uploading) were moved to the [express-twtkpr-core-plugins](https://www.npmjs.com/package/express-twtkpr-core-plugins) package.
More to come! More to come!
--- ---

33
dist/package.json vendored
View File

@@ -1,6 +1,6 @@
{ {
"name": "express-twtkpr", "name": "express-twtkpr",
"version": "0.8.2", "version": "0.9.0",
"description": "An express library for hosting and maintaining a twtxt.txt file.", "description": "An express library for hosting and maintaining a twtxt.txt file.",
"license": "MIT", "license": "MIT",
"author": { "author": {
@@ -26,18 +26,16 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"start": "node --env-file=.env dist/index-app.js", "build": "tsc && cp -r src/client dist/src && cp -r src/plugins dist/src",
"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", "get:hash": "tsx --env-file=.env src/cli.ts get-hash",
"lint": "eslint --fix src test", "lint": "eslint --fix src",
"prepublishOnly": "yarn build", "prepublishOnly": "yarn build",
"set:user": "tsx --env-file=.env src/cli.ts set-user", "set:user": "tsx --env-file=.env src/cli.ts set-user",
"test": "vitest", "test": "vitest",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@cacheable/node-cache": "^2.0.2", "@cacheable/node-cache": "^3.0.0",
"@exodus/blakejs": "^1.1.1-exodus.0", "@exodus/blakejs": "^1.1.1-exodus.0",
"base32.js": "^0.1.0", "base32.js": "^0.1.0",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
@@ -45,37 +43,36 @@
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"debug": "^4.4.3", "debug": "^4.4.3",
"express": "^5.2.1", "express": "^5.2.1",
"express-rate-limit": "^8.3.1", "express-rate-limit": "^8.5.0",
"express-session": "^1.19.0", "express-session": "^1.19.0",
"express-slow-down": "^3.1.0", "express-slow-down": "^3.1.0",
"formidable": "^3.5.4",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"link": "^2.1.2", "link": "^2.1.2",
"session-file-store": "^1.5.0", "session-file-store": "^1.5.0",
"twtxt-lib": "^0.9.4", "twtxt-lib": "^0.10.1",
"uuid": "^13.0.0", "uuid": "^14.0.0",
"zod": "^4.3.6" "zod": "^4.4.3"
}, },
"devDependencies": { "devDependencies": {
"@types/cookie-parser": "^1.4.10", "@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/debug": "^4.1.13", "@types/debug": "^4.1.13",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/express-session": "^1.18.2", "@types/express-session": "^1.19.0",
"@types/formidable": "^3.5.0", "@types/formidable": "^3.5.1",
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.10",
"@types/morgan": "^1.9.10", "@types/morgan": "^1.9.10",
"@types/node": "^25.5.0", "@types/node": "^25.6.0",
"@types/supertest": "^7.2.0", "@types/supertest": "^7.2.0",
"eslint": "^10.0.3", "eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-security": "^4.0.0", "eslint-plugin-security": "^4.0.0",
"prettier": "^3.8.1", "prettier": "^3.8.3",
"supertest": "^7.2.2", "supertest": "^7.2.2",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vitest": "^4.1.0" "vitest": "^4.1.5"
}, },
"packageManager": "yarn@4.13.0" "packageManager": "yarn@4.14.1"
} }

View File

@@ -1,3 +1,4 @@
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT
const DEBUG_ON = true; const DEBUG_ON = true;
// served from same path as TWTXT file // served from same path as TWTXT file
@@ -9,13 +10,67 @@ const debug = (...vals) => {
if (DEBUG_ON) console.log(...vals); if (DEBUG_ON) console.log(...vals);
}; };
window.token = undefined;
window.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=/`;
};
window.showToast = (message, type = 'success') => {
const toast = document.createElement('div');
toast.classList.add('toast');
if (type === 'error') toast.classList.add('error');
toast.textContent = message;
document.getElementById('toast-container').appendChild(toast);
setTimeout(() => {
toast.style.animation = 'fadeOut 0.5s forwards';
setTimeout(() => toast.remove(), 500);
}, 3000);
};
window.refreshToken = async (hideToast = false) => {
const rememberToggleVal = !!localStorage.getItem(REMEMBER_LOGIN_STORAGE_KEY);
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) {
setCookie(ACCESS_TOKEN_COOKIE_KEY, token);
}
return;
}
// Handle refresh failure
if (!hideToast)
showToast('Unable to refresh token, please try again later.', 'error');
token = undefined;
document.body.classList.remove('js-authorized');
throw new Error('Failed to refresh token');
};
export default (async () => { export default (async () => {
/* DOM Elements */ /* DOM Elements */
const twtForm = document.getElementById('twtForm'), const twtForm = document.getElementById('twtForm'),
loginForm = document.getElementById('loginControls-form'), loginForm = document.getElementById('loginControls-form'),
fileBox = document.getElementById('fileBox'), fileBox = document.getElementById('fileBox'),
fileContentsSection = document.getElementById('fileContentsSection'), fileContentsSection = document.getElementById('fileContentsSection'),
toastContainer = document.getElementById('toast-container'),
twtControlsContentInput = document.getElementById( twtControlsContentInput = document.getElementById(
'twtControlsContentInput' 'twtControlsContentInput'
), ),
@@ -24,27 +79,12 @@ export default (async () => {
twtFileEditButton = document.getElementById('twtControlsEditButton'), twtFileEditButton = document.getElementById('twtControlsEditButton'),
menuCheckbox = document.getElementById('hamburgerToggleCheckbox'), menuCheckbox = document.getElementById('hamburgerToggleCheckbox'),
twtxtEditFormText = document.getElementById('twtxtEditFormText'), twtxtEditFormText = document.getElementById('twtxtEditFormText'),
uploadInputs = document.querySelectorAll('.twtControls-uploadInput'),
rememberToggle = document.getElementById('loginControls-rememberToggle'); rememberToggle = document.getElementById('loginControls-rememberToggle');
const lastModifiedDates = {}; const lastModifiedDates = {};
let isEditing = false, let isEditing = false,
cookie, cookie,
fileText, 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 = () => { const beginEditMode = () => {
isEditing = true; isEditing = true;
@@ -70,17 +110,6 @@ export default (async () => {
return null; 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) => { const loadTwtxtFile = async (filePath = TWTXT_FILE_URL) => {
debug('loadTwtxtFile start'); debug('loadTwtxtFile start');
let response; let response;
@@ -119,142 +148,8 @@ export default (async () => {
debug('loadTwtxtFile end'); 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 */ /* 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 = () => { const editClickHandler = () => {
if (isEditing) return; if (isEditing) return;
@@ -384,7 +279,8 @@ export default (async () => {
}; };
const rememberToggleHandler = (ev) => { const rememberToggleHandler = (ev) => {
if (ev.target.checked) { debug('toggle!', ev?.target?.checked);
if (ev?.target?.checked) {
localStorage.setItem(REMEMBER_LOGIN_STORAGE_KEY, 'true'); localStorage.setItem(REMEMBER_LOGIN_STORAGE_KEY, 'true');
return; return;
} }
@@ -392,6 +288,14 @@ export default (async () => {
localStorage.removeItem(REMEMBER_LOGIN_STORAGE_KEY); localStorage.removeItem(REMEMBER_LOGIN_STORAGE_KEY);
}; };
const twtContentInputHandler = () => {
console.log('input!');
const hasContent =
(twtControlsContentInput.value?.trim() ?? '').length !== 0;
if (twtSubmitButton && hasContent && !isEditing)
twtSubmitButton.removeAttribute('disabled');
};
const twtContentKeyupHandler = (ev) => { const twtContentKeyupHandler = (ev) => {
const hasContent = const hasContent =
(twtControlsContentInput.value?.trim() ?? '').length !== 0; (twtControlsContentInput.value?.trim() ?? '').length !== 0;
@@ -422,7 +326,7 @@ export default (async () => {
debug('twtForm submit data', { twtData }); debug('twtForm submit data', { twtData });
if (!twtContent) return; if (!twtContent) return;
twtData.set('content', twtContent.replaceAll('\n', '\u2028')); twtData.set('content', twtContent.replace(/\n/g, ' \u2028'));
const twtBody = new URLSearchParams(twtData); const twtBody = new URLSearchParams(twtData);
debug('twtForm submit body', { twtBody }); debug('twtForm submit body', { twtBody });
@@ -461,25 +365,15 @@ export default (async () => {
return false; return false;
}; };
const uploadChangeHandler = (ev) => {
uploadFiles(ev.target.files, ev.target.getAttribute('data-route'));
};
/* Attach Handlers to Listeners */ /* Attach Handlers to Listeners */
Array.from(uploadInputs).forEach((uploadInput) => {
uploadInput.addEventListener('change', uploadChangeHandler);
});
loginForm.addEventListener('submit', loginFormSubmitHandler); loginForm.addEventListener('submit', loginFormSubmitHandler);
twtForm.addEventListener('submit', twtFormSubmitHandler); twtForm.addEventListener('submit', twtFormSubmitHandler);
twtForm.addEventListener('keyup', twtContentKeyupHandler); twtForm.addEventListener('keyup', twtContentKeyupHandler);
twtControlsContentInput.addEventListener('drop', dropHandler); twtForm.addEventListener('input', twtContentInputHandler);
twtControlsContentInput.addEventListener('dragover', dragOverHandler);
twtLogoutButton.addEventListener('click', logoutHandler); twtLogoutButton.addEventListener('click', logoutHandler);
@@ -489,11 +383,19 @@ export default (async () => {
twtxtEditForm.addEventListener('submit', editSubmitHandler); twtxtEditForm.addEventListener('submit', editSubmitHandler);
window.addEventListener('dragover', dragOverWindowHandler);
window.addEventListener('drop', dropWindowHandler);
rememberToggle.addEventListener('change', rememberToggleHandler); rememberToggle.addEventListener('change', rememberToggleHandler);
rememberToggle.setAttribute(
'checked',
!!localStorage.getItem(REMEMBER_LOGIN_STORAGE_KEY)
);
menuCheckbox.addEventListener('click', (evt) => {
evt.stopPropagation();
});
window.addEventListener('click', () => {
menuCheckbox.checked = false;
});
/* Start App*/ /* Start App*/
@@ -504,3 +406,4 @@ export default (async () => {
debug('client loaded'); debug('client loaded');
})(); })();
// @license-end

View File

@@ -77,7 +77,7 @@
} }
/** /**
* Local styles * Begin TwtKpr Client CSS
*/ */
:root { :root {
/* #0a0a14 / Vulcan*/ /* #0a0a14 / Vulcan*/
@@ -483,33 +483,6 @@ input:disabled {
display: none; 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 { .twtControls-submitButton {
background-color: var(--bg-hl); background-color: var(--bg-hl);
border: 1px solid var(--link); border: 1px solid var(--link);
@@ -715,3 +688,5 @@ input:disabled {
opacity: 0; opacity: 0;
} }
} }
/** End TwtKpr Client CSS */

3
dist/src/expressPlugin.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
import { Router } from 'express';
import { TwtKprPluginConfigurator, TwtKprConfiguration } from './types.js';
export default function expressPlugin(initialConfig?: Partial<TwtKprConfiguration>, initialPlugins?: TwtKprPluginConfigurator[]): Router;

58
dist/src/expressPlugin.js vendored Normal file
View File

@@ -0,0 +1,58 @@
import cookieParser from 'cookie-parser';
import Debug from 'debug';
import express from 'express';
import authCheck from './middlewares/authCheckJWT.js';
import twtxtCache from './lib/twtxtCache.js';
import getConfiguration from './lib/getConfiguration.js';
import queryHandler from './middlewares/queryHandler/index.js';
import postHandler, { pluginPostHandler, } from './middlewares/postHandler/index.js';
import putHandler, { pluginPutHandler, } from './middlewares/putHandler/index.js';
// import emojiPlugin from './plugins/emojiButton/index.js';
// import uploadPlugin from './plugins/uploadButton/index.js';
// import postToMastodon from './plugins/postToMastodon/index.js';
export default function expressPlugin(initialConfig, initialPlugins = []) {
const debug = Debug('twtkpr:expressPlugin');
const router = express.Router();
const config = getConfiguration(initialConfig ?? {});
const { mainRoute, publicDirectory, twtxtFilename } = config;
const verifyAuthRequest = (req) => authCheck(req, config);
const preparedPlugins = initialPlugins
// .concat([emojiPlugin, uploadPlugin, postToMastodon])
.map((plugin) => plugin(config));
debug('initializing cache');
const { getFromCache, reloadCache } = twtxtCache({
publicDirectory,
twtxtFilename,
});
preparedPlugins.forEach((plugin) => {
if (plugin.useFirst)
router.use(plugin.useFirst);
});
debug('adding URL encoder');
router.use(express.urlencoded({ extended: true }));
debug('adding cookieParser');
router.use(cookieParser());
debug('adding queryRouter');
router.use(mainRoute, queryHandler(config, getFromCache, verifyAuthRequest, preparedPlugins));
debug('adding postHandler and putHandler');
router.use(mainRoute, postHandler(config, preparedPlugins, reloadCache), putHandler(config, preparedPlugins, reloadCache));
debug('adding postHandlers and putHandlers for plugins');
router.use(config.pluginRoute, pluginPostHandler(preparedPlugins, verifyAuthRequest, reloadCache), pluginPutHandler(preparedPlugins, verifyAuthRequest, reloadCache));
debug('adding use handlers for plugins');
preparedPlugins.forEach((plugin) => {
if (plugin.use)
router.use(plugin.use);
});
debug('adding static');
router.use(express.static(config.publicDirectory));
debug('adding default redirect');
router.get('/', (_, res) => {
res.redirect(mainRoute);
});
preparedPlugins.forEach((plugin) => {
if (plugin.useLast)
router.use(plugin.useLast);
});
return router;
}
//# sourceMappingURL=expressPlugin.js.map

1
dist/src/expressPlugin.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"expressPlugin.js","sourceRoot":"","sources":["../../src/expressPlugin.ts"],"names":[],"mappings":"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"}

4
dist/src/index.d.ts vendored
View File

@@ -1 +1,3 @@
export { default } from "./plugin.js"; export { default } from './expressPlugin.js';
export { combineStreams, getReadStream } from './lib/utils.js';
export type { MimeOptions, TwtKprConfiguration, TwtKprPluginConfiguration, } from './types.js';

3
dist/src/index.js vendored
View File

@@ -1,2 +1,3 @@
export { default } from "./plugin.js"; export { default } from './expressPlugin.js';
export { combineStreams, getReadStream } from './lib/utils.js';
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"} {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}

View File

@@ -1,7 +1,8 @@
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_TWTXT_FILENAME = "twtxt.txt";
export declare const DEFAULT_ROUTE = "/twtxt.txt"; export declare const DEFAULT_ROUTE = "/twtxt.txt";
export declare const DEFAULT_PRIVATE_DIRECTORY = ".data";
export declare const DEFAULT_PUBLIC_DIRECTORY = "public";
export declare const DEFAULT_PLUGIN_ROUTE = "/";
export declare const DEFAULT_POST_LIMITER_ACTIVE = true; export declare const DEFAULT_POST_LIMITER_ACTIVE = true;
export declare const DEFAULT_QUERY_PARAMETER_APP = "app"; export declare const DEFAULT_QUERY_PARAMETER_APP = "app";
export declare const DEFAULT_QUERY_PARAMETER_CSS = "css"; export declare const DEFAULT_QUERY_PARAMETER_CSS = "css";

View File

@@ -1,9 +1,10 @@
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; 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_TWTXT_FILENAME = 'twtxt.txt';
export const DEFAULT_ROUTE = `/${DEFAULT_TWTXT_FILENAME}`; export const DEFAULT_ROUTE = `/${DEFAULT_TWTXT_FILENAME}`;
export const DEFAULT_PRIVATE_DIRECTORY = '.data';
export const DEFAULT_PUBLIC_DIRECTORY = 'public';
export const DEFAULT_PLUGIN_ROUTE = '/';
export const DEFAULT_POST_LIMITER_ACTIVE = true; export const DEFAULT_POST_LIMITER_ACTIVE = true;
export const DEFAULT_QUERY_PARAMETER_APP = 'app'; export const DEFAULT_QUERY_PARAMETER_APP = 'app';
export const DEFAULT_QUERY_PARAMETER_CSS = 'css'; export const DEFAULT_QUERY_PARAMETER_CSS = 'css';

View File

@@ -1 +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"} {"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,sBAAsB,GAAG,WAAW,CAAC;AAClD,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,sBAAsB,EAAE,CAAC;AAC1D,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAC;AACjD,MAAM,CAAC,MAAM,wBAAwB,GAAG,QAAQ,CAAC;AACjD,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC,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"}

View File

@@ -4,6 +4,7 @@ export declare const env: {
TWTKPR_REFRESH_SECRET: string; TWTKPR_REFRESH_SECRET: string;
TWTKPR_ACCESS_SECRET: string; TWTKPR_ACCESS_SECRET: string;
TWTKPR_DEFAULT_ROUTE: string; TWTKPR_DEFAULT_ROUTE: string;
TWTKPR_PLUGIN_ROUTE: string;
TWTKPR_PRIVATE_DIRECTORY: string; TWTKPR_PRIVATE_DIRECTORY: string;
TWTKPR_PUBLIC_DIRECTORY: string; TWTKPR_PUBLIC_DIRECTORY: string;
TWTKPR_QUERY_PARAMETER_APP: string; TWTKPR_QUERY_PARAMETER_APP: string;

4
dist/src/lib/env.js vendored
View File

@@ -1,7 +1,7 @@
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { z } from 'zod/v4'; 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'; import { DEFAULT_PLUGIN_ROUTE, 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 The following keys are expected to exist in `process.env`, either as listed, or without the
`TWTKPR_` prefix `TWTKPR_` prefix
@@ -18,6 +18,7 @@ const envSchema = z.object({
TWTKPR_ACCESS_SECRET: z.string().default(''), TWTKPR_ACCESS_SECRET: z.string().default(''),
// vars with default values // vars with default values
TWTKPR_DEFAULT_ROUTE: z.string().default(DEFAULT_ROUTE), TWTKPR_DEFAULT_ROUTE: z.string().default(DEFAULT_ROUTE),
TWTKPR_PLUGIN_ROUTE: z.string().default(DEFAULT_PLUGIN_ROUTE),
TWTKPR_PRIVATE_DIRECTORY: z.string().default(DEFAULT_PRIVATE_DIRECTORY), TWTKPR_PRIVATE_DIRECTORY: z.string().default(DEFAULT_PRIVATE_DIRECTORY),
TWTKPR_PUBLIC_DIRECTORY: z.string().default(DEFAULT_PUBLIC_DIRECTORY), TWTKPR_PUBLIC_DIRECTORY: z.string().default(DEFAULT_PUBLIC_DIRECTORY),
TWTKPR_QUERY_PARAMETER_APP: z.string().default(DEFAULT_QUERY_PARAMETER_APP), TWTKPR_QUERY_PARAMETER_APP: z.string().default(DEFAULT_QUERY_PARAMETER_APP),
@@ -100,6 +101,7 @@ const parseEnv = () => {
NODE_ENV: process.env.TWTKPR_NODE_ENV || process.env.NODE_ENV, NODE_ENV: process.env.TWTKPR_NODE_ENV || process.env.NODE_ENV,
TWTKPR_ACCESS_SECRET: process.env.TWTKPR_ACCESS_SECRET || process.env.ACCESS_SECRET, 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_DEFAULT_ROUTE: process.env.TWTKPR_DEFAULT_ROUTE || process.env.DEFAULT_ROUTE,
TWTKPR_PLUGIN_ROUTE: process.env.TWTKPR_PLUGIN_ROUTE || process.env.TWTKPR_PLUGIN_ROUTE,
TWTKPR_PRIVATE_DIRECTORY: process.env.TWTKPR_PRIVATE_DIRECTORY || process.env.PRIVATE_DIRECTORY, 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_PUBLIC_DIRECTORY: process.env.TWTKPR_PUBLIC_DIRECTORY || process.env.PUBLIC_DIRECTORY,
TWTKPR_REFRESH_SECRET: process.env.TWTKPR_REFRESH_SECRET || process.env.REFRESH_SECRET, TWTKPR_REFRESH_SECRET: process.env.TWTKPR_REFRESH_SECRET || process.env.REFRESH_SECRET,

File diff suppressed because one or more lines are too long

View File

@@ -52,18 +52,23 @@ const getDestinationByMimeTypeConfiguration = (allowedMimeTypes) => {
* @returns * @returns
*/ */
export default function getConfiguration(initialConfiguration) { 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 { mainRoute = env.TWTKPR_DEFAULT_ROUTE, pluginRoute = env.TWTKPR_PLUGIN_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 { 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 { 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 { return {
// secrets cannot be provided through configuration file, must use ENV / .env // secrets cannot be provided through configuration file, must use ENV / .env
accessSecret: env.TWTKPR_ACCESS_SECRET, accessSecret: env.TWTKPR_ACCESS_SECRET,
refreshSecret: env.TWTKPR_REFRESH_SECRET, refreshSecret: env.TWTKPR_REFRESH_SECRET,
mainRoute, mainRoute,
pluginRoute,
privateDirectory, privateDirectory,
publicDirectory, publicDirectory,
twtxtFilename, twtxtFilename,
plugins: {
...(initialConfiguration?.plugins ?? {}),
},
postLimiterConfiguration: { postLimiterConfiguration: {
active: postLimiterActive, active: postLimiterActive,
...(otherPostLimiterProps ?? {}), ...(otherPostLimiterProps ?? {}),
@@ -79,25 +84,6 @@ export default function getConfiguration(initialConfiguration) {
twt, twt,
twts, 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 //# sourceMappingURL=getConfiguration.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"getConfiguration.js","sourceRoot":"","sources":["../../../src/lib/getConfiguration.ts"],"names":[],"mappings":"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"} {"version":3,"file":"getConfiguration.js","sourceRoot":"","sources":["../../../src/lib/getConfiguration.ts"],"names":[],"mappings":"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"}

View File

@@ -1,4 +1,3 @@
import { NodeCache } from '@cacheable/node-cache';
import { TwtKprConfiguration } from '../types.js'; import { TwtKprConfiguration } from '../types.js';
/** /**
* *
@@ -6,6 +5,6 @@ import { TwtKprConfiguration } from '../types.js';
* @returns * @returns
*/ */
export default function twtxtCache({ publicDirectory, twtxtFilename, }: Pick<TwtKprConfiguration, 'publicDirectory' | 'twtxtFilename'>): { export default function twtxtCache({ publicDirectory, twtxtFilename, }: Pick<TwtKprConfiguration, 'publicDirectory' | 'twtxtFilename'>): {
cache: NodeCache<unknown>; getFromCache: (key?: string) => Promise<unknown>;
reloadCache: () => Promise<void>; reloadCache: () => Promise<void>;
}; };

View File

@@ -11,8 +11,20 @@ import { parseTwtxt } from 'twtxt-lib';
export default function twtxtCache({ publicDirectory, twtxtFilename, }) { export default function twtxtCache({ publicDirectory, twtxtFilename, }) {
let isLoaded = false; let isLoaded = false;
const debug = Debug('twtkpr:twtxtCache'); const debug = Debug('twtkpr:twtxtCache');
const cache = new NodeCache(); const defaultCacheOptions = {
const reloadCache = async () => { stdTTL: 299,
checkperiod: 300,
};
const cache = new NodeCache(defaultCacheOptions);
const getFromCache = async (key = '') => {
debug(`checking cache for key: ${key}`);
if (!key)
return undefined;
const value = cache.get(key);
if (value)
return value;
debug('Not found, reloading keys');
cache.flushAll();
const fileText = await fsp.readFile(path.join(publicDirectory, twtxtFilename), 'utf8'); const fileText = await fsp.readFile(path.join(publicDirectory, twtxtFilename), 'utf8');
const parsedFile = parseTwtxt(fileText); const parsedFile = parseTwtxt(fileText);
Object.keys(parsedFile).forEach((key) => { Object.keys(parsedFile).forEach((key) => {
@@ -21,10 +33,13 @@ export default function twtxtCache({ publicDirectory, twtxtFilename, }) {
cache.set('source', fileText); cache.set('source', fileText);
debug(`cache ${isLoaded ? 're' : ''}loaded`); debug(`cache ${isLoaded ? 're' : ''}loaded`);
isLoaded = true; isLoaded = true;
return cache.get(key);
};
const reloadCache = async () => {
cache.flushAll();
}; };
reloadCache();
return { return {
cache, getFromCache,
reloadCache, reloadCache,
}; };
} }

View File

@@ -1 +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"} {"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,EAAoB,MAAM,uBAAuB,CAAC;AACpE,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,mBAAmB,GAAqB;QAC7C,MAAM,EAAE,GAAG;QACX,WAAW,EAAE,GAAG;KAChB,CAAC;IAEF,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAEjD,MAAM,YAAY,GAAG,KAAK,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE;QACvC,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAE3B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QAExB,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACnC,KAAK,CAAC,QAAQ,EAAE,CAAC;QAEjB,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;QAEhB,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,KAAK,IAAI,EAAE;QAC9B,KAAK,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO;QACN,YAAY;QACZ,WAAW;KACX,CAAC;AACH,CAAC"}

View File

@@ -1,3 +1,5 @@
import { PassThrough } from 'node:stream';
export declare function combineStreams(streams: any[]): PassThrough;
/** /**
* *
* @param userId * @param userId
@@ -25,6 +27,12 @@ export declare const generateRefreshToken: (userId: string, secret?: string, ext
* @returns * @returns
*/ */
export declare const getQueryParameterArray: (value?: unknown | unknown[]) => string[]; export declare const getQueryParameterArray: (value?: unknown | unknown[]) => string[];
/**
*
* @param pathToFile
* @returns
*/
export declare const getReadStream: (pathToFile: string) => import("node:fs").ReadStream;
/** /**
* *
* @param value * @param value

41
dist/src/lib/utils.js vendored
View File

@@ -1,7 +1,34 @@
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import { createReadStream } from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises'; import { readFile, writeFile } from 'node:fs/promises';
import { PassThrough } from 'node:stream';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
async function combineWithPassthrough(sources, destination) {
for (const stream of sources) {
await new Promise((resolve, reject) => {
let s = stream;
if (typeof stream === 'function') {
s = stream();
}
if (typeof s === 'string') {
destination.push(s);
destination.push(null);
resolve(true);
return;
}
s.pipe(destination, { end: false });
s.on('end', resolve);
s.on('error', reject);
});
}
destination.emit('end');
}
export function combineStreams(streams) {
const stream = new PassThrough();
combineWithPassthrough(streams, stream).catch((err) => stream.destroy(err));
return stream;
}
/** /**
* *
* @param userId * @param userId
@@ -37,6 +64,20 @@ export const generateRefreshToken = (userId, secret = '', extendRefresh = false)
export const getQueryParameterArray = (value = []) => Array.isArray(value) export const getQueryParameterArray = (value = []) => Array.isArray(value)
? value.map((val) => `${val}`.trim()) ? value.map((val) => `${val}`.trim())
: [`${value}`.trim()]; : [`${value}`.trim()];
/**
*
* @param pathToFile
* @returns
*/
export const getReadStream = (pathToFile) => {
const theStream = createReadStream(pathToFile);
theStream.on('error', (err) => {
console.error(err);
theStream.close();
theStream.push(null);
});
return theStream;
};
/** /**
* *
* @param value * @param value

View File

@@ -1 +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"} {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/lib/utils.ts"],"names":[],"mappings":"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"}

View File

@@ -1 +1,2 @@
export { default } from "./postHandler.js"; export { default } from './postHandler.js';
export { default as pluginPostHandler } from './pluginPostHandler.js';

View File

@@ -1,2 +1,3 @@
export { default } from "./postHandler.js"; export { default } from './postHandler.js';
export { default as pluginPostHandler } from './pluginPostHandler.js';
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC"} {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}

View File

@@ -1,5 +1,4 @@
import type { NextFunction, Request, Response } from 'express'; import type { NextFunction, Request, Response } from 'express';
import NodeCache from '@cacheable/node-cache';
/** /**
* *
* @param req * @param req
@@ -9,4 +8,4 @@ import NodeCache from '@cacheable/node-cache';
* @param reloadCache * @param reloadCache
* @returns * @returns
*/ */
export default function memoryCache(req: Request, res: Response, next: NextFunction, cache: NodeCache<unknown>, reloadCache: () => Promise<void>): Promise<void>; export default function memoryCache(req: Request, res: Response, next: NextFunction, reloadCache: () => Promise<void>): Promise<void>;

View File

@@ -9,13 +9,15 @@ const debug = Debug('twtkpr:memoryCache');
* @param reloadCache * @param reloadCache
* @returns * @returns
*/ */
export default async function memoryCache(req, res, next, cache, reloadCache) { export default async function memoryCache(req, res, next, reloadCache) {
if (cache.keys().length && !['DELETE', 'POST', 'PUT'].includes(req.method)) { debug(req.method);
if (!['DELETE', 'POST', 'PUT'].includes(req.method)) {
next(); next();
return; return;
} }
reloadCache() reloadCache()
.then(() => { .then(() => {
debug('Cache reloaded');
next(); next();
}) })
.catch((err) => { .catch((err) => {

View File

@@ -1 +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"} {"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,WAAgC;IAEhC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,IAAI,EAAE,CAAC;QACP,OAAO;IACR,CAAC;IAED,WAAW,EAAE;SACX,IAAI,CAAC,GAAG,EAAE;QACV,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACxB,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"}

View File

@@ -0,0 +1,7 @@
import { Request } from 'express';
import { TwtKprPluginConfiguration } from '../../types.js';
/**
*
* @param config * @returns
*/
export default function pluginPostHandler(plugins: TwtKprPluginConfiguration[] | undefined, verifyAuthRequest: (r: Request) => Promise<boolean>, reloadCache?: () => Promise<void>): import("express-serve-static-core").Router;

View File

@@ -0,0 +1,30 @@
import Debug from 'debug';
import express from 'express';
const debug = Debug('twtkpr:postHandler');
/**
*
* @param config * @returns
*/
export default function pluginPostHandler(plugins = [], verifyAuthRequest, reloadCache) {
const router = express.Router();
const pluginRoutes = [].concat(...Object.keys(plugins)
.filter((key) => plugins[key]
?.postRoutes?.length)
.map((key) => plugins[key]
.postRoutes));
pluginRoutes.forEach(({ handler, path, requiresAuth }) => {
debug(`adding POST plugin router for ${path}`);
router.post(path, async (req, res, next) => {
debug(`handling POST plugin route to ${path}`);
if (requiresAuth && !(await verifyAuthRequest(req))) {
debug('auth check failed');
next();
return;
}
handler(req, res, next);
reloadCache?.();
});
});
return router;
}
//# sourceMappingURL=pluginPostHandler.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"pluginPostHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/pluginPostHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,OAA4C,MAAM,SAAS,CAAC;AAGnE,MAAM,KAAK,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC;AAE1C;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACxC,UAAuC,EAAE,EACzC,iBAAmD,EACnD,WAAiC;IAEjC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,MAAM,YAAY,GAAI,EAA0B,CAAC,MAAM,CACtD,GAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;SACtB,MAAM,CACN,CAAC,GAAG,EAAE,EAAE,CACN,OAAO,CAAC,GAA2B,CAA+B;QAClE,EAAE,UAAU,EAAE,MAAM,CACtB;SACA,GAAG,CACH,CAAC,GAAG,EAAE,EAAE,CACN,OAAO,CAAC,GAA2B,CAA+B;SACjE,UAAU,CACa,CAC5B,CAAC;IAEF,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE;QACxD,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;QAE/C,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YAC1C,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;YAE/C,IAAI,YAAY,IAAI,CAAC,CAAC,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBACrD,KAAK,CAAC,mBAAmB,CAAC,CAAC;gBAC3B,IAAI,EAAE,CAAC;gBACP,OAAO;YACR,CAAC;YAED,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YACxB,WAAW,EAAE,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AACf,CAAC"}

View File

@@ -1,7 +1,7 @@
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
/** /**
* *
* @param config * @param config
* @returns * @returns
*/ */
export default function postHandler(config: TwtKprConfiguration): import("express-serve-static-core").Router; export default function postHandler(config: TwtKprConfiguration, plugins?: TwtKprPluginConfiguration[], reloadCache?: () => Promise<void>): import("express-serve-static-core").Router;

View File

@@ -13,7 +13,7 @@ const debug = Debug('twtkpr:postHandler');
* @param config * @param config
* @returns * @returns
*/ */
export default function postHandler(config) { export default function postHandler(config, plugins = [], reloadCache) {
const { postLimiterConfiguration } = config; const { postLimiterConfiguration } = config;
const { active: isLimiterActive, ...otherLimiterProps } = postLimiterConfiguration ?? {}; const { active: isLimiterActive, ...otherLimiterProps } = postLimiterConfiguration ?? {};
const postLimiter = isLimiterActive const postLimiter = isLimiterActive
@@ -49,8 +49,11 @@ export default function postHandler(config) {
return; return;
} }
debug('auth check succeeded'); debug('auth check succeeded');
if (type === 'twt' || content) if (type === 'twt' || content) {
return twt(req, res, config); twt(req, res, config, plugins);
reloadCache?.();
return;
}
if (type === 'editFile') if (type === 'editFile')
return editFile(req, res, config); return editFile(req, res, config);
next(); next();

View File

@@ -1 +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"} {"version":3,"file":"postHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/postHandler.ts"],"names":[],"mappings":"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"}

View File

@@ -11,32 +11,33 @@ const debug = Debug('twtkpr:refresh');
* @param res * @param res
*/ */
export default async function refresh(req, res, config) { export default async function refresh(req, res, config) {
const send401 = (message) => { const sendError = (message, code = 401) => {
debug(message); debug(message);
res res
.clearCookie('accessToken') .clearCookie('accessToken')
.clearCookie('refreshToken') .clearCookie('refreshToken')
.status(401) .status(code)
.send(message ?? 'Unauthorized'); .send(message ?? 'Unauthorized');
return; return;
}; };
try { try {
const tokens = await refreshTokensDB(config.privateDirectory); const tokens = await refreshTokensDB(config.privateDirectory);
const oldToken = req.cookies.refreshToken; const oldToken = req.cookies.refreshToken;
debug(oldToken); debug(`Using old token: ${oldToken}`);
if (!oldToken) if (!oldToken)
return send401('Unauthorized'); return sendError('Unauthorized');
let decoded = { id: '' }; let decoded = { id: '' };
try { try {
decoded = jwt.verify(oldToken, config.refreshSecret); decoded = jwt.verify(oldToken, config.refreshSecret);
debug({ decoded }); debug('Decoded token: ', { decoded });
} }
catch (err) { catch (err) {
return send401('Refresh token invalid'); debug('Error decoding refresh token:', err);
return sendError('Refresh token invalid', 403);
} }
const username = req.username ?? decoded.id; const username = req.username ?? decoded.id;
if (!username) if (!username)
return send401('Missing username'); return sendError('Missing username');
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Math.floor(Date.now() / 1000);
// cleanup tokens on load // cleanup tokens on load
const validTokens = (tokens.get(decoded.id) ?? []).filter((token) => { const validTokens = (tokens.get(decoded.id) ?? []).filter((token) => {
@@ -46,11 +47,11 @@ export default async function refresh(req, res, config) {
// If token is invalid or not the latest one // If token is invalid or not the latest one
if (!validTokens.includes(oldToken)) { if (!validTokens.includes(oldToken)) {
debug('token missing from list'); debug('token missing from list');
return send401('Invalid refresh token'); return sendError('Invalid refresh token');
} }
debug('generating new tokens'); debug('generating new tokens');
const newAccessToken = generateAccessToken(req.username || decoded.id, config.accessSecret); const newAccessToken = generateAccessToken(req.username || decoded.id, config.accessSecret);
const newRefreshToken = generateRefreshToken(req.username || decoded.id, config.refreshSecret); const newRefreshToken = generateRefreshToken(req.username || decoded.id, config.refreshSecret, !!req.query.rememberToggle);
debug('updating token list'); debug('updating token list');
tokens.set(req.username || decoded.id, validTokens tokens.set(req.username || decoded.id, validTokens
.filter((token) => token !== oldToken) .filter((token) => token !== oldToken)

View File

@@ -1 +1 @@
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/refresh.ts"],"names":[],"mappings":"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"} {"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/refresh.ts"],"names":[],"mappings":"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"}

View File

@@ -1,9 +1,9 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
/** /**
* Creates a new twt, appending it to the bottom of the TWTXT file * Creates a new twt, appending it to the bottom of the TWTXT file
* *
* @param req * @param req
* @param res * @param res
*/ */
export default function twt(req: Request, res: Response, config: TwtKprConfiguration): void; export default function twt(req: Request, res: Response, config: TwtKprConfiguration, plugins?: TwtKprPluginConfiguration[]): void;

View File

@@ -1,21 +1,33 @@
import dayjs from 'dayjs';
import fs from 'node:fs'; import fs from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import dayjs from 'dayjs';
import Debug from 'debug';
const debug = Debug('twtkpr:twt');
/** /**
* Creates a new twt, appending it to the bottom of the TWTXT file * Creates a new twt, appending it to the bottom of the TWTXT file
* *
* @param req * @param req
* @param res * @param res
*/ */
export default function twt(req, res, config) { export default function twt(req, res, config, plugins = []) {
debug('Beginning twt add');
const { content } = req.body ?? {}; const { content } = req.body ?? {};
const date = dayjs().format(); const date = dayjs().format();
const twt = `${date}\t${content.trim()}\n`; const twt = `${date}\t${content.trim()}\n`;
debug(`Formatted twt: ${twt}`);
debug('Beginning stream...');
const stream = fs.createWriteStream(join(config.publicDirectory, config.twtxtFilename), { const stream = fs.createWriteStream(join(config.publicDirectory, config.twtxtFilename), {
flags: 'a', flags: 'a',
}); });
stream.write(twt); stream.write(twt);
stream.end(); stream.end();
debug('Streaming complete');
plugins.forEach(async (plugin) => {
if (!plugin.onAfterTwt)
return;
debug(`Handling plugin onAfterTwt function${plugin.name ? ' from ' + plugin.name : ''}`);
plugin.onAfterTwt(twt);
});
res.status(200).send(twt); res.status(200).send(twt);
} }
//# sourceMappingURL=twt.js.map //# sourceMappingURL=twt.js.map

View File

@@ -1 +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"} {"version":3,"file":"twt.js","sourceRoot":"","sources":["../../../../src/middlewares/postHandler/twt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC;AAElC;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,GAAG,CAC1B,GAAY,EACZ,GAAa,EACb,MAA2B,EAC3B,UAAuC,EAAE;IAEzC,KAAK,CAAC,mBAAmB,CAAC,CAAC;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,KAAK,CAAC,kBAAkB,GAAG,EAAE,CAAC,CAAC;IAE/B,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC7B,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,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAE5B,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QAChC,IAAI,CAAC,MAAM,CAAC,UAAU;YAAE,OAAO;QAE/B,KAAK,CACJ,sCACC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EACxC,EAAE,CACF,CAAC;QAEF,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC"}

View File

@@ -1 +1,2 @@
export { default } from "./putHandler.js"; export { default } from './putHandler.js';
export { default as pluginPutHandler } from './pluginPutHandler.js';

View File

@@ -1,2 +1,3 @@
export { default } from "./putHandler.js"; export { default } from './putHandler.js';
export { default as pluginPutHandler } from './pluginPutHandler.js';
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/putHandler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC"} {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/middlewares/putHandler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}

View File

@@ -0,0 +1,8 @@
import { Request } from 'express';
import { TwtKprPluginConfiguration } from '../../types.js';
/**
*
* @param plugins
* @returns
*/
export default function pluginPutHandler(plugins: TwtKprPluginConfiguration[] | undefined, verifyAuthRequest: (r: Request) => Promise<boolean>, reloadCache?: () => Promise<void>): import("express-serve-static-core").Router;

View File

@@ -0,0 +1,31 @@
import Debug from 'debug';
import express from 'express';
const debug = Debug('twtkpr:postHandler');
/**
*
* @param plugins
* @returns
*/
export default function pluginPutHandler(plugins = [], verifyAuthRequest, reloadCache) {
const router = express.Router();
const pluginRoutes = [].concat(...Object.keys(plugins)
.filter((key) => plugins[key]
?.putRoutes?.length)
.map((key) => plugins[key]
.putRoutes));
pluginRoutes.forEach(({ handler, path, requiresAuth }) => {
debug(`adding PUT plugin router for ${path}`);
router.put(path, async (req, res, next) => {
debug(`handling PUT plugin route to ${path}`);
if (requiresAuth && !(await verifyAuthRequest(req))) {
debug('auth check failed');
next();
return;
}
handler(req, res, next);
reloadCache?.();
});
});
return router;
}
//# sourceMappingURL=pluginPutHandler.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"pluginPutHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/putHandler/pluginPutHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,OAA4C,MAAM,SAAS,CAAC;AAGnE,MAAM,KAAK,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CACvC,UAAuC,EAAE,EACzC,iBAAmD,EACnD,WAAiC;IAEjC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,MAAM,YAAY,GAAI,EAA0B,CAAC,MAAM,CACtD,GAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;SACtB,MAAM,CACN,CAAC,GAAG,EAAE,EAAE,CACN,OAAO,CAAC,GAA2B,CAA+B;QAClE,EAAE,SAAS,EAAE,MAAM,CACrB;SACA,GAAG,CACH,CAAC,GAAG,EAAE,EAAE,CACN,OAAO,CAAC,GAA2B,CAA+B;SACjE,SAAS,CACc,CAC5B,CAAC;IAEF,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE;QACxD,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;QAE9C,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACzC,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;YAE9C,IAAI,YAAY,IAAI,CAAC,CAAC,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBACrD,KAAK,CAAC,mBAAmB,CAAC,CAAC;gBAC3B,IAAI,EAAE,CAAC;gBACP,OAAO;YACR,CAAC;YAED,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAExB,WAAW,EAAE,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AACf,CAAC"}

View File

@@ -1,7 +1,7 @@
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
/** /**
* *
* @param config * @param config
* @returns * @returns
*/ */
export default function putHandler(config: TwtKprConfiguration): import("express-serve-static-core").Router; export default function putHandler(config: TwtKprConfiguration, plugins?: TwtKprPluginConfiguration[], reloadCache?: () => Promise<void>): import("express-serve-static-core").Router;

View File

@@ -8,7 +8,7 @@ const debug = Debug('twtkpr:putHandler');
* @param config * @param config
* @returns * @returns
*/ */
export default function putHandler(config) { export default function putHandler(config, plugins = [], reloadCache) {
const router = express.Router(); const router = express.Router();
router.put('/', (req, res, next) => { router.put('/', (req, res, next) => {
debug('put', { path: req.path }); debug('put', { path: req.path });
@@ -19,7 +19,8 @@ export default function putHandler(config) {
return; return;
} }
debug('auth check succeeded'); debug('auth check succeeded');
return editFile(req, res, config); editFile(req, res, config);
reloadCache?.();
}); });
return router; return router;
} }

View File

@@ -1 +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"} {"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,CACjC,MAA2B,EAC3B,UAAuC,EAAE,EACzC,WAAiC;IAEjC,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,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAE3B,WAAW,EAAE,EAAE,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AACf,CAAC"}

View File

@@ -1,6 +1,6 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import type { Twttr } from 'twtxt-lib';
import { QueryParameters } from '../../types.js'; import { QueryParameters } from '../../types.js';
import NodeCache from '@cacheable/node-cache';
/** /**
* *
* @param req * @param req
@@ -8,4 +8,4 @@ import NodeCache from '@cacheable/node-cache';
* @param cache * @param cache
* @param followingParameter * @param followingParameter
*/ */
export default function followingHandler(req: Request, res: Response, cache: NodeCache<unknown>, followingParameter: QueryParameters['following']): void; export default function followingHandler(req: Request, res: Response, following: Twttr[], followingParameter: QueryParameters['following']): void;

View File

@@ -6,7 +6,7 @@ import { generateEtag, getQueryParameterArray, getValueOrFirstEntry, } from '../
* @param cache * @param cache
* @param followingParameter * @param followingParameter
*/ */
export default function followingHandler(req, res, cache, followingParameter) { export default function followingHandler(req, res, following, followingParameter) {
const followingsToMatch = getQueryParameterArray(req.query[followingParameter]); const followingsToMatch = getQueryParameterArray(req.query[followingParameter]);
const nicksToMatch = getQueryParameterArray(req.query.nick); const nicksToMatch = getQueryParameterArray(req.query.nick);
const urlsToMatch = getQueryParameterArray(req.query.url); const urlsToMatch = getQueryParameterArray(req.query.url);
@@ -20,7 +20,7 @@ export default function followingHandler(req, res, cache, followingParameter) {
res.set('content-type', 'application/json'); res.set('content-type', 'application/json');
else else
res.set('content-type', 'text/plain'); res.set('content-type', 'text/plain');
const matchedFollowing = cache.get('following').filter(({ nick, url }) => (!followingsToMatch.length || const matchedFollowing = following.filter(({ nick, url }) => (!followingsToMatch.length ||
(followingsToMatch.length === 1 && followingsToMatch[0] === '') || (followingsToMatch.length === 1 && followingsToMatch[0] === '') ||
followingsToMatch.includes(nick) || followingsToMatch.includes(nick) ||
followingsToMatch.includes(`@${nick}`) || followingsToMatch.includes(`@${nick}`) ||

View File

@@ -1 +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"} {"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,SAAkB,EAClB,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,GAAG,SAAS,CAAC,MAAM,CACxC,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"}

View File

@@ -1,5 +1,6 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import NodeCache from '@cacheable/node-cache'; import type { Metadata } from 'twtxt-lib';
import { NodeCache } from '@cacheable/node-cache';
import { QueryParameters } from '../../types.js'; import { QueryParameters } from '../../types.js';
export interface MetadataHandler { export interface MetadataHandler {
cache: NodeCache<unknown>; cache: NodeCache<unknown>;
@@ -14,4 +15,4 @@ export interface MetadataHandler {
* @param cache * @param cache
* @param metadataParameter * @param metadataParameter
*/ */
export default function metadataHandler(req: Request, res: Response, cache: NodeCache<unknown>, metadataParameter: QueryParameters['metadata']): void; export default function metadataHandler(req: Request, res: Response, metadata: Metadata, metadataParameter: QueryParameters['metadata']): void;

View File

@@ -6,13 +6,12 @@ import { generateEtag, getQueryParameterArray, getValueOrFirstEntry, } from '../
* @param cache * @param cache
* @param metadataParameter * @param metadataParameter
*/ */
export default function metadataHandler(req, res, cache, metadataParameter) { export default function metadataHandler(req, res, metadata, metadataParameter) {
const metadataToMatch = getQueryParameterArray(req.query[metadataParameter]); const metadataToMatch = getQueryParameterArray(req.query[metadataParameter]);
const searchTermsToMatch = [ const searchTermsToMatch = [
...getQueryParameterArray(req.query.search), ...getQueryParameterArray(req.query.search),
...getQueryParameterArray(req.query.s), ...getQueryParameterArray(req.query.s),
]; ];
const metadata = cache.get('metadata') ?? {};
const wantsJson = req.is('json') || const wantsJson = req.is('json') ||
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json'; getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';
if (wantsJson) if (wantsJson)

View File

@@ -1 +1 @@
{"version":3,"file":"metadataHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/metadataHandler.ts"],"names":[],"mappings":"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"} {"version":3,"file":"metadataHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/metadataHandler.ts"],"names":[],"mappings":"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"}

View File

@@ -1,6 +1,5 @@
import NodeCache from '@cacheable/node-cache';
import type { NextFunction, Request, Response } from 'express'; import type { NextFunction, Request, Response } from 'express';
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
/** /**
* *
* @param config * @param config
@@ -8,4 +7,4 @@ import { TwtKprConfiguration } from '../../types.js';
* @param verifyAuthRequest * @param verifyAuthRequest
* @returns * @returns
*/ */
export default function queryHandler(config: TwtKprConfiguration, cache: NodeCache<unknown>, verifyAuthRequest: (r: Request) => Promise<boolean>): (req: Request, res: Response, next: NextFunction) => Promise<void>; export default function queryHandler(config: TwtKprConfiguration, getFromCache: (key?: string) => Promise<unknown>, verifyAuthRequest: (r: Request) => Promise<boolean>, plugins?: TwtKprPluginConfiguration[]): (req: Request, res: Response, next: NextFunction) => Promise<void>;

View File

@@ -1,7 +1,8 @@
import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import Debug from 'debug'; import Debug from 'debug';
import { __dirname } from '../../lib/constants.js'; import { __dirname } from '../../lib/constants.js';
import { generateEtag } from '../../lib/utils.js'; import { combineStreams, generateEtag } from '../../lib/utils.js';
import renderApp from '../renderApp/index.js'; import renderApp from '../renderApp/index.js';
import followingHandler from './followingHandler.js'; import followingHandler from './followingHandler.js';
import metadataHandler from './metadataHandler.js'; import metadataHandler from './metadataHandler.js';
@@ -14,44 +15,89 @@ const debug = Debug('twtkpr:queryHandler');
* @param verifyAuthRequest * @param verifyAuthRequest
* @returns * @returns
*/ */
export default function queryHandler(config, cache, verifyAuthRequest) { export default function queryHandler(config, getFromCache, verifyAuthRequest, plugins = []) {
const { mainRoute, queryParameters, uploadConfiguration } = config; const { mainRoute, queryParameters } = config;
const getPluginStreams = (type) => plugins.filter((plugin) => !!plugin[type]).map((plugin) => plugin[type]);
return async (req, res, next) => { return async (req, res, next) => {
debug({ query: JSON.stringify(req.query) }); debug(`handling query`, JSON.stringify(req.query));
if (!Object.keys(req.query).length) { if (!Object.keys(req.query).length) {
next(); next();
return; return;
} }
if (req.query[queryParameters.app] !== undefined) { if (req.query[queryParameters.app] !== undefined) {
const appContent = renderApp({ mainRoute, uploadConfiguration }); debug('rendering app');
const appContent = renderApp({ mainRoute });
res.set('etag', generateEtag(appContent)).send(appContent); res.set('etag', generateEtag(appContent)).send(appContent);
return; return;
} }
if (req.query[queryParameters.css] !== undefined) { if (req.query[queryParameters.css] !== undefined) {
res.sendFile('styles.css', { debug('rendering css');
root: path.resolve(__dirname, 'client'), const mainStream = fs.createReadStream(path.join(__dirname, 'client', 'styles.css'));
const pluginStreams = getPluginStreams('clientCSS');
const streams = [mainStream, ...pluginStreams];
const combined = combineStreams(streams);
res.writeHead(200, {
'Content-Type': 'text/css',
});
combined.pipe(res);
combined.on('error', (error) => {
console.error('Error streaming file:', error);
res.end();
}); });
return; return;
} }
if (req.query[queryParameters.js] !== undefined) { if (req.query[queryParameters.js] !== undefined) {
res.sendFile('script.js', { debug('rendering js');
root: path.resolve(__dirname, 'client'), const mainStream = fs.createReadStream(path.join(__dirname, 'client', 'script.js'));
const pluginStreams = getPluginStreams('clientJS');
const streams = [mainStream, ...pluginStreams];
const combined = combineStreams(streams);
res.writeHead(200, {
'Content-Type': 'application/javascript',
});
combined.pipe(res);
combined.on('error', (error) => {
console.error('Error streaming file:', error);
res.end();
}); });
return; return;
} }
if (req.query[queryParameters.following] !== undefined && if (req.query[queryParameters.following] !== undefined &&
cache.get('following')) { (await getFromCache('following'))) {
return followingHandler(req, res, cache, queryParameters.following); debug('rendering following');
const following = await getFromCache('following');
return followingHandler(req, res, following, queryParameters.following);
} }
if (req.query[queryParameters.metadata] !== undefined && if (req.query[queryParameters.metadata] !== undefined &&
cache.get('metadata')) { (await getFromCache('metadata'))) {
return metadataHandler(req, res, cache, queryParameters.metadata); debug('rendering metadata');
const metadata = (await getFromCache('metadata'));
return metadataHandler(req, res, metadata, queryParameters.metadata);
} }
if ((req.query[queryParameters.twt] !== undefined || if ((req.query[queryParameters.twt] !== undefined ||
req.query[queryParameters.twts] !== undefined) && req.query[queryParameters.twts] !== undefined) &&
cache.get('twts')) { (await getFromCache('twts'))) {
return twtHandler(req, res, cache.get('twts'), queryParameters.twt, queryParameters.twts); debug('rendering twts');
const twts = await getFromCache('twts');
return twtHandler(req, res, twts, queryParameters.twt, queryParameters.twts);
} }
plugins.forEach((plugin) => {
if (!plugin?.queryRoutes?.length)
return;
plugin.queryRoutes.forEach(async (route) => {
// default to no auth
const { handler, queryParameter, requiresAuth = false } = route ?? {};
if (queryParameter && req.query[queryParameter] !== undefined) {
debug(`rendering plugin queryParameter ${queryParameter}`);
if (requiresAuth && !(await verifyAuthRequest(req))) {
debug('auth check failed');
next();
return;
}
return handler(req, res, next);
}
});
});
next(); next();
}; };
} }

View File

@@ -1 +1 @@
{"version":3,"file":"queryHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/queryHandler.ts"],"names":[],"mappings":"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"} {"version":3,"file":"queryHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/queryHandler.ts"],"names":[],"mappings":"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"}

View File

@@ -1 +1 @@
{"version":3,"file":"twtHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/twtHandler.ts"],"names":[],"mappings":"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"} {"version":3,"file":"twtHandler.js","sourceRoot":"","sources":["../../../../src/middlewares/queryHandler/twtHandler.ts"],"names":[],"mappings":"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"}

View File

@@ -4,4 +4,4 @@ import { TwtKprConfiguration } from '../../types.js';
* @param param0 * @param param0
* @returns * @returns
*/ */
export default function renderApp({ mainRoute, uploadConfiguration, }: Pick<TwtKprConfiguration, 'mainRoute' | 'uploadConfiguration'>): string; export default function renderApp({ mainRoute, }: Pick<TwtKprConfiguration, 'mainRoute'>): string;

View File

@@ -1,11 +1,10 @@
import { version } from '../../packageInfo.js'; import { version } from '../../packageInfo.js';
import renderUploadButton from './renderUploadButton.js';
/** /**
* *
* @param param0 * @param param0
* @returns * @returns
*/ */
export default function renderApp({ mainRoute, uploadConfiguration, }) { export default function renderApp({ mainRoute, }) {
return `<!doctype html> return `<!doctype html>
<html class="no-js" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <html class="no-js" lang="en" xmlns:fb="http://ogp.me/ns/fb#">
@@ -85,9 +84,8 @@ export default function renderApp({ mainRoute, uploadConfiguration, }) {
</a> </a>
</div> </div>
</div> </div>
${renderUploadButton(uploadConfiguration)}
<label class="twtControls-contentLabel" for="twtControlsContentInput"> <label class="twtControls-contentLabel" for="twtControlsContentInput">
<textarea class="twtControls-contentInput" <textarea class="twtControls-contentInput emoji_target"
id="twtControlsContentInput" name="content" id="twtControlsContentInput" name="content"
placeholder="What do you want to say?"></textarea> placeholder="What do you want to say?"></textarea>
</label> </label>
@@ -101,7 +99,6 @@ export default function renderApp({ mainRoute, uploadConfiguration, }) {
<div class="popupMenu-appInfo appInfo"> <div class="popupMenu-appInfo appInfo">
TwtKpr v${version ?? 'Unknown'} TwtKpr v${version ?? 'Unknown'}
</div> </div>
${renderUploadButton(uploadConfiguration, 'small')}
<button class="twtControls-editButton button" id="twtControlsEditButton"> <button class="twtControls-editButton button" id="twtControlsEditButton">
Edit File Edit File
</button> </button>

View File

@@ -1 +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"} {"version":3,"file":"renderApp.js","sourceRoot":"","sources":["../../../../src/middlewares/renderApp/renderApp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EACjC,SAAS,GAC+B;IACxC,OAAO;;;;;;;;;;;;;;mCAc2B,SAAS;;;;;;;;8CAQE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEAyCgB,SAAS;;;;sCAI1C,OAAO,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;8CAyBZ,OAAO,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCAkCjC,SAAS;;;;CAIzC,CAAC;AACF,CAAC"}

View File

@@ -1,8 +0,0 @@
import { TwtKprConfiguration } from '../../types.js';
/**
*
* @param uploadConfiguration
* @param variant
* @returns
*/
export default function renderUploadButton(uploadConfiguration: TwtKprConfiguration['uploadConfiguration'], variant?: 'normal' | 'small'): string;

View File

@@ -1,23 +0,0 @@
/**
*
* @param uploadConfiguration
* @param variant
* @returns
*/
export default function renderUploadButton(uploadConfiguration, variant = 'normal') {
const { active, allowedMimeTypes, route } = uploadConfiguration ?? {};
if (!active)
return '';
// determine accept from allowed mime types - may need to rebuild value based on fallback n getConfiguration, rather than at the end.
return `
<label class="button twtControls-uploadInputLabel twtControls-uploadInputLabel-${variant}"
for="twtControlsUploadInput-${variant}"
>
Upload${variant === 'normal' ? '<br />' : ' '}Files
<input accept="*" class="twtControls-uploadInput" data-route="${route}"
id="twtControlsUploadInput-${variant}"
multiple type="file" />
</label>
`;
}
//# sourceMappingURL=renderUploadButton.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"renderUploadButton.js","sourceRoot":"","sources":["../../../../src/middlewares/renderApp/renderUploadButton.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CACzC,mBAA+D,EAC/D,UAA8B,QAAQ;IAEtC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,GAAG,mBAAmB,IAAI,EAAE,CAAC;IAEtE,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,qIAAqI;IAErI,OAAO;yFACiF,OAAO;0CACtD,OAAO;;oBAE7B,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;4EACmB,KAAK;6CACpC,OAAO;;;CAGnD,CAAC;AACF,CAAC"}

View File

@@ -1,9 +0,0 @@
import type { NextFunction, Request, Response } from 'express';
import { TwtKprConfiguration } from '../types.js';
/**
*
* @param config
* @param verifyAuthRequest
* @returns
*/
export default function uploadHandler(config: TwtKprConfiguration, verifyAuthRequest: (r: Request) => Promise<boolean>): (req: Request, res: Response, next: NextFunction) => Promise<void>;

View File

@@ -1,129 +0,0 @@
import fsp from 'node:fs/promises';
import path from 'node:path';
import formidable from 'formidable';
import Debug from 'debug';
const debug = Debug('twtkpr:uploadHandler');
/**
*
* @param allowedMimeTypes
* @returns
*/
const getDestinationByMimeTypeConfiguration = (allowedMimeTypes) => {
const fallback = {
audio: 'audio',
image: 'images',
text: 'texts',
video: 'videos',
'*': 'files',
};
const mimeTypeArrayReducer = (acc, curr) => {
if (fallback[curr])
acc[curr] = fallback[curr];
else
acc[curr] = `${curr}s`;
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 config
* @param verifyAuthRequest
* @returns
*/
export default function uploadHandler(config, verifyAuthRequest) {
return async (req, res, next) => {
debug('checking auth');
if (!(await verifyAuthRequest(req))) {
debug('auth check failed');
res.status(401).send('Unauthorized');
return;
}
debug('auth check succeeded');
const { active, allowedMimeTypes, directory, route, ...otherProps } = config.uploadConfiguration;
if (!active ||
(Array.isArray(allowedMimeTypes) && !allowedMimeTypes.length)) {
next();
return;
}
debug('using configuration: ', {
uploadConfiguration: config.uploadConfiguration,
});
const form = formidable({
uploadDir: directory,
...otherProps,
});
form.parse(req, async (err, fields, files) => {
if (err) {
next(err);
return;
}
const uploadsDir = (route ?? '').replaceAll('/', '');
let hadFileError = false;
const processedFiles = [];
const destinationByMimeType = allowedMimeTypes;
debug(`processing ${(files?.files ?? []).length} files`);
for (const file of files?.files ?? []) {
const { filepath, hash, mimetype, newFilename, originalFilename } = file ?? {};
if (!(filepath && newFilename && originalFilename))
return;
console.log({ file });
let ext = path.extname(originalFilename).toLocaleLowerCase();
if (ext === '.jpeg')
ext = '.jpg';
const finalFilename = (hash && (mimetype?.includes('image') || mimetype?.includes('video'))
? `${hash}${ext}`
: originalFilename)
.replace(/\s+/g, '-')
.toLocaleLowerCase();
let destinationDir = '';
Object.keys(destinationByMimeType).forEach((mimeType) => {
if (file.mimetype?.split('/')?.[0] === mimeType.toLocaleLowerCase())
destinationDir =
destinationByMimeType[mimeType].directory ?? '';
});
if (destinationDir === '')
destinationDir =
destinationByMimeType['*'].directory ?? uploadsDir;
const finalPath = path.join(process.cwd(), 'public', destinationDir);
debug(`creating '${finalPath}'`);
fsp.mkdir(finalPath, { recursive: true });
debug(`copying '${filepath}' to '/${destinationDir}/${finalFilename}'`);
try {
await fsp.copyFile(filepath, path.join(finalPath, finalFilename));
debug(`cleaning up '${filepath}'`);
await fsp.rm(filepath);
debug(`processed successfully`);
processedFiles.push(`/${destinationDir}/${finalFilename}`);
}
catch (err) {
debug(`error!`);
hadFileError = true;
console.error(err);
}
}
debug('generating reply...');
if (hadFileError && processedFiles.length) {
res.type('text/plain').status(206).send(processedFiles.join('\n'));
return;
}
if (!processedFiles.length) {
res.type('text/plain').status(500).send('No files processed');
return;
}
res.type('text/plain').status(201).send(processedFiles.join('\n'));
});
};
}
//# sourceMappingURL=uploadHandler.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"uploadHandler.js","sourceRoot":"","sources":["../../../src/middlewares/uploadHandler.ts"],"names":[],"mappings":"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"}

View File

@@ -1,3 +0,0 @@
import { Router } from 'express';
import { TwtKprPluginConfiguration } from './types.js';
export default function plugin(initialConfig?: TwtKprPluginConfiguration): Router;

40
dist/src/plugin.js vendored
View File

@@ -1,40 +0,0 @@
import cookieParser from 'cookie-parser';
import Debug from 'debug';
import express from 'express';
import authCheck from './middlewares/authCheckJWT.js';
import twtxtCache from './lib/twtxtCache.js';
import getConfiguration from './lib/getConfiguration.js';
import queryHandler from './middlewares/queryHandler/index.js';
import uploadHandler from './middlewares/uploadHandler.js';
import postHandler from './middlewares/postHandler/index.js';
import memoryCache from './middlewares/postHandler/memoryCache.js';
import putHandler from './middlewares/putHandler/index.js';
export default function plugin(initialConfig) {
const debug = Debug('twtkpr:plugin');
const router = express.Router();
const config = getConfiguration(initialConfig ?? {});
const { publicDirectory, twtxtFilename } = config;
const verifyAuthRequest = (req) => authCheck(req, config);
debug('initializing cache');
const { cache, reloadCache } = twtxtCache({ publicDirectory, twtxtFilename });
debug('adding URL encoder');
router.use(express.urlencoded({ extended: true }));
debug('adding cookieParser');
router.use(cookieParser());
debug('adding queryRouter');
router.use(config.mainRoute, queryHandler(config, cache, verifyAuthRequest));
debug(`adding uploadHandler at /${config.uploadConfiguration.route}`);
router.post(`/${config.uploadConfiguration.route}`, uploadHandler(config, verifyAuthRequest));
debug('adding postHandler and putHandler');
router.use(config.mainRoute, postHandler(config), putHandler(config));
debug('adding static');
router.use(express.static(config.publicDirectory));
debug('adding default redirect');
router.get('/', (_, res) => {
res.redirect(config.mainRoute);
});
debug('adding memoryCache');
router.use((req, res, next) => memoryCache(req, res, next, cache, reloadCache));
return router;
}
//# sourceMappingURL=plugin.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,OAA4B,MAAM,SAAS,CAAC;AAGnD,OAAO,SAAS,MAAM,+BAA+B,CAAC;AACtD,OAAO,UAAU,MAAM,qBAAqB,CAAC;AAC7C,OAAO,gBAAgB,MAAM,2BAA2B,CAAC;AACzD,OAAO,YAAY,MAAM,qCAAqC,CAAC;AAC/D,OAAO,aAAa,MAAM,gCAAgC,CAAC;AAC3D,OAAO,WAAW,MAAM,oCAAoC,CAAC;AAC7D,OAAO,WAAW,MAAM,0CAA0C,CAAC;AACnE,OAAO,UAAU,MAAM,mCAAmC,CAAC;AAE3D,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,aAAyC;IACvE,MAAM,KAAK,GAAG,KAAK,CAAC,eAAe,CAAC,CAAC;IAErC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,MAAM,MAAM,GAAG,gBAAgB,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IAElD,MAAM,iBAAiB,GAAG,CAAC,GAAY,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAEnE,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC5B,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,UAAU,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,CAAC;IAE9E,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEnD,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC7B,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;IAE3B,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAE7E,KAAK,CAAC,4BAA4B,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC;IACtE,MAAM,CAAC,IAAI,CACV,IAAI,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,EACtC,aAAa,CAAC,MAAM,EAAE,iBAAiB,CAAC,CACxC,CAAC;IAEF,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC3C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAEtE,KAAK,CAAC,eAAe,CAAC,CAAC;IACvB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC;IAEnD,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;QAC1B,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAC7B,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAC/C,CAAC;IAEF,OAAO,MAAgB,CAAC;AACzB,CAAC"}

42
dist/src/types.d.ts vendored
View File

@@ -1,20 +1,9 @@
import { RequestHandler } from 'express';
import { Options } from 'express-rate-limit'; import { Options } from 'express-rate-limit';
import formidable from 'formidable';
export interface MimeOptions { export interface MimeOptions {
directory?: string; directory?: string;
rename?: boolean; rename?: boolean;
} }
export interface MimeImageOptions extends MimeOptions {
compression?: string;
maxHeight?: string;
maxWidth?: string;
}
export interface UploadConfiguration extends Partial<Omit<formidable.Options, 'uploadDir'>> {
active: boolean;
directory: string;
allowedMimeTypes: string | string[] | Record<string, MimeOptions>;
route: string;
}
export interface QueryParameters { export interface QueryParameters {
app: string; app: string;
css: string; css: string;
@@ -31,16 +20,37 @@ export interface PostLimiterConfiguration extends Partial<Options> {
export interface TwtKprConfiguration { export interface TwtKprConfiguration {
accessSecret: string; accessSecret: string;
mainRoute: string; mainRoute: string;
pluginRoute: string;
plugins?: Record<string, Record<string, any>>;
postLimiterConfiguration?: PostLimiterConfiguration;
privateDirectory: string; privateDirectory: string;
publicDirectory: string; publicDirectory: string;
queryParameters: QueryParameters;
refreshSecret: string; refreshSecret: string;
twtxtFilename: string; twtxtFilename: string;
postLimiterConfiguration?: PostLimiterConfiguration; }
queryParameters: QueryParameters; export interface TwtKprPluginQueryRoute extends Omit<TwtKprPluginRoute, 'path'> {
uploadConfiguration: UploadConfiguration; queryParameter: string;
}
export interface TwtKprPluginRoute {
path: string;
handler: RequestHandler;
requiresAuth?: boolean;
}
export type TwtKprPluginConfigurator = (config: TwtKprConfiguration) => TwtKprPluginConfiguration;
export interface TwtKprPluginConfiguration {
clientCSS?: string | NodeJS.ReadableStream | (() => string | NodeJS.ReadableStream);
clientJS?: string | NodeJS.ReadableStream | (() => string | NodeJS.ReadableStream);
name?: string;
onAfterTwt?: (twt: string) => void;
postRoutes?: TwtKprPluginRoute[];
putRoutes?: TwtKprPluginRoute[];
queryRoutes?: TwtKprPluginQueryRoute[];
use?: RequestHandler;
useFirst?: RequestHandler;
useLast?: RequestHandler;
} }
export interface TwtKprPluginConfiguration extends Omit<Partial<TwtKprConfiguration>, 'postLimiterConfiguration' | 'queryParameters' | 'uploadConfiguration'> { export interface TwtKprPluginConfiguration extends Omit<Partial<TwtKprConfiguration>, 'postLimiterConfiguration' | 'queryParameters' | 'uploadConfiguration'> {
postLimiterConfiguration?: Partial<PostLimiterConfiguration>; postLimiterConfiguration?: Partial<PostLimiterConfiguration>;
queryParameters?: Partial<QueryParameters>; queryParameters?: Partial<QueryParameters>;
uploadConfiguration?: Partial<UploadConfiguration>;
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "express-twtkpr", "name": "express-twtkpr",
"version": "0.8.2", "version": "0.9.0",
"description": "An express library for hosting and maintaining a twtxt.txt file.", "description": "An express library for hosting and maintaining a twtxt.txt file.",
"license": "MIT", "license": "MIT",
"author": { "author": {
@@ -26,18 +26,16 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"start": "node --env-file=.env dist/index-app.js", "build": "tsc && cp -r src/client dist/src && cp -r src/plugins dist/src",
"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", "get:hash": "tsx --env-file=.env src/cli.ts get-hash",
"lint": "eslint --fix src test", "lint": "eslint --fix src",
"prepublishOnly": "yarn build", "prepublishOnly": "yarn build",
"set:user": "tsx --env-file=.env src/cli.ts set-user", "set:user": "tsx --env-file=.env src/cli.ts set-user",
"test": "vitest", "test": "vitest",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@cacheable/node-cache": "^2.0.2", "@cacheable/node-cache": "^3.0.0",
"@exodus/blakejs": "^1.1.1-exodus.0", "@exodus/blakejs": "^1.1.1-exodus.0",
"base32.js": "^0.1.0", "base32.js": "^0.1.0",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
@@ -45,37 +43,36 @@
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"debug": "^4.4.3", "debug": "^4.4.3",
"express": "^5.2.1", "express": "^5.2.1",
"express-rate-limit": "^8.3.1", "express-rate-limit": "^8.5.0",
"express-session": "^1.19.0", "express-session": "^1.19.0",
"express-slow-down": "^3.1.0", "express-slow-down": "^3.1.0",
"formidable": "^3.5.4",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"link": "^2.1.2", "link": "^2.1.2",
"session-file-store": "^1.5.0", "session-file-store": "^1.5.0",
"twtxt-lib": "^0.9.4", "twtxt-lib": "^0.10.1",
"uuid": "^13.0.0", "uuid": "^14.0.0",
"zod": "^4.3.6" "zod": "^4.4.3"
}, },
"devDependencies": { "devDependencies": {
"@types/cookie-parser": "^1.4.10", "@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/debug": "^4.1.13", "@types/debug": "^4.1.13",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/express-session": "^1.18.2", "@types/express-session": "^1.19.0",
"@types/formidable": "^3.5.0", "@types/formidable": "^3.5.1",
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.10",
"@types/morgan": "^1.9.10", "@types/morgan": "^1.9.10",
"@types/node": "^25.5.0", "@types/node": "^25.6.0",
"@types/supertest": "^7.2.0", "@types/supertest": "^7.2.0",
"eslint": "^10.0.3", "eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-security": "^4.0.0", "eslint-plugin-security": "^4.0.0",
"prettier": "^3.8.1", "prettier": "^3.8.3",
"supertest": "^7.2.2", "supertest": "^7.2.2",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vitest": "^4.1.0" "vitest": "^4.1.5"
}, },
"packageManager": "yarn@4.13.0" "packageManager": "yarn@4.14.1"
} }

View File

@@ -1,3 +1,4 @@
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT
const DEBUG_ON = true; const DEBUG_ON = true;
// served from same path as TWTXT file // served from same path as TWTXT file
@@ -9,13 +10,67 @@ const debug = (...vals) => {
if (DEBUG_ON) console.log(...vals); if (DEBUG_ON) console.log(...vals);
}; };
window.token = undefined;
window.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=/`;
};
window.showToast = (message, type = 'success') => {
const toast = document.createElement('div');
toast.classList.add('toast');
if (type === 'error') toast.classList.add('error');
toast.textContent = message;
document.getElementById('toast-container').appendChild(toast);
setTimeout(() => {
toast.style.animation = 'fadeOut 0.5s forwards';
setTimeout(() => toast.remove(), 500);
}, 3000);
};
window.refreshToken = async (hideToast = false) => {
const rememberToggleVal = !!localStorage.getItem(REMEMBER_LOGIN_STORAGE_KEY);
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) {
setCookie(ACCESS_TOKEN_COOKIE_KEY, token);
}
return;
}
// Handle refresh failure
if (!hideToast)
showToast('Unable to refresh token, please try again later.', 'error');
token = undefined;
document.body.classList.remove('js-authorized');
throw new Error('Failed to refresh token');
};
export default (async () => { export default (async () => {
/* DOM Elements */ /* DOM Elements */
const twtForm = document.getElementById('twtForm'), const twtForm = document.getElementById('twtForm'),
loginForm = document.getElementById('loginControls-form'), loginForm = document.getElementById('loginControls-form'),
fileBox = document.getElementById('fileBox'), fileBox = document.getElementById('fileBox'),
fileContentsSection = document.getElementById('fileContentsSection'), fileContentsSection = document.getElementById('fileContentsSection'),
toastContainer = document.getElementById('toast-container'),
twtControlsContentInput = document.getElementById( twtControlsContentInput = document.getElementById(
'twtControlsContentInput' 'twtControlsContentInput'
), ),
@@ -24,27 +79,12 @@ export default (async () => {
twtFileEditButton = document.getElementById('twtControlsEditButton'), twtFileEditButton = document.getElementById('twtControlsEditButton'),
menuCheckbox = document.getElementById('hamburgerToggleCheckbox'), menuCheckbox = document.getElementById('hamburgerToggleCheckbox'),
twtxtEditFormText = document.getElementById('twtxtEditFormText'), twtxtEditFormText = document.getElementById('twtxtEditFormText'),
uploadInputs = document.querySelectorAll('.twtControls-uploadInput'),
rememberToggle = document.getElementById('loginControls-rememberToggle'); rememberToggle = document.getElementById('loginControls-rememberToggle');
const lastModifiedDates = {}; const lastModifiedDates = {};
let isEditing = false, let isEditing = false,
cookie, cookie,
fileText, 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 = () => { const beginEditMode = () => {
isEditing = true; isEditing = true;
@@ -70,17 +110,6 @@ export default (async () => {
return null; 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) => { const loadTwtxtFile = async (filePath = TWTXT_FILE_URL) => {
debug('loadTwtxtFile start'); debug('loadTwtxtFile start');
let response; let response;
@@ -119,142 +148,8 @@ export default (async () => {
debug('loadTwtxtFile end'); 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 */ /* 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 = () => { const editClickHandler = () => {
if (isEditing) return; if (isEditing) return;
@@ -384,7 +279,8 @@ export default (async () => {
}; };
const rememberToggleHandler = (ev) => { const rememberToggleHandler = (ev) => {
if (ev.target.checked) { debug('toggle!', ev?.target?.checked);
if (ev?.target?.checked) {
localStorage.setItem(REMEMBER_LOGIN_STORAGE_KEY, 'true'); localStorage.setItem(REMEMBER_LOGIN_STORAGE_KEY, 'true');
return; return;
} }
@@ -392,6 +288,14 @@ export default (async () => {
localStorage.removeItem(REMEMBER_LOGIN_STORAGE_KEY); localStorage.removeItem(REMEMBER_LOGIN_STORAGE_KEY);
}; };
const twtContentInputHandler = () => {
console.log('input!');
const hasContent =
(twtControlsContentInput.value?.trim() ?? '').length !== 0;
if (twtSubmitButton && hasContent && !isEditing)
twtSubmitButton.removeAttribute('disabled');
};
const twtContentKeyupHandler = (ev) => { const twtContentKeyupHandler = (ev) => {
const hasContent = const hasContent =
(twtControlsContentInput.value?.trim() ?? '').length !== 0; (twtControlsContentInput.value?.trim() ?? '').length !== 0;
@@ -422,7 +326,7 @@ export default (async () => {
debug('twtForm submit data', { twtData }); debug('twtForm submit data', { twtData });
if (!twtContent) return; if (!twtContent) return;
twtData.set('content', twtContent.replaceAll('\n', '\u2028')); twtData.set('content', twtContent.replace(/\n/g, ' \u2028'));
const twtBody = new URLSearchParams(twtData); const twtBody = new URLSearchParams(twtData);
debug('twtForm submit body', { twtBody }); debug('twtForm submit body', { twtBody });
@@ -461,25 +365,15 @@ export default (async () => {
return false; return false;
}; };
const uploadChangeHandler = (ev) => {
uploadFiles(ev.target.files, ev.target.getAttribute('data-route'));
};
/* Attach Handlers to Listeners */ /* Attach Handlers to Listeners */
Array.from(uploadInputs).forEach((uploadInput) => {
uploadInput.addEventListener('change', uploadChangeHandler);
});
loginForm.addEventListener('submit', loginFormSubmitHandler); loginForm.addEventListener('submit', loginFormSubmitHandler);
twtForm.addEventListener('submit', twtFormSubmitHandler); twtForm.addEventListener('submit', twtFormSubmitHandler);
twtForm.addEventListener('keyup', twtContentKeyupHandler); twtForm.addEventListener('keyup', twtContentKeyupHandler);
twtControlsContentInput.addEventListener('drop', dropHandler); twtForm.addEventListener('input', twtContentInputHandler);
twtControlsContentInput.addEventListener('dragover', dragOverHandler);
twtLogoutButton.addEventListener('click', logoutHandler); twtLogoutButton.addEventListener('click', logoutHandler);
@@ -489,11 +383,19 @@ export default (async () => {
twtxtEditForm.addEventListener('submit', editSubmitHandler); twtxtEditForm.addEventListener('submit', editSubmitHandler);
window.addEventListener('dragover', dragOverWindowHandler);
window.addEventListener('drop', dropWindowHandler);
rememberToggle.addEventListener('change', rememberToggleHandler); rememberToggle.addEventListener('change', rememberToggleHandler);
rememberToggle.setAttribute(
'checked',
!!localStorage.getItem(REMEMBER_LOGIN_STORAGE_KEY)
);
menuCheckbox.addEventListener('click', (evt) => {
evt.stopPropagation();
});
window.addEventListener('click', () => {
menuCheckbox.checked = false;
});
/* Start App*/ /* Start App*/
@@ -504,3 +406,4 @@ export default (async () => {
debug('client loaded'); debug('client loaded');
})(); })();
// @license-end

View File

@@ -77,7 +77,7 @@
} }
/** /**
* Local styles * Begin TwtKpr Client CSS
*/ */
:root { :root {
/* #0a0a14 / Vulcan*/ /* #0a0a14 / Vulcan*/
@@ -483,33 +483,6 @@ input:disabled {
display: none; 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 { .twtControls-submitButton {
background-color: var(--bg-hl); background-color: var(--bg-hl);
border: 1px solid var(--link); border: 1px solid var(--link);
@@ -715,3 +688,5 @@ input:disabled {
opacity: 0; opacity: 0;
} }
} }
/** End TwtKpr Client CSS */

92
src/expressPlugin.ts Normal file
View File

@@ -0,0 +1,92 @@
import cookieParser from 'cookie-parser';
import Debug from 'debug';
import express, { Request, Router } from 'express';
import { TwtKprPluginConfigurator, TwtKprConfiguration } from './types.js';
import authCheck from './middlewares/authCheckJWT.js';
import twtxtCache from './lib/twtxtCache.js';
import getConfiguration from './lib/getConfiguration.js';
import queryHandler from './middlewares/queryHandler/index.js';
import postHandler, {
pluginPostHandler,
} from './middlewares/postHandler/index.js';
import putHandler, {
pluginPutHandler,
} from './middlewares/putHandler/index.js';
// import emojiPlugin from './plugins/emojiButton/index.js';
// import uploadPlugin from './plugins/uploadButton/index.js';
// import postToMastodon from './plugins/postToMastodon/index.js';
export default function expressPlugin(
initialConfig?: Partial<TwtKprConfiguration>,
initialPlugins: TwtKprPluginConfigurator[] = []
) {
const debug = Debug('twtkpr:expressPlugin');
const router = express.Router();
const config = getConfiguration(initialConfig ?? {});
const { mainRoute, publicDirectory, twtxtFilename } = config;
const verifyAuthRequest = (req: Request) => authCheck(req, config);
const preparedPlugins = initialPlugins
// .concat([emojiPlugin, uploadPlugin, postToMastodon])
.map((plugin) => plugin(config));
debug('initializing cache');
const { getFromCache, reloadCache } = twtxtCache({
publicDirectory,
twtxtFilename,
});
preparedPlugins.forEach((plugin) => {
if (plugin.useFirst) router.use(plugin.useFirst);
});
debug('adding URL encoder');
router.use(express.urlencoded({ extended: true }));
debug('adding cookieParser');
router.use(cookieParser());
debug('adding queryRouter');
router.use(
mainRoute,
queryHandler(config, getFromCache, verifyAuthRequest, preparedPlugins)
);
debug('adding postHandler and putHandler');
router.use(
mainRoute,
postHandler(config, preparedPlugins, reloadCache),
putHandler(config, preparedPlugins, reloadCache)
);
debug('adding postHandlers and putHandlers for plugins');
router.use(
config.pluginRoute,
pluginPostHandler(preparedPlugins, verifyAuthRequest, reloadCache),
pluginPutHandler(preparedPlugins, verifyAuthRequest, reloadCache)
);
debug('adding use handlers for plugins');
preparedPlugins.forEach((plugin) => {
if (plugin.use) router.use(plugin.use);
});
debug('adding static');
router.use(express.static(config.publicDirectory));
debug('adding default redirect');
router.get('/', (_, res) => {
res.redirect(mainRoute);
});
preparedPlugins.forEach((plugin) => {
if (plugin.useLast) router.use(plugin.useLast);
});
return router as Router;
}

View File

@@ -1 +1,8 @@
export { default } from "./plugin.js"; export { default } from './expressPlugin.js';
export { combineStreams, getReadStream } from './lib/utils.js';
export type {
MimeOptions,
TwtKprConfiguration,
TwtKprPluginConfiguration,
} from './types.js';

View File

@@ -1,10 +1,11 @@
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; 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_TWTXT_FILENAME = 'twtxt.txt';
export const DEFAULT_ROUTE = `/${DEFAULT_TWTXT_FILENAME}`; export const DEFAULT_ROUTE = `/${DEFAULT_TWTXT_FILENAME}`;
export const DEFAULT_PRIVATE_DIRECTORY = '.data';
export const DEFAULT_PUBLIC_DIRECTORY = 'public';
export const DEFAULT_PLUGIN_ROUTE = '/';
export const DEFAULT_POST_LIMITER_ACTIVE = true; export const DEFAULT_POST_LIMITER_ACTIVE = true;

View File

@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
import { z } from 'zod/v4'; import { z } from 'zod/v4';
import { import {
DEFAULT_PLUGIN_ROUTE,
DEFAULT_POST_LIMITER_ACTIVE, DEFAULT_POST_LIMITER_ACTIVE,
DEFAULT_PRIVATE_DIRECTORY, DEFAULT_PRIVATE_DIRECTORY,
DEFAULT_PUBLIC_DIRECTORY, DEFAULT_PUBLIC_DIRECTORY,
@@ -42,6 +43,7 @@ const envSchema = z.object({
// vars with default values // vars with default values
TWTKPR_DEFAULT_ROUTE: z.string().default(DEFAULT_ROUTE), TWTKPR_DEFAULT_ROUTE: z.string().default(DEFAULT_ROUTE),
TWTKPR_PLUGIN_ROUTE: z.string().default(DEFAULT_PLUGIN_ROUTE),
TWTKPR_PRIVATE_DIRECTORY: z.string().default(DEFAULT_PRIVATE_DIRECTORY), TWTKPR_PRIVATE_DIRECTORY: z.string().default(DEFAULT_PRIVATE_DIRECTORY),
TWTKPR_PUBLIC_DIRECTORY: z.string().default(DEFAULT_PUBLIC_DIRECTORY), TWTKPR_PUBLIC_DIRECTORY: z.string().default(DEFAULT_PUBLIC_DIRECTORY),
TWTKPR_QUERY_PARAMETER_APP: z.string().default(DEFAULT_QUERY_PARAMETER_APP), TWTKPR_QUERY_PARAMETER_APP: z.string().default(DEFAULT_QUERY_PARAMETER_APP),
@@ -139,6 +141,8 @@ const parseEnv = () => {
process.env.TWTKPR_ACCESS_SECRET || process.env.ACCESS_SECRET, process.env.TWTKPR_ACCESS_SECRET || process.env.ACCESS_SECRET,
TWTKPR_DEFAULT_ROUTE: TWTKPR_DEFAULT_ROUTE:
process.env.TWTKPR_DEFAULT_ROUTE || process.env.DEFAULT_ROUTE, process.env.TWTKPR_DEFAULT_ROUTE || process.env.DEFAULT_ROUTE,
TWTKPR_PLUGIN_ROUTE:
process.env.TWTKPR_PLUGIN_ROUTE || process.env.TWTKPR_PLUGIN_ROUTE,
TWTKPR_PRIVATE_DIRECTORY: TWTKPR_PRIVATE_DIRECTORY:
process.env.TWTKPR_PRIVATE_DIRECTORY || process.env.PRIVATE_DIRECTORY, process.env.TWTKPR_PRIVATE_DIRECTORY || process.env.PRIVATE_DIRECTORY,
TWTKPR_PUBLIC_DIRECTORY: TWTKPR_PUBLIC_DIRECTORY:

View File

@@ -72,12 +72,13 @@ export default function getConfiguration(
) { ) {
const { const {
mainRoute = env.TWTKPR_DEFAULT_ROUTE, mainRoute = env.TWTKPR_DEFAULT_ROUTE,
pluginRoute = env.TWTKPR_PLUGIN_ROUTE,
privateDirectory = env.TWTKPR_PRIVATE_DIRECTORY, privateDirectory = env.TWTKPR_PRIVATE_DIRECTORY,
publicDirectory = env.TWTKPR_PUBLIC_DIRECTORY, publicDirectory = env.TWTKPR_PUBLIC_DIRECTORY,
twtxtFilename = env.TWTKPR_TWTXT_FILENAME, twtxtFilename = env.TWTKPR_TWTXT_FILENAME,
postLimiterConfiguration, postLimiterConfiguration,
queryParameters, queryParameters,
uploadConfiguration, // uploadConfiguration,
} = initialConfiguration ?? {}; } = initialConfiguration ?? {};
const { const {
@@ -96,33 +97,18 @@ export default function getConfiguration(
twts = env.TWTKPR_QUERY_PARAMETER_TWTS, twts = env.TWTKPR_QUERY_PARAMETER_TWTS,
} = queryParameters ?? {}; } = 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 { return {
// secrets cannot be provided through configuration file, must use ENV / .env // secrets cannot be provided through configuration file, must use ENV / .env
accessSecret: env.TWTKPR_ACCESS_SECRET, accessSecret: env.TWTKPR_ACCESS_SECRET,
refreshSecret: env.TWTKPR_REFRESH_SECRET, refreshSecret: env.TWTKPR_REFRESH_SECRET,
mainRoute, mainRoute,
pluginRoute,
privateDirectory, privateDirectory,
publicDirectory, publicDirectory,
twtxtFilename, twtxtFilename,
plugins: {
...(initialConfiguration?.plugins ?? {}),
},
postLimiterConfiguration: { postLimiterConfiguration: {
active: postLimiterActive, active: postLimiterActive,
...(otherPostLimiterProps ?? {}), ...(otherPostLimiterProps ?? {}),
@@ -138,24 +124,5 @@ export default function getConfiguration(
twt, twt,
twts, twts,
}, },
uploadConfiguration: {
...uploadConfiguration,
active: uploadActive,
allowEmptyFiles,
allowedMimeTypes: getDestinationByMimeTypeConfiguration(allowedMimeTypes),
createDirsFromUploads,
directory,
encoding,
fileWriteStreamHandler,
filter,
hashAlgorithm: hashAlgorithm as string | false | undefined,
keepExtensions,
maxFields,
maxFileSize,
maxFiles,
maxTotalFileSize,
minFileSize,
route,
},
} as TwtKprConfiguration; } as TwtKprConfiguration;
} }

View File

@@ -1,7 +1,7 @@
import fsp from 'node:fs/promises'; import fsp from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { NodeCache } from '@cacheable/node-cache'; import { NodeCache, NodeCacheOptions } from '@cacheable/node-cache';
import Debug from 'debug'; import Debug from 'debug';
import { parseTwtxt } from 'twtxt-lib'; import { parseTwtxt } from 'twtxt-lib';
@@ -20,9 +20,23 @@ export default function twtxtCache({
const debug = Debug('twtkpr:twtxtCache'); const debug = Debug('twtkpr:twtxtCache');
const cache = new NodeCache(); const defaultCacheOptions: NodeCacheOptions = {
stdTTL: 299,
checkperiod: 300,
};
const cache = new NodeCache(defaultCacheOptions);
const getFromCache = async (key = '') => {
debug(`checking cache for key: ${key}`);
if (!key) return undefined;
const value = cache.get(key);
if (value) return value;
debug('Not found, reloading keys');
cache.flushAll();
const reloadCache = async () => {
const fileText = await fsp.readFile( const fileText = await fsp.readFile(
path.join(publicDirectory, twtxtFilename), path.join(publicDirectory, twtxtFilename),
'utf8' 'utf8'
@@ -37,12 +51,16 @@ export default function twtxtCache({
debug(`cache ${isLoaded ? 're' : ''}loaded`); debug(`cache ${isLoaded ? 're' : ''}loaded`);
isLoaded = true; isLoaded = true;
return cache.get(key);
}; };
reloadCache(); const reloadCache = async () => {
cache.flushAll();
};
return { return {
cache, getFromCache,
reloadCache, reloadCache,
}; };
} }

View File

@@ -1,8 +1,42 @@
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import { createReadStream } from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises'; import { readFile, writeFile } from 'node:fs/promises';
import { PassThrough } from 'node:stream';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { __dirname } from './constants.js';
async function combineWithPassthrough(sources: any[], destination: any) {
for (const stream of sources) {
await new Promise((resolve, reject) => {
let s = stream;
if (typeof stream === 'function') {
s = stream();
}
if (typeof s === 'string') {
destination.push(s);
destination.push(null);
resolve(true);
return;
}
s.pipe(destination, { end: false });
s.on('end', resolve);
s.on('error', reject);
});
}
destination.emit('end');
}
export function combineStreams(streams: any[]) {
const stream = new PassThrough();
combineWithPassthrough(streams, stream).catch((err) => stream.destroy(err));
return stream;
}
/** /**
* *
* @param userId * @param userId
@@ -51,6 +85,23 @@ export const getQueryParameterArray = (value: unknown | unknown[] = []) =>
? value.map((val) => `${val}`.trim()) ? value.map((val) => `${val}`.trim())
: [`${value}`.trim()]; : [`${value}`.trim()];
/**
*
* @param pathToFile
* @returns
*/
export const getReadStream = (pathToFile: string) => {
const theStream = createReadStream(pathToFile);
theStream.on('error', (err) => {
console.error(err);
theStream.close();
theStream.push(null);
});
return theStream;
};
/** /**
* *
* @param value * @param value

View File

@@ -1 +1,2 @@
export { default } from "./postHandler.js"; export { default } from './postHandler.js';
export { default as pluginPostHandler } from './pluginPostHandler.js';

View File

@@ -18,16 +18,17 @@ export default async function memoryCache(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction, next: NextFunction,
cache: NodeCache<unknown>,
reloadCache: () => Promise<void> reloadCache: () => Promise<void>
) { ) {
if (cache.keys().length && !['DELETE', 'POST', 'PUT'].includes(req.method)) { debug(req.method);
if (!['DELETE', 'POST', 'PUT'].includes(req.method)) {
next(); next();
return; return;
} }
reloadCache() reloadCache()
.then(() => { .then(() => {
debug('Cache reloaded');
next(); next();
}) })
.catch((err) => { .catch((err) => {

View File

@@ -0,0 +1,50 @@
import Debug from 'debug';
import express, { NextFunction, Request, Response } from 'express';
import { TwtKprPluginConfiguration, TwtKprPluginRoute } from '../../types.js';
const debug = Debug('twtkpr:postHandler');
/**
*
* @param config * @returns
*/
export default function pluginPostHandler(
plugins: TwtKprPluginConfiguration[] = [],
verifyAuthRequest: (r: Request) => Promise<boolean>,
reloadCache?: () => Promise<void>
) {
const router = express.Router();
const pluginRoutes = ([] as TwtKprPluginRoute[]).concat(
...(Object.keys(plugins)
.filter(
(key) =>
(plugins[key as keyof typeof plugins] as TwtKprPluginConfiguration)
?.postRoutes?.length
)
.map(
(key) =>
(plugins[key as keyof typeof plugins] as TwtKprPluginConfiguration)
.postRoutes
) as TwtKprPluginRoute[][])
);
pluginRoutes.forEach(({ handler, path, requiresAuth }) => {
debug(`adding POST plugin router for ${path}`);
router.post(path, async (req, res, next) => {
debug(`handling POST plugin route to ${path}`);
if (requiresAuth && !(await verifyAuthRequest(req))) {
debug('auth check failed');
next();
return;
}
handler(req, res, next);
reloadCache?.();
});
});
return router;
}

View File

@@ -3,7 +3,7 @@ import express, { NextFunction, Request, Response } from 'express';
import rateLimit from 'express-rate-limit'; import rateLimit from 'express-rate-limit';
import authCheck from '../../middlewares/authCheckJWT.js'; import authCheck from '../../middlewares/authCheckJWT.js';
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
import login from './login.js'; import login from './login.js';
import logout from './logout.js'; import logout from './logout.js';
import refresh from './refresh.js'; import refresh from './refresh.js';
@@ -17,7 +17,11 @@ const debug = Debug('twtkpr:postHandler');
* @param config * @param config
* @returns * @returns
*/ */
export default function postHandler(config: TwtKprConfiguration) { export default function postHandler(
config: TwtKprConfiguration,
plugins: TwtKprPluginConfiguration[] = [],
reloadCache?: () => Promise<void>
) {
const { postLimiterConfiguration } = config; const { postLimiterConfiguration } = config;
const { active: isLimiterActive, ...otherLimiterProps } = const { active: isLimiterActive, ...otherLimiterProps } =
postLimiterConfiguration ?? {}; postLimiterConfiguration ?? {};
@@ -59,7 +63,11 @@ export default function postHandler(config: TwtKprConfiguration) {
} }
debug('auth check succeeded'); debug('auth check succeeded');
if (type === 'twt' || content) return twt(req, res, config); if (type === 'twt' || content) {
twt(req, res, config, plugins);
reloadCache?.();
return;
}
if (type === 'editFile') return editFile(req, res, config); if (type === 'editFile') return editFile(req, res, config);
next(); next();

View File

@@ -25,13 +25,13 @@ export default async function refresh(
res: Response, res: Response,
config: TwtKprConfiguration config: TwtKprConfiguration
) { ) {
const send401 = (message: string) => { const sendError = (message: string, code = 401) => {
debug(message); debug(message);
res res
.clearCookie('accessToken') .clearCookie('accessToken')
.clearCookie('refreshToken') .clearCookie('refreshToken')
.status(401) .status(code)
.send(message ?? 'Unauthorized'); .send(message ?? 'Unauthorized');
return; return;
@@ -41,9 +41,9 @@ export default async function refresh(
const tokens = await refreshTokensDB(config.privateDirectory); const tokens = await refreshTokensDB(config.privateDirectory);
const oldToken = req.cookies.refreshToken; const oldToken = req.cookies.refreshToken;
debug(oldToken); debug(`Using old token: ${oldToken}`);
if (!oldToken) return send401('Unauthorized'); if (!oldToken) return sendError('Unauthorized');
let decoded = { id: '' }; let decoded = { id: '' };
@@ -52,14 +52,15 @@ export default async function refresh(
id: string; id: string;
}; };
debug({ decoded }); debug('Decoded token: ', { decoded });
} catch (err) { } catch (err) {
return send401('Refresh token invalid'); debug('Error decoding refresh token:', err);
return sendError('Refresh token invalid', 403);
} }
const username = req.username ?? decoded.id; const username = req.username ?? decoded.id;
if (!username) return send401('Missing username'); if (!username) return sendError('Missing username');
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Math.floor(Date.now() / 1000);
@@ -72,7 +73,7 @@ export default async function refresh(
// If token is invalid or not the latest one // If token is invalid or not the latest one
if (!validTokens.includes(oldToken)) { if (!validTokens.includes(oldToken)) {
debug('token missing from list'); debug('token missing from list');
return send401('Invalid refresh token'); return sendError('Invalid refresh token');
} }
debug('generating new tokens'); debug('generating new tokens');
@@ -84,7 +85,8 @@ export default async function refresh(
const newRefreshToken = generateRefreshToken( const newRefreshToken = generateRefreshToken(
req.username || decoded.id, req.username || decoded.id,
config.refreshSecret config.refreshSecret,
!!req.query.rememberToggle
); );
debug('updating token list'); debug('updating token list');

View File

@@ -1,10 +1,13 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import dayjs from 'dayjs';
import fs from 'node:fs'; import fs from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import dayjs from 'dayjs';
import Debug from 'debug';
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
const debug = Debug('twtkpr:twt');
/** /**
* Creates a new twt, appending it to the bottom of the TWTXT file * Creates a new twt, appending it to the bottom of the TWTXT file
@@ -15,13 +18,19 @@ import { TwtKprConfiguration } from '../../types.js';
export default function twt( export default function twt(
req: Request, req: Request,
res: Response, res: Response,
config: TwtKprConfiguration config: TwtKprConfiguration,
plugins: TwtKprPluginConfiguration[] = []
) { ) {
debug('Beginning twt add');
const { content } = req.body ?? {}; const { content } = req.body ?? {};
const date = dayjs().format(); const date = dayjs().format();
const twt = `${date}\t${content.trim()}\n`; const twt = `${date}\t${content.trim()}\n`;
debug(`Formatted twt: ${twt}`);
debug('Beginning stream...');
const stream = fs.createWriteStream( const stream = fs.createWriteStream(
join(config.publicDirectory, config.twtxtFilename), join(config.publicDirectory, config.twtxtFilename),
{ {
@@ -32,5 +41,19 @@ export default function twt(
stream.write(twt); stream.write(twt);
stream.end(); stream.end();
debug('Streaming complete');
plugins.forEach(async (plugin) => {
if (!plugin.onAfterTwt) return;
debug(
`Handling plugin onAfterTwt function${
plugin.name ? ' from ' + plugin.name : ''
}`
);
plugin.onAfterTwt(twt);
});
res.status(200).send(twt); res.status(200).send(twt);
} }

View File

@@ -1 +1,2 @@
export { default } from "./putHandler.js"; export { default } from './putHandler.js';
export { default as pluginPutHandler } from './pluginPutHandler.js';

View File

@@ -0,0 +1,52 @@
import Debug from 'debug';
import express, { NextFunction, Request, Response } from 'express';
import { TwtKprPluginConfiguration, TwtKprPluginRoute } from '../../types.js';
const debug = Debug('twtkpr:postHandler');
/**
*
* @param plugins
* @returns
*/
export default function pluginPutHandler(
plugins: TwtKprPluginConfiguration[] = [],
verifyAuthRequest: (r: Request) => Promise<boolean>,
reloadCache?: () => Promise<void>
) {
const router = express.Router();
const pluginRoutes = ([] as TwtKprPluginRoute[]).concat(
...(Object.keys(plugins)
.filter(
(key) =>
(plugins[key as keyof typeof plugins] as TwtKprPluginConfiguration)
?.putRoutes?.length
)
.map(
(key) =>
(plugins[key as keyof typeof plugins] as TwtKprPluginConfiguration)
.putRoutes
) as TwtKprPluginRoute[][])
);
pluginRoutes.forEach(({ handler, path, requiresAuth }) => {
debug(`adding PUT plugin router for ${path}`);
router.put(path, async (req, res, next) => {
debug(`handling PUT plugin route to ${path}`);
if (requiresAuth && !(await verifyAuthRequest(req))) {
debug('auth check failed');
next();
return;
}
handler(req, res, next);
reloadCache?.();
});
});
return router;
}

View File

@@ -2,7 +2,7 @@ import Debug from 'debug';
import express from 'express'; import express from 'express';
import authCheck from '../../middlewares/authCheckJWT.js'; import authCheck from '../../middlewares/authCheckJWT.js';
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
import editFile from './editFile.js'; import editFile from './editFile.js';
const debug = Debug('twtkpr:putHandler'); const debug = Debug('twtkpr:putHandler');
@@ -12,7 +12,11 @@ const debug = Debug('twtkpr:putHandler');
* @param config * @param config
* @returns * @returns
*/ */
export default function putHandler(config: TwtKprConfiguration) { export default function putHandler(
config: TwtKprConfiguration,
plugins: TwtKprPluginConfiguration[] = [],
reloadCache?: () => Promise<void>
) {
const router = express.Router(); const router = express.Router();
router.put('/', (req, res, next) => { router.put('/', (req, res, next) => {
@@ -28,7 +32,10 @@ export default function putHandler(config: TwtKprConfiguration) {
debug('auth check succeeded'); debug('auth check succeeded');
return editFile(req, res, config); editFile(req, res, config);
reloadCache?.();
}); });
return router; return router;
} }

View File

@@ -19,7 +19,7 @@ import NodeCache from '@cacheable/node-cache';
export default function followingHandler( export default function followingHandler(
req: Request, req: Request,
res: Response, res: Response,
cache: NodeCache<unknown>, following: Twttr[],
followingParameter: QueryParameters['following'] followingParameter: QueryParameters['following']
) { ) {
const followingsToMatch = getQueryParameterArray( const followingsToMatch = getQueryParameterArray(
@@ -40,7 +40,7 @@ export default function followingHandler(
if (wantsJson) res.set('content-type', 'application/json'); if (wantsJson) res.set('content-type', 'application/json');
else res.set('content-type', 'text/plain'); else res.set('content-type', 'text/plain');
const matchedFollowing = (cache.get('following') as Twttr[]).filter( const matchedFollowing = following.filter(
({ nick, url }) => ({ nick, url }) =>
(!followingsToMatch.length || (!followingsToMatch.length ||
(followingsToMatch.length === 1 && followingsToMatch[0] === '') || (followingsToMatch.length === 1 && followingsToMatch[0] === '') ||

View File

@@ -1,14 +1,12 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import type { Metadata } from 'twtxt-lib'; import type { Metadata } from 'twtxt-lib';
import { env } from '../../lib/env.js';
import twtxtCache from '../../lib/twtxtCache.js';
import { import {
generateEtag, generateEtag,
getQueryParameterArray, getQueryParameterArray,
getValueOrFirstEntry, getValueOrFirstEntry,
} from '../../lib/utils.js'; } from '../../lib/utils.js';
import NodeCache from '@cacheable/node-cache'; import { NodeCache } from '@cacheable/node-cache';
import { QueryParameters } from '../../types.js'; import { QueryParameters } from '../../types.js';
export interface MetadataHandler { export interface MetadataHandler {
@@ -28,7 +26,7 @@ export interface MetadataHandler {
export default function metadataHandler( export default function metadataHandler(
req: Request, req: Request,
res: Response, res: Response,
cache: NodeCache<unknown>, metadata: Metadata,
metadataParameter: QueryParameters['metadata'] metadataParameter: QueryParameters['metadata']
) { ) {
const metadataToMatch = getQueryParameterArray(req.query[metadataParameter]); const metadataToMatch = getQueryParameterArray(req.query[metadataParameter]);
@@ -38,8 +36,6 @@ export default function metadataHandler(
...getQueryParameterArray(req.query.s), ...getQueryParameterArray(req.query.s),
]; ];
const metadata = (cache.get('metadata') as Metadata) ?? {};
const wantsJson = const wantsJson =
req.is('json') || req.is('json') ||
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json'; getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';

View File

@@ -1,15 +1,15 @@
import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import NodeCache from '@cacheable/node-cache';
import Debug from 'debug'; import Debug from 'debug';
import type { NextFunction, Request, Response } from 'express'; import type { NextFunction, Request, Response } from 'express';
import { __dirname } from '../../lib/constants.js'; import { __dirname } from '../../lib/constants.js';
import { generateEtag } from '../../lib/utils.js'; import { combineStreams, generateEtag } from '../../lib/utils.js';
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration, TwtKprPluginConfiguration } from '../../types.js';
import renderApp from '../renderApp/index.js'; import renderApp from '../renderApp/index.js';
import followingHandler from './followingHandler.js'; import followingHandler from './followingHandler.js';
import metadataHandler from './metadataHandler.js'; import metadataHandler from './metadataHandler.js';
import twtHandler from './twtHandler.js'; import twtHandler from './twtHandler.js';
import { Twt } from 'twtxt-lib'; import { Metadata, Twt, Twttr } from 'twtxt-lib';
const debug = Debug('twtkpr:queryHandler'); const debug = Debug('twtkpr:queryHandler');
@@ -22,13 +22,17 @@ const debug = Debug('twtkpr:queryHandler');
*/ */
export default function queryHandler( export default function queryHandler(
config: TwtKprConfiguration, config: TwtKprConfiguration,
cache: NodeCache<unknown>, getFromCache: (key?: string) => Promise<unknown>,
verifyAuthRequest: (r: Request) => Promise<boolean> verifyAuthRequest: (r: Request) => Promise<boolean>,
plugins: TwtKprPluginConfiguration[] = []
) { ) {
const { mainRoute, queryParameters, uploadConfiguration } = config; const { mainRoute, queryParameters } = config;
const getPluginStreams = (type: keyof TwtKprPluginConfiguration) =>
plugins.filter((plugin) => !!plugin[type]).map((plugin) => plugin[type]);
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
debug({ query: JSON.stringify(req.query) }); debug(`handling query`, JSON.stringify(req.query));
if (!Object.keys(req.query).length) { if (!Object.keys(req.query).length) {
next(); next();
@@ -36,53 +40,119 @@ export default function queryHandler(
} }
if (req.query[queryParameters.app] !== undefined) { if (req.query[queryParameters.app] !== undefined) {
const appContent = renderApp({ mainRoute, uploadConfiguration }); debug('rendering app');
const appContent = renderApp({ mainRoute });
res.set('etag', generateEtag(appContent)).send(appContent); res.set('etag', generateEtag(appContent)).send(appContent);
return; return;
} }
if (req.query[queryParameters.css] !== undefined) { if (req.query[queryParameters.css] !== undefined) {
res.sendFile('styles.css', { debug('rendering css');
root: path.resolve(__dirname, 'client'), const mainStream = fs.createReadStream(
path.join(__dirname, 'client', 'styles.css')
);
const pluginStreams = getPluginStreams('clientCSS');
const streams = [mainStream, ...pluginStreams];
const combined = combineStreams(streams);
res.writeHead(200, {
'Content-Type': 'text/css',
}); });
combined.pipe(res);
combined.on('error', (error) => {
console.error('Error streaming file:', error);
res.end();
});
return; return;
} }
if (req.query[queryParameters.js] !== undefined) { if (req.query[queryParameters.js] !== undefined) {
res.sendFile('script.js', { debug('rendering js');
root: path.resolve(__dirname, 'client'), const mainStream = fs.createReadStream(
path.join(__dirname, 'client', 'script.js')
);
const pluginStreams = getPluginStreams('clientJS');
const streams = [mainStream, ...pluginStreams];
const combined = combineStreams(streams);
res.writeHead(200, {
'Content-Type': 'application/javascript',
}); });
combined.pipe(res);
combined.on('error', (error) => {
console.error('Error streaming file:', error);
res.end();
});
return; return;
} }
if ( if (
req.query[queryParameters.following] !== undefined && req.query[queryParameters.following] !== undefined &&
cache.get('following') (await getFromCache('following'))
) { ) {
return followingHandler(req, res, cache, queryParameters.following); debug('rendering following');
const following = await getFromCache('following');
return followingHandler(
req,
res,
following as Twttr[],
queryParameters.following
);
} }
if ( if (
req.query[queryParameters.metadata] !== undefined && req.query[queryParameters.metadata] !== undefined &&
cache.get('metadata') (await getFromCache('metadata'))
) { ) {
return metadataHandler(req, res, cache, queryParameters.metadata); debug('rendering metadata');
const metadata = (await getFromCache('metadata')) as Metadata;
return metadataHandler(req, res, metadata, queryParameters.metadata);
} }
if ( if (
(req.query[queryParameters.twt] !== undefined || (req.query[queryParameters.twt] !== undefined ||
req.query[queryParameters.twts] !== undefined) && req.query[queryParameters.twts] !== undefined) &&
cache.get('twts') (await getFromCache('twts'))
) { ) {
debug('rendering twts');
const twts = await getFromCache('twts');
return twtHandler( return twtHandler(
req, req,
res, res,
cache.get('twts') as Twt[], twts as Twt[],
queryParameters.twt, queryParameters.twt,
queryParameters.twts queryParameters.twts
); );
} }
(plugins as TwtKprPluginConfiguration[]).forEach((plugin) => {
if (!plugin?.queryRoutes?.length) return;
plugin.queryRoutes.forEach(async (route) => {
// default to no auth
const { handler, queryParameter, requiresAuth = false } = route ?? {};
if (queryParameter && req.query[queryParameter] !== undefined) {
debug(`rendering plugin queryParameter ${queryParameter}`);
if (requiresAuth && !(await verifyAuthRequest(req))) {
debug('auth check failed');
next();
return;
}
return handler(req, res, next);
}
});
});
next(); next();
}; };
} }

View File

@@ -1,5 +1,5 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import type { Metadata, Twt } from 'twtxt-lib'; import type { Twt } from 'twtxt-lib';
import { import {
generateEtag, generateEtag,
@@ -8,7 +8,6 @@ import {
} from '../../lib/utils.js'; } from '../../lib/utils.js';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js'; import utc from 'dayjs/plugin/utc.js';
import NodeCache from '@cacheable/node-cache';
import { QueryParameters } from '../../types.js'; import { QueryParameters } from '../../types.js';
dayjs.extend(utc); dayjs.extend(utc);

View File

@@ -1,8 +1,6 @@
import { version } from '../../packageInfo.js'; import { version } from '../../packageInfo.js';
import { TwtKprConfiguration } from '../../types.js'; import { TwtKprConfiguration } from '../../types.js';
import renderUploadButton from './renderUploadButton.js';
/** /**
* *
* @param param0 * @param param0
@@ -10,8 +8,7 @@ import renderUploadButton from './renderUploadButton.js';
*/ */
export default function renderApp({ export default function renderApp({
mainRoute, mainRoute,
uploadConfiguration, }: Pick<TwtKprConfiguration, 'mainRoute'>) {
}: Pick<TwtKprConfiguration, 'mainRoute' | 'uploadConfiguration'>) {
return `<!doctype html> return `<!doctype html>
<html class="no-js" lang="en" xmlns:fb="http://ogp.me/ns/fb#"> <html class="no-js" lang="en" xmlns:fb="http://ogp.me/ns/fb#">
@@ -91,9 +88,8 @@ export default function renderApp({
</a> </a>
</div> </div>
</div> </div>
${renderUploadButton(uploadConfiguration)}
<label class="twtControls-contentLabel" for="twtControlsContentInput"> <label class="twtControls-contentLabel" for="twtControlsContentInput">
<textarea class="twtControls-contentInput" <textarea class="twtControls-contentInput emoji_target"
id="twtControlsContentInput" name="content" id="twtControlsContentInput" name="content"
placeholder="What do you want to say?"></textarea> placeholder="What do you want to say?"></textarea>
</label> </label>
@@ -107,7 +103,6 @@ export default function renderApp({
<div class="popupMenu-appInfo appInfo"> <div class="popupMenu-appInfo appInfo">
TwtKpr v${version ?? 'Unknown'} TwtKpr v${version ?? 'Unknown'}
</div> </div>
${renderUploadButton(uploadConfiguration, 'small')}
<button class="twtControls-editButton button" id="twtControlsEditButton"> <button class="twtControls-editButton button" id="twtControlsEditButton">
Edit File Edit File
</button> </button>

View File

@@ -1,29 +0,0 @@
import { TwtKprConfiguration } from '../../types.js';
/**
*
* @param uploadConfiguration
* @param variant
* @returns
*/
export default function renderUploadButton(
uploadConfiguration: TwtKprConfiguration['uploadConfiguration'],
variant: 'normal' | 'small' = 'normal'
) {
const { active, allowedMimeTypes, route } = uploadConfiguration ?? {};
if (!active) return '';
// determine accept from allowed mime types - may need to rebuild value based on fallback n getConfiguration, rather than at the end.
return `
<label class="button twtControls-uploadInputLabel twtControls-uploadInputLabel-${variant}"
for="twtControlsUploadInput-${variant}"
>
Upload${variant === 'normal' ? '<br />' : ' '}Files
<input accept="*" class="twtControls-uploadInput" data-route="${route}"
id="twtControlsUploadInput-${variant}"
multiple type="file" />
</label>
`;
}

View File

@@ -1,174 +0,0 @@
import fsp from 'node:fs/promises';
import path from 'node:path';
import formidable from 'formidable';
import type { NextFunction, Request, Response } from 'express';
import Debug from 'debug';
import { __dirname } from '../lib/env.js';
import { MimeOptions, TwtKprConfiguration } from '../types.js';
const debug = Debug('twtkpr:uploadHandler');
/**
*
* @param allowedMimeTypes
* @returns
*/
const getDestinationByMimeTypeConfiguration = (
allowedMimeTypes?: string | string[] | Record<string, string>
) => {
const fallback: Record<string, string> = {
audio: 'audio',
image: 'images',
text: 'texts',
video: 'videos',
'*': 'files',
};
const mimeTypeArrayReducer = (acc: Record<string, string>, curr: string) => {
if (fallback[curr]) acc[curr] = fallback[curr];
else acc[curr] = `${curr}s`;
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 as string[]).reduce(mimeTypeArrayReducer, {});
if (typeof allowedMimeTypes === 'object') return allowedMimeTypes;
return fallback;
};
/**
*
* @param config
* @param verifyAuthRequest
* @returns
*/
export default function uploadHandler(
config: TwtKprConfiguration,
verifyAuthRequest: (r: Request) => Promise<boolean>
) {
return async (req: Request, res: Response, next: NextFunction) => {
debug('checking auth');
if (!(await verifyAuthRequest(req))) {
debug('auth check failed');
res.status(401).send('Unauthorized');
return;
}
debug('auth check succeeded');
const { active, allowedMimeTypes, directory, route, ...otherProps } =
config.uploadConfiguration;
if (
!active ||
(Array.isArray(allowedMimeTypes) && !allowedMimeTypes.length)
) {
next();
return;
}
debug('using configuration: ', {
uploadConfiguration: config.uploadConfiguration,
});
const form = formidable({
uploadDir: directory,
...otherProps,
});
form.parse(req, async (err, fields, files) => {
if (err) {
next(err);
return;
}
const uploadsDir = (route ?? '').replaceAll('/', '');
let hadFileError = false;
const processedFiles: string[] = [];
const destinationByMimeType = allowedMimeTypes;
debug(`processing ${(files?.files ?? []).length} files`);
for (const file of files?.files ?? []) {
const { filepath, hash, mimetype, newFilename, originalFilename } =
file ?? {};
if (!(filepath && newFilename && originalFilename)) return;
console.log({ file });
let ext = path.extname(originalFilename).toLocaleLowerCase();
if (ext === '.jpeg') ext = '.jpg';
const finalFilename = (
hash && (mimetype?.includes('image') || mimetype?.includes('video'))
? `${hash}${ext}`
: originalFilename
)
.replace(/\s+/g, '-')
.toLocaleLowerCase();
let destinationDir = '';
Object.keys(destinationByMimeType).forEach((mimeType) => {
if (file.mimetype?.split('/')?.[0] === mimeType.toLocaleLowerCase())
destinationDir =
(
destinationByMimeType[
mimeType as keyof typeof destinationByMimeType
] as MimeOptions
).directory ?? '';
});
if (destinationDir === '')
destinationDir =
(
destinationByMimeType[
'*' as keyof typeof destinationByMimeType
] as MimeOptions
).directory ?? uploadsDir;
const finalPath = path.join(process.cwd(), 'public', destinationDir);
debug(`creating '${finalPath}'`);
fsp.mkdir(finalPath, { recursive: true });
debug(`copying '${filepath}' to '/${destinationDir}/${finalFilename}'`);
try {
await fsp.copyFile(filepath, path.join(finalPath, finalFilename));
debug(`cleaning up '${filepath}'`);
await fsp.rm(filepath);
debug(`processed successfully`);
processedFiles.push(`/${destinationDir}/${finalFilename}`);
} catch (err) {
debug(`error!`);
hadFileError = true;
console.error(err);
}
}
debug('generating reply...');
if (hadFileError && processedFiles.length) {
res.type('text/plain').status(206).send(processedFiles.join('\n'));
return;
}
if (!processedFiles.length) {
res.type('text/plain').status(500).send('No files processed');
return;
}
res.type('text/plain').status(201).send(processedFiles.join('\n'));
});
};
}

Some files were not shown because too many files have changed in this diff Show More