22import { Base64 } from 'js-base64' ;
33import { isIPv6 } from '@/utils' ;
44import { normalizePluginMuxValue } from './utils' ;
5+ import { normalizeXhttpScalarUpperBound } from '../xhttp-utils' ;
56
67function isObject ( value ) {
78 return value != null && typeof value === 'object' && ! Array . isArray ( value ) ;
@@ -103,6 +104,22 @@ function buildXhttpDownloadSettings(downloadSettings) {
103104 if ( downloadSettings [ 'x-padding-bytes' ] ) {
104105 xhttpSettings . xPaddingBytes = downloadSettings [ 'x-padding-bytes' ] ;
105106 }
107+ if ( downloadSettings [ 'sc-max-each-post-bytes' ] != null ) {
108+ const scMaxEachPostBytes = normalizeXhttpScalarUpperBound (
109+ downloadSettings [ 'sc-max-each-post-bytes' ] ,
110+ ) ;
111+ if ( scMaxEachPostBytes != null ) {
112+ xhttpSettings . scMaxEachPostBytes = scMaxEachPostBytes ;
113+ }
114+ }
115+ if ( downloadSettings [ 'sc-min-posts-interval-ms' ] != null ) {
116+ const scMinPostsIntervalMs = normalizeXhttpScalarUpperBound (
117+ downloadSettings [ 'sc-min-posts-interval-ms' ] ,
118+ ) ;
119+ if ( scMinPostsIntervalMs != null ) {
120+ xhttpSettings . scMinPostsIntervalMs = scMinPostsIntervalMs ;
121+ }
122+ }
106123
107124 const xmux = mapReuseSettingsToXmux ( downloadSettings [ 'reuse-settings' ] ) ;
108125 if ( xmux ) {
@@ -126,7 +143,20 @@ function buildStructuredVlessExtra(proxy) {
126143 extra . xPaddingBytes = xhttpOpts [ 'x-padding-bytes' ] ;
127144 }
128145 if ( xhttpOpts [ 'sc-max-each-post-bytes' ] != null ) {
129- extra . scMaxEachPostBytes = xhttpOpts [ 'sc-max-each-post-bytes' ] ;
146+ const scMaxEachPostBytes = normalizeXhttpScalarUpperBound (
147+ xhttpOpts [ 'sc-max-each-post-bytes' ] ,
148+ ) ;
149+ if ( scMaxEachPostBytes != null ) {
150+ extra . scMaxEachPostBytes = scMaxEachPostBytes ;
151+ }
152+ }
153+ if ( xhttpOpts [ 'sc-min-posts-interval-ms' ] != null ) {
154+ const scMinPostsIntervalMs = normalizeXhttpScalarUpperBound (
155+ xhttpOpts [ 'sc-min-posts-interval-ms' ] ,
156+ ) ;
157+ if ( scMinPostsIntervalMs != null ) {
158+ extra . scMinPostsIntervalMs = scMinPostsIntervalMs ;
159+ }
130160 }
131161
132162 const xmux = mapReuseSettingsToXmux ( xhttpOpts [ 'reuse-settings' ] ) ;
@@ -144,13 +174,124 @@ function buildStructuredVlessExtra(proxy) {
144174 return Object . keys ( extra ) . length > 0 ? JSON . stringify ( extra ) : '' ;
145175}
146176
177+ function applyNormalizedXhttpScalar ( target , rawKey , value ) {
178+ if ( ! isObject ( target ) ) {
179+ return false ;
180+ }
181+
182+ const normalizedValue = normalizeXhttpScalarUpperBound ( value ) ;
183+ if ( normalizedValue != null ) {
184+ if ( target [ rawKey ] === normalizedValue ) {
185+ return false ;
186+ }
187+ target [ rawKey ] = normalizedValue ;
188+ return true ;
189+ }
190+
191+ if ( Object . prototype . hasOwnProperty . call ( target , rawKey ) ) {
192+ delete target [ rawKey ] ;
193+ return true ;
194+ }
195+
196+ return false ;
197+ }
198+
199+ function normalizeRawXhttpExtra ( proxy ) {
200+ if ( ! proxy . _extra ) {
201+ return '' ;
202+ }
203+
204+ let rawExtra ;
205+ try {
206+ rawExtra = JSON . parse ( proxy . _extra ) ;
207+ } catch ( e ) {
208+ return proxy . _extra ;
209+ }
210+
211+ if ( ! isObject ( rawExtra ) || ! isObject ( proxy [ 'xhttp-opts' ] ) ) {
212+ return proxy . _extra ;
213+ }
214+
215+ let didChange = false ;
216+ const xhttpOpts = proxy [ 'xhttp-opts' ] ;
217+
218+ if ( xhttpOpts [ 'sc-max-each-post-bytes' ] != null ) {
219+ didChange =
220+ applyNormalizedXhttpScalar (
221+ rawExtra ,
222+ 'scMaxEachPostBytes' ,
223+ xhttpOpts [ 'sc-max-each-post-bytes' ] ,
224+ ) || didChange ;
225+ }
226+ if ( xhttpOpts [ 'sc-min-posts-interval-ms' ] != null ) {
227+ didChange =
228+ applyNormalizedXhttpScalar (
229+ rawExtra ,
230+ 'scMinPostsIntervalMs' ,
231+ xhttpOpts [ 'sc-min-posts-interval-ms' ] ,
232+ ) || didChange ;
233+ }
234+
235+ const downloadSettings = xhttpOpts [ 'download-settings' ] ;
236+ if ( isObject ( downloadSettings ) ) {
237+ const rawDownloadSettings = isObject ( rawExtra . downloadSettings )
238+ ? rawExtra . downloadSettings
239+ : undefined ;
240+
241+ let rawXhttpSettings = isObject ( rawDownloadSettings ?. xhttpSettings )
242+ ? rawDownloadSettings . xhttpSettings
243+ : undefined ;
244+ if (
245+ ! rawXhttpSettings &&
246+ ( downloadSettings [ 'sc-max-each-post-bytes' ] != null ||
247+ downloadSettings [ 'sc-min-posts-interval-ms' ] != null )
248+ ) {
249+ rawExtra . downloadSettings = isObject ( rawExtra . downloadSettings )
250+ ? rawExtra . downloadSettings
251+ : { } ;
252+ rawExtra . downloadSettings . xhttpSettings = { } ;
253+ rawXhttpSettings = rawExtra . downloadSettings . xhttpSettings ;
254+ didChange = true ;
255+ }
256+
257+ if ( rawXhttpSettings ) {
258+ if ( downloadSettings [ 'sc-max-each-post-bytes' ] != null ) {
259+ didChange =
260+ applyNormalizedXhttpScalar (
261+ rawXhttpSettings ,
262+ 'scMaxEachPostBytes' ,
263+ downloadSettings [ 'sc-max-each-post-bytes' ] ,
264+ ) || didChange ;
265+ }
266+ if ( downloadSettings [ 'sc-min-posts-interval-ms' ] != null ) {
267+ didChange =
268+ applyNormalizedXhttpScalar (
269+ rawXhttpSettings ,
270+ 'scMinPostsIntervalMs' ,
271+ downloadSettings [ 'sc-min-posts-interval-ms' ] ,
272+ ) || didChange ;
273+ }
274+
275+ if ( Object . keys ( rawXhttpSettings ) . length === 0 ) {
276+ delete rawExtra . downloadSettings . xhttpSettings ;
277+ didChange = true ;
278+ if ( Object . keys ( rawExtra . downloadSettings ) . length === 0 ) {
279+ delete rawExtra . downloadSettings ;
280+ }
281+ }
282+ }
283+ }
284+
285+ return didChange ? JSON . stringify ( rawExtra ) : proxy . _extra ;
286+ }
287+
147288function buildVlessExtra ( proxy ) {
148289 if ( proxy . network !== 'xhttp' ) {
149290 return proxy . _extra || '' ;
150291 }
151292
152293 if ( proxy . _extra ) {
153- return proxy . _extra ;
294+ return normalizeRawXhttpExtra ( proxy ) ;
154295 }
155296
156297 return buildStructuredVlessExtra ( proxy ) ;
0 commit comments