livedesk 0.1.607 → 0.1.609

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.
@@ -0,0 +1,153 @@
1
+ const DEFAULT_RETRY_DELAY_MS = 1_000;
2
+
3
+ function normalizeDelay(value, fallback = 0) {
4
+ const milliseconds = Number(value);
5
+ return Number.isFinite(milliseconds) && milliseconds >= 0
6
+ ? Math.round(milliseconds)
7
+ : fallback;
8
+ }
9
+
10
+ /**
11
+ * Owns the one retry timer and one in-flight load attempt for a BrowserWindow.
12
+ * A successfully mounted runtime document is never reloaded by start(). Only a
13
+ * real main-frame failure or renderer termination opens a new generation.
14
+ */
15
+ export function createRuntimeWindowLoadOwner({
16
+ probeRuntime,
17
+ showRecovery = async () => undefined,
18
+ loadRuntime,
19
+ onLoaded = () => undefined,
20
+ onAttemptError = () => undefined,
21
+ retryDelayMs = DEFAULT_RETRY_DELAY_MS,
22
+ setTimer = setTimeout,
23
+ clearTimer = clearTimeout
24
+ } = {}) {
25
+ if (typeof probeRuntime !== 'function') throw new Error('runtime-window-probe-required');
26
+ if (typeof loadRuntime !== 'function') throw new Error('runtime-window-loader-required');
27
+
28
+ const retryDelay = normalizeDelay(retryDelayMs, DEFAULT_RETRY_DELAY_MS);
29
+ let generation = 1;
30
+ let stopped = false;
31
+ let loaded = false;
32
+ let recoveryVisible = false;
33
+ let timer = null;
34
+ let operation = null;
35
+ let attemptCount = 0;
36
+ let pendingReason = 'created';
37
+ let pendingRetryDelay = retryDelay;
38
+
39
+ const snapshot = () => ({
40
+ generation,
41
+ stopped,
42
+ loaded,
43
+ recoveryVisible,
44
+ retryScheduled: timer !== null,
45
+ attemptPending: operation !== null,
46
+ attemptCount,
47
+ pendingReason
48
+ });
49
+
50
+ const reportAttemptError = (error, context) => {
51
+ try {
52
+ onAttemptError(error, context);
53
+ } catch {
54
+ // Diagnostics cannot take ownership from the load recovery path.
55
+ }
56
+ };
57
+
58
+ const ensureRecoveryVisible = async context => {
59
+ if (stopped || loaded || recoveryVisible || context.generation !== generation) return;
60
+ try {
61
+ await showRecovery(context);
62
+ if (!stopped && !loaded && context.generation === generation) recoveryVisible = true;
63
+ } catch (error) {
64
+ reportAttemptError(error, { ...context, phase: 'recovery-page' });
65
+ }
66
+ };
67
+
68
+ const schedule = (reason = 'retry', delayMs = 0) => {
69
+ if (stopped || loaded || timer !== null || operation !== null) return false;
70
+ pendingReason = String(reason || 'retry');
71
+ const ownerGeneration = generation;
72
+ timer = setTimer(() => {
73
+ timer = null;
74
+ if (stopped || loaded || ownerGeneration !== generation) return;
75
+ const context = {
76
+ generation: ownerGeneration,
77
+ attempt: ++attemptCount,
78
+ reason: pendingReason
79
+ };
80
+ const currentOperation = (async () => {
81
+ const ready = await probeRuntime(context);
82
+ if (stopped || loaded || ownerGeneration !== generation) return;
83
+ if (!ready) {
84
+ await ensureRecoveryVisible({ ...context, phase: 'runtime-wait' });
85
+ return;
86
+ }
87
+ await loadRuntime(context);
88
+ if (stopped || loaded || ownerGeneration !== generation) return;
89
+ loaded = true;
90
+ recoveryVisible = false;
91
+ try {
92
+ onLoaded(context);
93
+ } catch (error) {
94
+ reportAttemptError(error, { ...context, phase: 'loaded-callback' });
95
+ }
96
+ })()
97
+ .catch(async error => {
98
+ if (stopped || loaded || ownerGeneration !== generation) return;
99
+ reportAttemptError(error, { ...context, phase: 'runtime-load' });
100
+ await ensureRecoveryVisible({ ...context, phase: 'runtime-load' });
101
+ })
102
+ .finally(() => {
103
+ if (operation !== currentOperation) return;
104
+ operation = null;
105
+ if (!stopped && !loaded) {
106
+ const delay = pendingRetryDelay;
107
+ pendingRetryDelay = retryDelay;
108
+ schedule(pendingReason || 'retry', delay);
109
+ }
110
+ });
111
+ operation = currentOperation;
112
+ }, normalizeDelay(delayMs));
113
+ timer?.unref?.();
114
+ return true;
115
+ };
116
+
117
+ const beginNewGeneration = reason => {
118
+ if (stopped) return false;
119
+ generation += 1;
120
+ loaded = false;
121
+ recoveryVisible = false;
122
+ pendingReason = String(reason || 'reload');
123
+ pendingRetryDelay = 0;
124
+ if (timer !== null) {
125
+ clearTimer(timer);
126
+ timer = null;
127
+ }
128
+ schedule(pendingReason, 0);
129
+ return true;
130
+ };
131
+
132
+ return {
133
+ getStatus: snapshot,
134
+ start(reason = 'start') {
135
+ return schedule(reason, 0);
136
+ },
137
+ noteMainFrameFailure(reason = 'main-frame-failed') {
138
+ return beginNewGeneration(reason);
139
+ },
140
+ noteRendererGone(reason = 'renderer-gone') {
141
+ return beginNewGeneration(reason);
142
+ },
143
+ stop() {
144
+ if (stopped) return;
145
+ stopped = true;
146
+ generation += 1;
147
+ if (timer !== null) {
148
+ clearTimer(timer);
149
+ timer = null;
150
+ }
151
+ }
152
+ };
153
+ }