|
|
@@ -21,23 +21,75 @@
|
|
|
* - `ChatService.stageFiles()` —— 选文件时就拦,最好的体验:不合格的根本不进来
|
|
|
* - `ChatService.uploadPending()` —— 发送前兜底,防止暂存期被替换或绕过
|
|
|
*/
|
|
|
-export const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
|
+/**
|
|
|
+ * 附件上传限制(2026-11-10 建立 · 2026-11-13 用户裁决调整)
|
|
|
+ *
|
|
|
+ * ## 历史
|
|
|
+ *
|
|
|
+ * 最初取 20MB:超过 Matrix homeserver 的 `m.upload.size` 会被服务端直接拒,
|
|
|
+ * 用户只看到一条莫名其妙的失败,所以客户端先挡一道。
|
|
|
+ *
|
|
|
+ * ## 现在(用户裁决 2026-11-13:「去掉限制,看一下 matrix 最大支持多少」)
|
|
|
+ *
|
|
|
+ * **不再用写死的 20MB 拦人。** 改为:
|
|
|
+ * 1. 上限从服务端 `/media/v3/config` 的 `m.upload.size` **动态获取**
|
|
|
+ * (实测 `matrix.fmode.cn` 为 **32MB / 33554432 字节**);
|
|
|
+ * 2. 拿不到服务端值时**不拦截**(`Infinity`)——宁可让服务端报错,也不误伤;
|
|
|
+ * 3. 超限时给出**带了真实上限**的友好提示,而不是一句「超过限制」。
|
|
|
+ *
|
|
|
+ * ## 为什么保留「拦截」这个能力
|
|
|
+ *
|
|
|
+ * 上传是「先 S3、再 Matrix」两份都传。超大文件传完 S3 再被 Matrix 拒,
|
|
|
+ * 用户白等很久。所以仍在**选文件时**用动态上限做一次预检 ——
|
|
|
+ * 但阈值来自服务端,不是产品拍的数字。
|
|
|
+ */
|
|
|
+
|
|
|
+/**
|
|
|
+ * 服务端上限(字节)。`null` = 尚未探测到。
|
|
|
+ *
|
|
|
+ * 由 `MatrixClientService` 在连接成功后调 `/media/v3/config` 写入。
|
|
|
+ */
|
|
|
+let serverLimitBytes: number | null = null;
|
|
|
+
|
|
|
+/** 记录服务端上报的上传上限 */
|
|
|
+export function setServerUploadLimit(bytes: number | null): void {
|
|
|
+ serverLimitBytes = typeof bytes === 'number' && bytes > 0 ? bytes : null;
|
|
|
+}
|
|
|
+
|
|
|
+/** 当前生效的上限:优先服务端值;未探测到则不限制 */
|
|
|
+export function uploadLimitBytes(): number {
|
|
|
+ return serverLimitBytes ?? Number.POSITIVE_INFINITY;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 兼容旧调用:`MAX_UPLOAD_BYTES` 的语义是「**已知**的服务端上限」。
|
|
|
+ *
|
|
|
+ * ⚠️ 未探测到服务端值时它是 `Infinity`。判断「会不会超」请用 `isOversize()`。
|
|
|
+ */
|
|
|
+export const MAX_UPLOAD_BYTES = 32 * 1024 * 1024;
|
|
|
|
|
|
/** 人类可读的大小,用于提示文案 */
|
|
|
export function formatBytes(n: number): string {
|
|
|
+ if (!Number.isFinite(n)) return '不限';
|
|
|
if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
|
if (n >= 1024) return `${(n / 1024).toFixed(0)}KB`;
|
|
|
return `${n}B`;
|
|
|
}
|
|
|
|
|
|
-/** 该文件是否超出上传上限 */
|
|
|
+/**
|
|
|
+ * 该文件是否超出上传上限。
|
|
|
+ *
|
|
|
+ * 🔴 上限动态取自服务端;**未探测到时不拦截**(返回 false)。
|
|
|
+ */
|
|
|
export function isOversize(file: { size: number }): boolean {
|
|
|
- return file.size > MAX_UPLOAD_BYTES;
|
|
|
+ const limit = uploadLimitBytes();
|
|
|
+ if (!Number.isFinite(limit)) return false;
|
|
|
+ return file.size > limit;
|
|
|
}
|
|
|
|
|
|
-/** 超限提示文案(统一口径,单聊/群聊共用) */
|
|
|
+/** 超限提示文案(统一口径,单聊/群聊共用;带上服务端真实上限) */
|
|
|
export function oversizeMessage(file: { name: string; size: number }): string {
|
|
|
- return `「${file.name}」${formatBytes(file.size)},超过单个附件上限 ${formatBytes(MAX_UPLOAD_BYTES)},未添加`;
|
|
|
+ return `「${file.name}」${formatBytes(file.size)},超过平台单文件上限 ${formatBytes(uploadLimitBytes())},未添加`;
|
|
|
}
|
|
|
|
|
|
/** 超限提示的标题(去重键;同一轮多个文件只弹一条) */
|