@remit/web-client 0.0.114 → 0.0.115
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.115",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -236,6 +236,28 @@ describe("SelfUpdateOverlay — the blocking screen", () => {
|
|
|
236
236
|
assert.match(dom.html(), /has not answered since the restart/);
|
|
237
237
|
assert.match(dom.html(), /remit logs/);
|
|
238
238
|
});
|
|
239
|
+
|
|
240
|
+
test("a poll that never answers gives up at the budget too", async () => {
|
|
241
|
+
saveHeldRun({
|
|
242
|
+
runId: "upd_1",
|
|
243
|
+
attemptedVersion: "0.9.4",
|
|
244
|
+
previousVersion: "0.9.3",
|
|
245
|
+
startedAt: Date.now() - 60 * 60_000,
|
|
246
|
+
});
|
|
247
|
+
http = mockFetch(() => new Promise(() => {}));
|
|
248
|
+
harness = createDomHarness();
|
|
249
|
+
harness.renderApp(
|
|
250
|
+
createElement(SelfUpdateProvider, null, createElement(SelfUpdateOverlay)),
|
|
251
|
+
);
|
|
252
|
+
await harness.flush();
|
|
253
|
+
await harness.wait(1);
|
|
254
|
+
await harness.flush();
|
|
255
|
+
|
|
256
|
+
assert.doesNotMatch(harness.html(), /Installing Remit 0\.9\.4/);
|
|
257
|
+
assert.match(harness.html(), /has not answered since the restart/);
|
|
258
|
+
assert.match(harness.html(), /remit logs/);
|
|
259
|
+
assert.equal(loadHeldRun(), null);
|
|
260
|
+
});
|
|
239
261
|
});
|
|
240
262
|
|
|
241
263
|
describe("useSystemUpdate — actions", () => {
|
|
@@ -470,6 +470,30 @@ describe("deriveUpdateSurface — a held run across a restart", () => {
|
|
|
470
470
|
assert.equal(result.releaseHeld, true);
|
|
471
471
|
});
|
|
472
472
|
|
|
473
|
+
test("a first poll still in flight keeps applying inside the budget", () => {
|
|
474
|
+
const result = deriveUpdateSurface(
|
|
475
|
+
input({ held: held({ startedAt: NOW - 20_000 }) }),
|
|
476
|
+
);
|
|
477
|
+
if (
|
|
478
|
+
result.surface.status !== "ready" ||
|
|
479
|
+
result.surface.overlay.kind !== "applying"
|
|
480
|
+
) {
|
|
481
|
+
assert.fail("expected the applying overlay");
|
|
482
|
+
}
|
|
483
|
+
assert.equal(result.surface.overlay.phase, "preparing");
|
|
484
|
+
assert.equal(result.clearStoredRun, false);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("a poll that never answers gives up at the budget", () => {
|
|
488
|
+
const result = deriveUpdateSurface(
|
|
489
|
+
input({ held: held({ startedAt: NOW - BUDGET_MS - 60_000 }) }),
|
|
490
|
+
);
|
|
491
|
+
assert.equal(result.surface.status, "ready");
|
|
492
|
+
if (result.surface.status !== "ready") return;
|
|
493
|
+
assert.equal(result.surface.overlay.kind, "neverCameBack");
|
|
494
|
+
assert.equal(result.clearStoredRun, true);
|
|
495
|
+
});
|
|
496
|
+
|
|
473
497
|
test("an early server answer with no run yet keeps applying", () => {
|
|
474
498
|
const result = deriveUpdateSurface(
|
|
475
499
|
input({ data: response({ run: null }), held: held() }),
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* The design constraints (RFC 037 Interface, issue #135) live here:
|
|
11
11
|
* - A held run id turns a failed request into `applying`, never `unreachable`.
|
|
12
|
-
* - Once the apply budget plus a margin has passed with no answer
|
|
13
|
-
*
|
|
14
|
-
* the rollback ran, because from a
|
|
12
|
+
* - Once the apply budget plus a margin has passed with no answer — a failed
|
|
13
|
+
* request or one still in flight — that silence becomes "the server never
|
|
14
|
+
* came back", a state that never claims the rollback ran, because from a
|
|
15
|
+
* dead connection the client cannot know.
|
|
15
16
|
* - The check block and the run block are independent: a check that cannot
|
|
16
17
|
* reach the update source is a failed check, never a failed update.
|
|
17
18
|
*/
|
|
@@ -325,6 +326,35 @@ function ready(
|
|
|
325
326
|
};
|
|
326
327
|
}
|
|
327
328
|
|
|
329
|
+
/**
|
|
330
|
+
* The client gave up waiting. The stored token goes so a reload cannot resume
|
|
331
|
+
* the same dead wait, while the in-memory hold stays: the screen has to sit
|
|
332
|
+
* still, and its retry has to keep polling, until the server answers for itself.
|
|
333
|
+
*/
|
|
334
|
+
function neverCameBack(held: HeldRun, elapsedSeconds: number): DeriveResult {
|
|
335
|
+
return {
|
|
336
|
+
surface: {
|
|
337
|
+
status: "ready",
|
|
338
|
+
section: applyingSection(
|
|
339
|
+
held.runId,
|
|
340
|
+
held.previousVersion,
|
|
341
|
+
held.attemptedVersion,
|
|
342
|
+
"reconnecting",
|
|
343
|
+
elapsedSeconds,
|
|
344
|
+
),
|
|
345
|
+
overlay: {
|
|
346
|
+
kind: "neverCameBack",
|
|
347
|
+
attemptedVersion: held.attemptedVersion,
|
|
348
|
+
previousVersion: held.previousVersion,
|
|
349
|
+
elapsedSeconds,
|
|
350
|
+
logsCommand: FALLBACK_LOGS_COMMAND,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
clearStoredRun: true,
|
|
354
|
+
releaseHeld: false,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
328
358
|
/**
|
|
329
359
|
* A held run resolves to one of: still applying, gave up ("never came back"),
|
|
330
360
|
* recovered but unaccountable, or — returning `null` — resolved terminally on
|
|
@@ -360,32 +390,17 @@ function deriveHeld(
|
|
|
360
390
|
}
|
|
361
391
|
|
|
362
392
|
if (isError) {
|
|
363
|
-
const section = applyingSection(
|
|
364
|
-
held.runId,
|
|
365
|
-
held.previousVersion,
|
|
366
|
-
held.attemptedVersion,
|
|
367
|
-
"reconnecting",
|
|
368
|
-
elapsedSeconds,
|
|
369
|
-
);
|
|
370
393
|
if (elapsedSeconds > budgetLimitSeconds()) {
|
|
371
|
-
return
|
|
372
|
-
surface: {
|
|
373
|
-
status: "ready",
|
|
374
|
-
section,
|
|
375
|
-
overlay: {
|
|
376
|
-
kind: "neverCameBack",
|
|
377
|
-
attemptedVersion: held.attemptedVersion,
|
|
378
|
-
previousVersion: held.previousVersion,
|
|
379
|
-
elapsedSeconds,
|
|
380
|
-
logsCommand: FALLBACK_LOGS_COMMAND,
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
clearStoredRun: true,
|
|
384
|
-
releaseHeld: false,
|
|
385
|
-
};
|
|
394
|
+
return neverCameBack(held, elapsedSeconds);
|
|
386
395
|
}
|
|
387
396
|
return ready(
|
|
388
|
-
|
|
397
|
+
applyingSection(
|
|
398
|
+
held.runId,
|
|
399
|
+
held.previousVersion,
|
|
400
|
+
held.attemptedVersion,
|
|
401
|
+
"reconnecting",
|
|
402
|
+
elapsedSeconds,
|
|
403
|
+
),
|
|
389
404
|
{
|
|
390
405
|
kind: "applying",
|
|
391
406
|
target: held.attemptedVersion,
|
|
@@ -397,9 +412,14 @@ function deriveHeld(
|
|
|
397
412
|
);
|
|
398
413
|
}
|
|
399
414
|
|
|
400
|
-
// No answer yet — the resume request is still in flight.
|
|
401
|
-
//
|
|
415
|
+
// No answer yet — the resume request is still in flight. A request that is
|
|
416
|
+
// pending looks exactly like one that will never settle, so the budget bounds
|
|
417
|
+
// this wait too: within it the client stays applying, past it it stops
|
|
418
|
+
// claiming an install is running and says what it cannot account for.
|
|
402
419
|
if (data === undefined) {
|
|
420
|
+
if (elapsedSeconds > budgetLimitSeconds()) {
|
|
421
|
+
return neverCameBack(held, elapsedSeconds);
|
|
422
|
+
}
|
|
403
423
|
return ready(
|
|
404
424
|
applyingSection(
|
|
405
425
|
held.runId,
|
package/src/test-support/http.ts
CHANGED
|
@@ -22,7 +22,8 @@ type Responder = (call: HttpCall) => unknown;
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Answer every request with `responder`'s return value as JSON. Throwing from
|
|
25
|
-
* the responder, or returning a `Response`, is how a test drives a failure
|
|
25
|
+
* the responder, or returning a `Response`, is how a test drives a failure;
|
|
26
|
+
* returning a promise that never settles is how it drives a request that hangs.
|
|
26
27
|
*/
|
|
27
28
|
export const mockFetch = (responder: Responder = () => ({})): HttpMock => {
|
|
28
29
|
const original = globalThis.fetch;
|
|
@@ -43,7 +44,7 @@ export const mockFetch = (responder: Responder = () => ({})): HttpMock => {
|
|
|
43
44
|
};
|
|
44
45
|
calls.push(call);
|
|
45
46
|
|
|
46
|
-
const result = responder(call);
|
|
47
|
+
const result = await responder(call);
|
|
47
48
|
if (result instanceof Response) return result;
|
|
48
49
|
return new Response(JSON.stringify(result ?? {}), {
|
|
49
50
|
status: 200,
|