@@ -35,6 +35,16 @@ export const setGlobalStore = (store) => {
35
35
export const backendUpdate = createAction ( 'backend/update' ) ;
36
36
export const backendSetSharedState = createAction ( 'backend/setSharedState' ) ;
37
37
export const backendSuspendStart = createAction ( 'backend/suspendStart' ) ;
38
+ export const backendCreatePayloadQueue = createAction (
39
+ 'backend/createPayloadQueue' ,
40
+ ) ;
41
+ export const backendDequeuePayloadQueue = createAction (
42
+ 'backend/dequeuePayloadQueue' ,
43
+ ) ;
44
+ export const backendRemovePayloadQueue = createAction (
45
+ 'backend/removePayloadQueue' ,
46
+ ) ;
47
+ export const nextPayloadChunk = createAction ( 'nextPayloadChunk' ) ;
38
48
39
49
export const backendSuspendSuccess = ( ) => ( {
40
50
type : 'backend/suspendSuccess' ,
@@ -47,6 +57,7 @@ const initialState = {
47
57
config : { } ,
48
58
data : { } ,
49
59
shared : { } ,
60
+ outgoingPayloadQueues : { } as Record < string , string [ ] > ,
50
61
// Start as suspended
51
62
suspended : Date . now ( ) ,
52
63
suspending : false ,
@@ -123,6 +134,44 @@ export const backendReducer = (state = initialState, action) => {
123
134
} ;
124
135
}
125
136
137
+ if ( type === 'backend/createPayloadQueue' ) {
138
+ const { id, chunks } = payload ;
139
+ const { outgoingPayloadQueues } = state ;
140
+ return {
141
+ ...state ,
142
+ outgoingPayloadQueues : {
143
+ ...outgoingPayloadQueues ,
144
+ [ id ] : chunks ,
145
+ } ,
146
+ } ;
147
+ }
148
+
149
+ if ( type === 'backend/dequeuePayloadQueue' ) {
150
+ const { id } = payload ;
151
+ const { outgoingPayloadQueues } = state ;
152
+ const { [ id ] : targetQueue , ...otherQueues } = outgoingPayloadQueues ;
153
+ const [ _ , ...rest ] = targetQueue ;
154
+ return {
155
+ ...state ,
156
+ outgoingPayloadQueues : rest . length
157
+ ? {
158
+ ...otherQueues ,
159
+ [ id ] : rest ,
160
+ }
161
+ : otherQueues ,
162
+ } ;
163
+ }
164
+
165
+ if ( type === 'backend/removePayloadQueue' ) {
166
+ const { id } = payload ;
167
+ const { outgoingPayloadQueues } = state ;
168
+ const { [ id ] : _ , ...otherQueues } = outgoingPayloadQueues ;
169
+ return {
170
+ ...state ,
171
+ outgoingPayloadQueues : otherQueues ,
172
+ } ;
173
+ }
174
+
126
175
return state ;
127
176
} ;
128
177
@@ -131,7 +180,9 @@ export const backendMiddleware = (store) => {
131
180
let suspendInterval ;
132
181
133
182
return ( next ) => ( action ) => {
134
- const { suspended } = selectBackend ( store . getState ( ) ) ;
183
+ const { suspended, outgoingPayloadQueues } = selectBackend (
184
+ store . getState ( ) ,
185
+ ) ;
135
186
const { type, payload } = action ;
136
187
137
188
if ( type === 'update' ) {
@@ -234,10 +285,59 @@ export const backendMiddleware = (store) => {
234
285
} ) ;
235
286
}
236
287
288
+ if ( type === 'oversizePayloadResponse' ) {
289
+ const { allow } = payload ;
290
+ if ( allow ) {
291
+ store . dispatch ( nextPayloadChunk ( payload ) ) ;
292
+ } else {
293
+ store . dispatch ( backendRemovePayloadQueue ( payload ) ) ;
294
+ }
295
+ }
296
+
297
+ if ( type === 'acknowlegePayloadChunk' ) {
298
+ store . dispatch ( backendDequeuePayloadQueue ( payload ) ) ;
299
+ store . dispatch ( nextPayloadChunk ( payload ) ) ;
300
+ }
301
+
302
+ if ( type === 'nextPayloadChunk' ) {
303
+ const { id } = payload ;
304
+ const chunk = outgoingPayloadQueues [ id ] [ 0 ] ;
305
+ Byond . sendMessage ( 'payloadChunk' , {
306
+ id,
307
+ chunk,
308
+ } ) ;
309
+ }
310
+
237
311
return next ( action ) ;
238
312
} ;
239
313
} ;
240
314
315
+ const chunkSplitter = {
316
+ [ Symbol . split ] : ( string : string ) => {
317
+ let chunks : string [ ] = [ ] ;
318
+ const uriEncoded = encodeURIComponent ( string ) ;
319
+ let startIndex = 0 ;
320
+ let endIndex = 1024 ;
321
+ while ( startIndex < uriEncoded . length ) {
322
+ const lastFoundPercent = uriEncoded . lastIndexOf ( '%' , endIndex - 1 ) ;
323
+ if ( lastFoundPercent > endIndex - 3 ) {
324
+ endIndex = lastFoundPercent + 3 ;
325
+ }
326
+ chunks . push (
327
+ decodeURIComponent (
328
+ uriEncoded . substring (
329
+ startIndex ,
330
+ endIndex < uriEncoded . length ? endIndex : undefined ,
331
+ ) ,
332
+ ) ,
333
+ ) ;
334
+ startIndex = endIndex ;
335
+ endIndex += 1024 ;
336
+ }
337
+ return chunks ;
338
+ } ,
339
+ } ;
340
+
241
341
/**
242
342
* Sends an action to `ui_act` on `src_object` that this tgui window
243
343
* is associated with.
@@ -252,6 +352,31 @@ export const sendAct = (action: string, payload: object = {}) => {
252
352
logger . error ( `Payload for act() must be an object, got this:` , payload ) ;
253
353
return ;
254
354
}
355
+ if ( ! Byond . TRIDENT ) {
356
+ const stringifiedPayload = JSON . stringify ( payload ) ;
357
+ const urlSize = Object . entries ( {
358
+ type : 'act/' + action ,
359
+ payload : stringifiedPayload ,
360
+ tgui : 1 ,
361
+ windowId : Byond . windowId ,
362
+ } ) . reduce (
363
+ ( url , [ key , value ] , i ) =>
364
+ url +
365
+ `${ i > 0 ? '&' : '?' } ${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` ,
366
+ '' ,
367
+ ) . length ;
368
+ if ( urlSize > 2048 ) {
369
+ let chunks : string [ ] = stringifiedPayload . split ( chunkSplitter ) ;
370
+ const id = `${ Date . now ( ) } ` ;
371
+ globalStore ?. dispatch ( backendCreatePayloadQueue ( { id, chunks } ) ) ;
372
+ Byond . sendMessage ( 'oversizedPayloadRequest' , {
373
+ type : 'act/' + action ,
374
+ id,
375
+ chunkCount : chunks . length ,
376
+ } ) ;
377
+ return ;
378
+ }
379
+ }
255
380
Byond . sendMessage ( 'act/' + action , payload ) ;
256
381
} ;
257
382
@@ -279,6 +404,7 @@ type BackendState<TData> = {
279
404
} ;
280
405
data : TData ;
281
406
shared : Record < string , any > ;
407
+ outgoingPayloadQueues : Record < string , any [ ] > ;
282
408
suspending : boolean ;
283
409
suspended : boolean ;
284
410
debug ?: {
0 commit comments