Fix fragment exports for older Xray clients (#6702)

* Fix fragment exports for older Xray clients

* fix(link): tolerate a finalmask without tcp in panel share links

withLegacyFragmentRanges called finalmask.tcp.map unguarded, but stored
rows reach the link generator unparsed and dropEmptyFinalMask deletes an
empty tcp list on save. Any VMess/VLESS/Trojan/SS inbound with only UDP
masks or quicParams threw a TypeError in the QR, info and export-links
views. The Go counterpart already skipped a missing tcp.

Also trims the Go helper's comment to the two-line cap and drops a
[]string branch no JSON-decoded finalmask can reach.

---------

Co-authored-by: Artem K <a.kush@vkteam.ru>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
Artem K
2026-10-02 18:38:44 +03:00
committed by GitHub
parent a716122ef2
commit 721de5adde
6 changed files with 413 additions and 7 deletions
+28 -1
View File
@@ -144,9 +144,36 @@ function hasShareableFinalMaskValue(value: unknown): boolean {
return true;
}
function withLegacyFragmentRanges(finalmask: FinalMaskStreamSettings): FinalMaskStreamSettings {
// Stored rows reach here unparsed: dropEmptyFinalMask deletes an empty `tcp` on save.
if (!Array.isArray(finalmask.tcp)) return finalmask;
let changed = false;
const tcp = finalmask.tcp.map((mask) => {
if (mask.type !== 'fragment' || !mask.settings) return mask;
const settings = mask.settings;
const legacy: Record<string, unknown> = {};
if (settings.length === undefined && Array.isArray(settings.lengths)) {
const length = settings.lengths.at(-1);
if (typeof length === 'string' && length.trim().length > 0) legacy.length = length;
}
if (settings.delay === undefined && Array.isArray(settings.delays)) {
const delay = settings.delays.at(-1);
if (typeof delay === 'string' && delay.trim().length > 0) legacy.delay = delay;
}
if (Object.keys(legacy).length === 0) return mask;
changed = true;
return { ...mask, settings: { ...settings, ...legacy } };
});
return changed ? { ...finalmask, tcp } : finalmask;
}
function serializeFinalMask(finalmask: FinalMaskStreamSettings | undefined): string {
if (!finalmask) return '';
return hasShareableFinalMaskValue(finalmask) ? JSON.stringify(finalmask) : '';
const shareable = withLegacyFragmentRanges(finalmask);
return hasShareableFinalMaskValue(shareable) ? JSON.stringify(shareable) : '';
}
function applyFinalMaskToObj(