upstream: integrar 0.9.5 en modelos y vistas medianas
chats_model: se adopta 0.9.5 y se reinyecta el replyTo de la rama (responder a mensajes). El fix del salt en joinByInvite ya venia en upstream, y el limite de mensajes pasa a ser configurable (60/h) en vez del parche que lo saltaba en movil. config-manager: version de 0.9.5 con karvanMod, los seis modulos de public y toda la normalizacion de bottomBarPins reinyectados; housingMod fuera. Entran blogsMod y pollsMod del dev. Se adopta 0.9.5 en votes_model (ya trae el margen de 2 minutos de la rama y anade codigos de error), blobHandler (superconjunto compatible: mantiene handleBlobUpload y anade subida multiple y deteccion de OGG), market_model (misma refactorizacion mas opiniones y compradores), agenda_model y agenda_view (el codigo de 0.9.5 ya degrada solo sin housingModel), opinions_view, cv_view y trending_view. peers_view y parliament_view se quedan como estan: sus celdas llevan data-label, que es lo que OasisMobile.css usa para convertir las tablas en tarjetas en movil. Verificado con la app en marcha: 20 rutas OK, interfaz intacta (10 categorias, 47 modulos, 7 accesos rapidos, 0 etiquetas vacias) y Karvan completo de punta a punta — crear sala, enviar y leer mensajes, mailbox de senalizacion WebRTC con miembros, y la pagina de sala sirviendo karvan.js con el panel de videollamada.
This commit is contained in:
parent
82b6c3ae05
commit
c662e37668
10 changed files with 310 additions and 40 deletions
|
|
@ -58,25 +58,30 @@ class FileTooLargeError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
const handleBlobUpload = async function (ctx, fileFieldName) {
|
||||
if (!ctx.request.files || !ctx.request.files[fileFieldName]) {
|
||||
return null;
|
||||
}
|
||||
const OGG_VIDEO_MARKERS = ['theora', 'VP80', 'OggDS', 'Dirac'];
|
||||
const OGG_AUDIO_MARKERS = ['vorbis', 'OpusHead', 'FLAC', 'Speex'];
|
||||
|
||||
const blobUpload = ctx.request.files[fileFieldName];
|
||||
if (!blobUpload) return null;
|
||||
const oggMime = (buffer) => {
|
||||
const head = buffer.slice(0, 65536);
|
||||
for (const marker of OGG_VIDEO_MARKERS) if (head.includes(Buffer.from(marker))) return 'video/ogg';
|
||||
for (const marker of OGG_AUDIO_MARKERS) if (head.includes(Buffer.from(marker))) return 'audio/ogg';
|
||||
return 'video/ogg';
|
||||
};
|
||||
|
||||
const storeUploadedFile = async function (blobUpload) {
|
||||
if (!blobUpload || !blobUpload.filepath) return null;
|
||||
|
||||
let data = await promisesFs.readFile(blobUpload.filepath);
|
||||
if (data.length === 0) return null;
|
||||
|
||||
if (data.length > MAX_BLOB_SIZE) {
|
||||
throw new FileTooLargeError(blobUpload.originalFilename || blobUpload.name || fileFieldName, data.length);
|
||||
throw new FileTooLargeError(blobUpload.originalFilename || blobUpload.name || 'file', data.length);
|
||||
}
|
||||
|
||||
const EXTENSION_MIME_MAP = {
|
||||
'.mp4': 'video/mp4', '.webm': 'video/webm', '.ogg': 'video/ogg',
|
||||
'.ogv': 'video/ogg', '.avi': 'video/x-msvideo', '.mov': 'video/quicktime',
|
||||
'.mkv': 'video/x-matroska', '.mp3': 'audio/mpeg', '.wav': 'audio/wav',
|
||||
'.ogv': 'video/ogg', '.oga': 'audio/ogg', '.mkv': 'video/x-matroska', '.avi': 'video/x-msvideo', '.mov': 'video/quicktime',
|
||||
'.mp3': 'audio/mpeg', '.wav': 'audio/wav',
|
||||
'.flac': 'audio/flac', '.aac': 'audio/aac', '.opus': 'audio/opus',
|
||||
'.pdf': 'application/pdf', '.png': 'image/png', '.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp',
|
||||
|
|
@ -93,9 +98,12 @@ const handleBlobUpload = async function (ctx, fileFieldName) {
|
|||
blob.mime = null;
|
||||
}
|
||||
|
||||
if (!blob.mime && blob.name) {
|
||||
if (blob.mime === 'application/ogg') blob.mime = oggMime(data);
|
||||
|
||||
const GENERIC_MIMES = new Set(['application/octet-stream', 'video/x-matroska']);
|
||||
if ((!blob.mime || GENERIC_MIMES.has(blob.mime)) && blob.name) {
|
||||
const ext = (blob.name.match(/\.[^.]+$/) || [''])[0].toLowerCase();
|
||||
blob.mime = EXTENSION_MIME_MAP[ext] || 'application/octet-stream';
|
||||
blob.mime = EXTENSION_MIME_MAP[ext] || blob.mime || 'application/octet-stream';
|
||||
}
|
||||
|
||||
if (!blob.mime) {
|
||||
|
|
@ -126,6 +134,25 @@ const handleBlobUpload = async function (ctx, fileFieldName) {
|
|||
return `\n[${blob.name}](${blob.id})`;
|
||||
};
|
||||
|
||||
const handleBlobUpload = async function (ctx, fileFieldName) {
|
||||
if (!ctx.request.files || !ctx.request.files[fileFieldName]) return null;
|
||||
const entry = ctx.request.files[fileFieldName];
|
||||
const first = Array.isArray(entry) ? entry[0] : entry;
|
||||
return storeUploadedFile(first);
|
||||
};
|
||||
|
||||
const handleBlobUploads = async function (ctx, fileFieldName, max = 8) {
|
||||
if (!ctx.request.files || !ctx.request.files[fileFieldName]) return [];
|
||||
const entry = ctx.request.files[fileFieldName];
|
||||
const files = (Array.isArray(entry) ? entry : [entry]).slice(0, Math.max(0, max));
|
||||
const out = [];
|
||||
for (const file of files) {
|
||||
const stored = await storeUploadedFile(file);
|
||||
if (stored) out.push(stored);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
function waitForBlob(ssbClient, blobId, timeoutMs = 8000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let done = false;
|
||||
|
|
@ -229,6 +256,8 @@ const serveBlob = async function (ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
if (mime === 'application/ogg') mime = oggMime(buffer);
|
||||
|
||||
const isSvg = mime === 'image/svg+xml';
|
||||
const qName = ctx.query.name ? String(ctx.query.name).replace(/["\r\n\\]/g, '').trim() : '';
|
||||
const safeRaw = String(raw).replace(/["\r\n\\]/g, '');
|
||||
|
|
@ -275,5 +304,5 @@ const serveBlob = async function (ctx) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports = { handleBlobUpload, serveBlob, FileTooLargeError };
|
||||
module.exports = { handleBlobUpload, handleBlobUploads, serveBlob, oggMime, FileTooLargeError };
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue