Skip to content

Commit 6619ba4

Browse files
committed
Merge branch 'main' into table-cell-bgcolor
2 parents 4cc98e2 + 9094a87 commit 6619ba4

7 files changed

Lines changed: 92 additions & 30 deletions

File tree

src/classes/DuplexRPCClient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,10 @@ export class DuplexRPCClient<
278278

279279
public async send<MethodName extends keyof CallerSchema>(
280280
methodName: MethodName,
281-
inputs: z.input<CallerSchema[MethodName]['inputs']>
281+
inputs: z.input<CallerSchema[MethodName]['inputs']>,
282+
options: {
283+
timeoutFactor?: number
284+
} = {}
282285
) {
283286
const id = generateId()
284287

@@ -335,7 +338,7 @@ export class DuplexRPCClient<
335338
}
336339
})
337340
} else {
338-
this.communicator.send(msg).catch(err => {
341+
this.communicator.send(msg, options).catch(err => {
339342
reject(err)
340343
})
341344
}

src/classes/ISocket.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,15 @@ export default class ISocket {
224224
* Send a `MESSAGE` containing data to the connected counterpart,
225225
* throwing an error if `ACK` is not received within `sendTimeout`.
226226
*/
227-
async send(data: string) {
227+
async send(data: string, options: { timeoutFactor?: number } = {}) {
228228
if (this.isClosed) throw new NotConnectedError()
229229

230230
return new Promise<void>((resolve, reject) => {
231231
const id = v4()
232232

233233
const failTimeout = setTimeout(() => {
234234
reject(new TimeoutError())
235-
}, this.sendTimeout)
235+
}, this.sendTimeout * (options.timeoutFactor ?? 1))
236236

237237
this.timeouts.add(failTimeout)
238238

@@ -280,8 +280,8 @@ export default class ISocket {
280280

281281
this.id = config?.id || v4()
282282
this.connectTimeout = config?.connectTimeout ?? 15_000
283-
this.sendTimeout = config?.sendTimeout ?? 3000
284-
this.pingTimeout = config?.pingTimeout ?? 3000
283+
this.sendTimeout = config?.sendTimeout ?? 5000
284+
this.pingTimeout = config?.pingTimeout ?? 5000
285285
this.isAuthenticated = false
286286

287287
this.onClose.attach(() => {

src/classes/IntervalClient.ts

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export default class IntervalClient {
113113
#completeHttpRequestDelayMs: number = 3000
114114
#completeShutdownDelayMs: number = 3000
115115
#retryIntervalMs: number = 3000
116+
#maxResendAttempts: number = 10
116117
#pingIntervalMs: number = 30_000
117118
#closeUnresponsiveConnectionTimeoutMs: number = 3 * 60 * 1000 // 3 minutes
118119
#reinitializeBatchTimeoutMs: number = 200
@@ -175,6 +176,10 @@ export default class IntervalClient {
175176
this.#completeHttpRequestDelayMs = config.completeHttpRequestDelayMs
176177
}
177178

179+
if (config.maxResendAttempts && config.maxResendAttempts > 0) {
180+
this.#maxResendAttempts = config.maxResendAttempts
181+
}
182+
178183
this.#httpEndpoint = getHttpEndpoint(this.#endpoint)
179184

180185
if (config.setHostHandlers) {
@@ -499,7 +504,8 @@ export default class IntervalClient {
499504
)
500505
: new Map(this.#pendingIOCalls)
501506

502-
while (toResend.size > 0) {
507+
let attemptNumber = 1
508+
while (toResend.size > 0 && attemptNumber <= this.#maxResendAttempts) {
503509
await Promise.allSettled(
504510
Array.from(toResend.entries()).map(([transactionId, ioCall]) =>
505511
this.#send('SEND_IO_CALL', {
@@ -534,15 +540,16 @@ export default class IntervalClient {
534540
this.#logger.debug('Failed resending pending IO call:', err)
535541
}
536542

543+
const retrySleepMs = this.#retryIntervalMs * attemptNumber
537544
this.#logger.debug(
538-
`Trying again in ${Math.round(
539-
this.#retryIntervalMs / 1000
540-
)}s...`
545+
`Trying again in ${Math.round(retrySleepMs / 1000)}s...`
541546
)
542-
await sleep(this.#retryIntervalMs)
547+
await sleep(retrySleepMs)
543548
})
544549
)
545550
)
551+
552+
attemptNumber++
546553
}
547554
}
548555

@@ -560,7 +567,8 @@ export default class IntervalClient {
560567
)
561568
: new Map(this.#pendingPageLayouts)
562569

563-
while (toResend.size > 0) {
570+
let attemptNumber = 1
571+
while (toResend.size > 0 && attemptNumber <= this.#maxResendAttempts) {
564572
await Promise.allSettled(
565573
Array.from(toResend.entries()).map(([pageKey, page]) =>
566574
this.#send('SEND_PAGE', {
@@ -595,15 +603,16 @@ export default class IntervalClient {
595603
this.#logger.debug('Failed resending pending page layout:', err)
596604
}
597605

606+
const retrySleepMs = this.#retryIntervalMs * attemptNumber
598607
this.#logger.debug(
599-
`Trying again in ${Math.round(
600-
this.#retryIntervalMs / 1000
601-
)}s...`
608+
`Trying again in ${Math.round(retrySleepMs / 1000)}s...`
602609
)
603-
await sleep(this.#retryIntervalMs)
610+
await sleep(retrySleepMs)
604611
})
605612
)
606613
)
614+
615+
attemptNumber++
607616
}
608617
}
609618

@@ -621,7 +630,8 @@ export default class IntervalClient {
621630
)
622631
: new Map(this.#transactionLoadingStates)
623632

624-
while (toResend.size > 0) {
633+
let attemptNumber = 0
634+
while (toResend.size > 0 && attemptNumber <= this.#maxResendAttempts) {
625635
await Promise.allSettled(
626636
Array.from(toResend.entries()).map(([transactionId, loadingState]) =>
627637
this.#send('SEND_LOADING_CALL', {
@@ -657,15 +667,16 @@ export default class IntervalClient {
657667
this.#logger.debug('Failed resending pending IO call:', err)
658668
}
659669

670+
const retrySleepMs = this.#retryIntervalMs * attemptNumber
660671
this.#logger.debug(
661-
`Trying again in ${Math.round(
662-
this.#retryIntervalMs / 1000
663-
)}s...`
672+
`Trying again in ${Math.round(retrySleepMs / 1000)}s...`
664673
)
665-
await sleep(this.#retryIntervalMs)
674+
await sleep(retrySleepMs)
666675
})
667676
)
668677
)
678+
679+
attemptNumber++
669680
}
670681
}
671682

@@ -2003,27 +2014,41 @@ export default class IntervalClient {
20032014
this.#logger.debug('Error from peer RPC', err)
20042015
}
20052016

2006-
while (true) {
2017+
for (
2018+
let attemptNumber = 1;
2019+
attemptNumber <= this.#maxResendAttempts;
2020+
attemptNumber++
2021+
) {
20072022
try {
20082023
this.#logger.debug('Sending via server', methodName, inputs)
2009-
return await this.#serverRpc.send(methodName, {
2010-
...inputs,
2011-
skipClientCall,
2012-
})
2024+
return await this.#serverRpc.send(
2025+
methodName,
2026+
{
2027+
...inputs,
2028+
skipClientCall,
2029+
},
2030+
{
2031+
timeoutFactor: attemptNumber,
2032+
}
2033+
)
20132034
} catch (err) {
2035+
const sleepTimeBeforeRetrying = this.#retryIntervalMs * attemptNumber
2036+
20142037
if (err instanceof TimeoutError) {
20152038
this.#log.debug(
20162039
`RPC call timed out, retrying in ${Math.round(
2017-
this.#retryIntervalMs / 1000
2040+
sleepTimeBeforeRetrying / 1000
20182041
)}s...`
20192042
)
20202043
this.#log.debug(err)
2021-
sleep(this.#retryIntervalMs)
2044+
sleep(sleepTimeBeforeRetrying)
20222045
} else {
20232046
throw err
20242047
}
20252048
}
20262049
}
2050+
2051+
throw new IntervalError('Maximum failed resend attempts reached, aborting.')
20272052
}
20282053

20292054
/**

src/examples/basic/index.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,35 @@ const empty_page = new Page({
114114
},
115115
routes: {
116116
child_action: new Action(async () => {
117-
return 'Hello!'
117+
await io.group([
118+
io.display.link('Go to unlisted action', {
119+
route: 'empty_page/unlisted_action',
120+
theme: 'secondary',
121+
}),
122+
io.display.link('Go to unlisted page', {
123+
route: 'empty_page/unlisted_page',
124+
theme: 'secondary',
125+
}),
126+
])
127+
}),
128+
unlisted_action: new Action({
129+
unlisted: true,
130+
handler: async () => {
131+
return 'Hello!'
132+
},
133+
}),
134+
unlisted_page: new Page({
135+
name: 'Unlisted page',
136+
unlisted: true,
137+
handler: async () => {
138+
return new Layout({
139+
children: [
140+
io.display.markdown(
141+
'This page is unlisted, but you can still access it!'
142+
),
143+
],
144+
})
145+
},
118146
}),
119147
show_layout: new Action(async () => {
120148
ctx.redirect({ route: 'empty_page', params: { show_layout: 1 } })

src/examples/basic/table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export const multiple_tables: IntervalActionHandler = async io => {
227227
export const big_payload_table = new Page({
228228
name: 'Big table',
229229
handler: async () => {
230-
const bigData = generateRows(100000)
230+
const bigData = generateRows(10_000)
231231

232232
return new Layout({
233233
children: [

src/examples/utils/helpers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ export function generateRows(count: number, offset = 0) {
100100
~~~`,
101101
]),
102102
number: faker.datatype.number(100),
103+
...Object.fromEntries(
104+
Array(50)
105+
.fill(null)
106+
.map((_, i) => [`text_${i}`, faker.lorem.paragraph()])
107+
),
103108
boolean: faker.datatype.boolean(),
104109
date: faker.datatype.datetime(),
105110
image: faker.image.imageUrl(

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface InternalConfig {
6464
connectTimeoutMs?: number
6565
sendTimeoutMs?: number
6666
pingTimeoutMs?: number
67+
maxResendAttempts?: number
6768
completeHttpRequestDelayMs?: number
6869

6970
closeUnresponsiveConnectionTimeoutMs?: number

0 commit comments

Comments
 (0)