@linxin666/dsh-remote-web-ui 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +8 -1
- package/README.zh.md +2 -1
- package/lib/client.js +55 -10
- package/lib/client.js.map +1 -1
- package/lib/index.js +12 -8
- package/lib/mobile.js +37 -18
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/FenceNotice.d.ts +3 -2
- package/lib/types/client/FenceNotice.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +8 -8
- package/lib/types/client/RemotePanel.js +3 -3
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +2 -2
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +2 -2
- package/lib/types/client/UpdatePanel.js +1 -1
- package/lib/types/client/deep-link.js +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +45 -23
- package/lib/types/client/pair-api.d.ts +6 -0
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +11 -0
- package/lib/types/client/remote-channel.d.ts +25 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +24 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/update-api.d.ts.map +1 -1
- package/lib/types/gate.js +1 -1
- package/lib/types/index.js +15 -15
- package/lib/types/invariant.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +1 -1
- package/lib/types/mobile/PairRequiredView.js +1 -1
- package/lib/types/mobile/PairRequiredView.test.js +1 -1
- package/lib/types/mobile/api.js +1 -1
- package/lib/types/mobile/index.js +5 -5
- package/lib/types/mobile/markdown.test.js +1 -1
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +5 -3
- package/lib/types/mobile/messages.test.js +21 -1
- package/lib/types/mobile/mobile-theme.test.js +2 -2
- package/lib/types/mobile/mux.d.ts +7 -4
- package/lib/types/mobile/mux.d.ts.map +1 -1
- package/lib/types/mobile/mux.js +44 -16
- package/lib/types/mobile/mux.test.js +58 -1
- package/lib/types/mobile/rpc.d.ts.map +1 -1
- package/lib/types/mobile/theme-toggle.js +1 -1
- package/lib/types/mobile/views/App.js +7 -7
- package/lib/types/mobile/views/App.test.js +2 -2
- package/lib/types/mobile/views/ChatView.js +6 -6
- package/lib/types/mobile/views/ChatView.test.js +3 -3
- package/lib/types/mobile/views/SessionListView.js +3 -3
- package/lib/types/mobile/views/SessionListView.test.js +2 -2
- package/lib/types/mobile/views/WorkspaceView.js +3 -3
- package/lib/types/mobile/views/WorkspaceView.test.js +3 -3
- package/lib/types/mobile-api.d.ts +1 -1
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +2 -2
- package/lib/types/pairing-access.d.ts.map +1 -1
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.persist.test.js +2 -2
- package/lib/types/remote-api.js +6 -6
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/routes.d.ts +9 -7
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +8 -5
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/update-routes.d.ts +2 -2
- package/lib/types/update-routes.d.ts.map +1 -1
- package/lib/types/update-routes.js +1 -1
- package/package.json +15 -12
- package/src/client/FenceNotice.tsx +3 -2
- package/src/client/RemoteEntry.tsx +3 -2
- package/src/client/UpdateEntry.tsx +1 -0
- package/src/client/index.ts +40 -15
- package/src/client/pair-api.ts +16 -0
- package/src/client/remote-channel.ts +42 -0
- package/src/index.ts +1 -1
- package/src/mobile/messages.test.ts +23 -0
- package/src/mobile/messages.ts +5 -3
- package/src/mobile/mux.test.ts +64 -0
- package/src/mobile/mux.ts +44 -17
- package/src/routes.ts +7 -2
package/src/mobile/mux.ts
CHANGED
|
@@ -39,9 +39,9 @@ export interface MuxClientOptions {
|
|
|
39
39
|
* the ordinary HTTP channel (unaffected by SSE-impairing tunnels).
|
|
40
40
|
*/
|
|
41
41
|
pollLatest?: (sessionId: string) => Promise<HistoryPage>
|
|
42
|
-
/**
|
|
42
|
+
/** Initial poll cadence while SSE is stalled (default 3000 ms). Empty polls back off to 60000 ms. */
|
|
43
43
|
pollIntervalMs?: number
|
|
44
|
-
/**
|
|
44
|
+
/** Initial SSE stall window (default 12000 ms); a previously-live stream gets three windows before fallback. */
|
|
45
45
|
stallThresholdMs?: number
|
|
46
46
|
/** Clock seam for tests (defaults to Date.now). */
|
|
47
47
|
now?: () => number
|
|
@@ -66,6 +66,8 @@ type SessionEventFrame = Extract<MuxFrame, { type: 'session/event' }>
|
|
|
66
66
|
|
|
67
67
|
const DEFAULT_POLL_INTERVAL_MS = 3000
|
|
68
68
|
const DEFAULT_STALL_THRESHOLD_MS = 12000
|
|
69
|
+
const LIVE_SSE_STALL_MULTIPLIER = 3
|
|
70
|
+
const MAX_POLL_BACKOFF_MS = 60000
|
|
69
71
|
/**
|
|
70
72
|
* Stall-check granularity: the single scheduler tick runs at least this
|
|
71
73
|
* often so fallback arms within a second of the stall threshold passing,
|
|
@@ -98,8 +100,8 @@ export class MuxClient {
|
|
|
98
100
|
private lastDataAt = 0
|
|
99
101
|
/**
|
|
100
102
|
* Whether the SSE channel has ever delivered a frame in this stream (a
|
|
101
|
-
* delivered frame proves the tunnel
|
|
102
|
-
*
|
|
103
|
+
* delivered frame proves the tunnel can forward SSE; sustained silence may
|
|
104
|
+
* still mean a suspended mobile tunnel, so polling re-arms after 3 windows).
|
|
103
105
|
*/
|
|
104
106
|
private sseAlive = false
|
|
105
107
|
/** Per-session highest event seq already emitted, for poll dedup. */
|
|
@@ -109,6 +111,8 @@ export class MuxClient {
|
|
|
109
111
|
private polling = false
|
|
110
112
|
/** Epoch ms of the next due poll while polling (kept on the same tick timer). */
|
|
111
113
|
private nextPollAt = 0
|
|
114
|
+
/** Adaptive delay: productive polls reset it; empty/error polls add one base interval up to one minute. */
|
|
115
|
+
private pollDelayMs: number
|
|
112
116
|
|
|
113
117
|
/**
|
|
114
118
|
* @param url - the mobile events endpoint (browser-relative).
|
|
@@ -119,6 +123,7 @@ export class MuxClient {
|
|
|
119
123
|
this.sourceFactory = options.sourceFactory ?? browserSource
|
|
120
124
|
this.pollLatest = options.pollLatest ?? ((sessionId) => fetchHistory(sessionId, undefined, DEFAULT_POLL_PAGE_SIZE))
|
|
121
125
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS
|
|
126
|
+
this.pollDelayMs = this.pollIntervalMs
|
|
122
127
|
this.stallThresholdMs = options.stallThresholdMs ?? DEFAULT_STALL_THRESHOLD_MS
|
|
123
128
|
this.now = options.now ?? (() => Date.now())
|
|
124
129
|
}
|
|
@@ -159,9 +164,7 @@ export class MuxClient {
|
|
|
159
164
|
return
|
|
160
165
|
}
|
|
161
166
|
// If SSE is already stalled for this session, start patching right away.
|
|
162
|
-
if (!this.polling && !this.stopped &&
|
|
163
|
-
this.startPolling()
|
|
164
|
-
}
|
|
167
|
+
if (!this.polling && !this.stopped && this.isSseStalled()) this.startPolling()
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
private connect(): void {
|
|
@@ -208,29 +211,37 @@ export class MuxClient {
|
|
|
208
211
|
if (this.stopped) return
|
|
209
212
|
if (this.observeSessionId === undefined) return
|
|
210
213
|
if (this.polling) {
|
|
211
|
-
// The stall phase has ended; the same tick now paces the polls.
|
|
214
|
+
// The stall phase has ended; the same tick now paces the adaptive polls.
|
|
212
215
|
if (this.now() >= this.nextPollAt) {
|
|
213
|
-
|
|
216
|
+
// pollTick schedules the next run after it settles, so slow requests
|
|
217
|
+
// cannot overlap with another scheduler tick.
|
|
218
|
+
this.nextPollAt = Number.POSITIVE_INFINITY
|
|
214
219
|
void this.pollTick()
|
|
215
220
|
}
|
|
216
221
|
return
|
|
217
222
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
if (this.isSseStalled()) this.startPolling()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private isSseStalled(): boolean {
|
|
227
|
+
const windowMs = this.sseAlive
|
|
228
|
+
? this.stallThresholdMs * LIVE_SSE_STALL_MULTIPLIER
|
|
229
|
+
: this.stallThresholdMs
|
|
230
|
+
return (this.now() - this.lastDataAt) > windowMs
|
|
223
231
|
}
|
|
224
232
|
|
|
225
233
|
private startPolling(): void {
|
|
226
234
|
if (this.polling || this.stopped) return
|
|
227
235
|
this.polling = true
|
|
228
|
-
this.
|
|
236
|
+
this.pollDelayMs = this.pollIntervalMs
|
|
237
|
+
this.nextPollAt = Number.POSITIVE_INFINITY
|
|
229
238
|
void this.pollTick()
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
private stopPolling(): void {
|
|
233
242
|
this.polling = false
|
|
243
|
+
this.pollDelayMs = this.pollIntervalMs
|
|
244
|
+
this.nextPollAt = 0
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
/**
|
|
@@ -244,19 +255,35 @@ export class MuxClient {
|
|
|
244
255
|
this.stopPolling()
|
|
245
256
|
return
|
|
246
257
|
}
|
|
258
|
+
let emitted = 0
|
|
247
259
|
try {
|
|
248
260
|
const page = await this.pollLatest(sessionId)
|
|
249
261
|
let maxSeq = this.pollWatermark.get(sessionId) ?? -1
|
|
250
|
-
|
|
262
|
+
const ordered = [...page.events].sort((left, right) => {
|
|
263
|
+
const leftSeq = typeof left.event?.seq === 'number' ? left.event.seq : -1
|
|
264
|
+
const rightSeq = typeof right.event?.seq === 'number' ? right.event.seq : -1
|
|
265
|
+
return leftSeq - rightSeq
|
|
266
|
+
})
|
|
267
|
+
for (const entry of ordered) {
|
|
251
268
|
const event = entry.event
|
|
252
269
|
const seq = typeof event?.seq === 'number' ? event.seq : -1
|
|
253
270
|
if (seq <= maxSeq) continue
|
|
254
271
|
maxSeq = seq
|
|
272
|
+
emitted += 1
|
|
255
273
|
this.emit({ type: 'session/event', sessionId: sessionId as SessionEventFrame['sessionId'], event } as SessionEventFrame)
|
|
256
274
|
}
|
|
257
275
|
this.pollWatermark.set(sessionId, maxSeq)
|
|
258
276
|
} catch {
|
|
259
|
-
// Transient (network, pairing, history paging);
|
|
277
|
+
// Transient (network, pairing, history paging); retry with backoff.
|
|
278
|
+
} finally {
|
|
279
|
+
if (emitted > 0) {
|
|
280
|
+
this.pollDelayMs = this.pollIntervalMs
|
|
281
|
+
} else {
|
|
282
|
+
this.pollDelayMs = Math.min(MAX_POLL_BACKOFF_MS, this.pollDelayMs + this.pollIntervalMs)
|
|
283
|
+
}
|
|
284
|
+
if (this.polling && this.observeSessionId === sessionId) {
|
|
285
|
+
this.nextPollAt = this.now() + this.pollDelayMs
|
|
286
|
+
}
|
|
260
287
|
}
|
|
261
288
|
}
|
|
262
289
|
|
package/src/routes.ts
CHANGED
|
@@ -195,6 +195,8 @@ export interface PairRoutesDeps {
|
|
|
195
195
|
service: PairingService
|
|
196
196
|
/** The LAN IP literals the fence accepts (derived from the bind host). */
|
|
197
197
|
lanAddresses: readonly string[]
|
|
198
|
+
/** Current desktop gate policy, re-read for every status response. */
|
|
199
|
+
requirePairingForLan?: boolean | (() => boolean)
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
/**
|
|
@@ -203,7 +205,10 @@ export interface PairRoutesDeps {
|
|
|
203
205
|
* @returns the exact routes to register on webServer.
|
|
204
206
|
*/
|
|
205
207
|
export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
|
|
206
|
-
const { service, lanAddresses } = deps
|
|
208
|
+
const { service, lanAddresses, requirePairingForLan = true } = deps
|
|
209
|
+
const pairingRequired = (): boolean => typeof requirePairingForLan === 'function'
|
|
210
|
+
? requirePairingForLan()
|
|
211
|
+
: requirePairingForLan
|
|
207
212
|
const events = new PairingEventsStream(service)
|
|
208
213
|
|
|
209
214
|
/** Loopback-only fence: the desktop panel's control endpoints. */
|
|
@@ -404,7 +409,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
|
|
|
404
409
|
const visible = paired
|
|
405
410
|
? rest
|
|
406
411
|
: { phase: snapshot.phase, lanAvailable: snapshot.lanAvailable, lanAddresses: snapshot.lanAddresses }
|
|
407
|
-
writeJson(res, 200, { ok: true, paired, ...visible })
|
|
412
|
+
writeJson(res, 200, { ok: true, paired, requirePairingForLan: pairingRequired(), ...visible })
|
|
408
413
|
}
|
|
409
414
|
|
|
410
415
|
const handleEvents = (req: IncomingMessage, res: ServerResponse): void => {
|