@jungjaehoon/mama-os 0.32.3 → 0.34.0
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.md +15 -17
- package/dist/agent/os-agent-capabilities.md +2 -2
- package/dist/api/graph-api.d.ts +4 -4
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +47 -78
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +9 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/operator-handler.d.ts +1 -1
- package/dist/api/operator-handler.js +1 -1
- package/dist/api/runtime-status-handler.d.ts +41 -0
- package/dist/api/runtime-status-handler.d.ts.map +1 -0
- package/dist/api/runtime-status-handler.js +55 -0
- package/dist/api/runtime-status-handler.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +58 -23
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +2 -0
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +62 -1
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/cli/runtime/api-server-init.d.ts +12 -1
- package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-server-init.js +54 -0
- package/dist/cli/runtime/api-server-init.js.map +1 -1
- package/dist/cli/runtime/utilities.d.ts +1 -1
- package/dist/cli/runtime/utilities.d.ts.map +1 -1
- package/dist/cli/runtime/utilities.js +3 -1
- package/dist/cli/runtime/utilities.js.map +1 -1
- package/dist/gateways/message-router.d.ts +4 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +62 -13
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/owner-report-inbox.d.ts +40 -0
- package/dist/gateways/owner-report-inbox.d.ts.map +1 -0
- package/dist/gateways/owner-report-inbox.js +128 -0
- package/dist/gateways/owner-report-inbox.js.map +1 -0
- package/dist/gateways/report-projection-v1.d.ts +41 -0
- package/dist/gateways/report-projection-v1.d.ts.map +1 -0
- package/dist/gateways/report-projection-v1.js +160 -0
- package/dist/gateways/report-projection-v1.js.map +1 -0
- package/dist/gateways/session-store.d.ts +22 -0
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +76 -3
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/telegram-message-ledger.d.ts +10 -0
- package/dist/gateways/telegram-message-ledger.d.ts.map +1 -1
- package/dist/gateways/telegram-message-ledger.js +23 -2
- package/dist/gateways/telegram-message-ledger.js.map +1 -1
- package/dist/gateways/telegram-report-context-store.d.ts +193 -0
- package/dist/gateways/telegram-report-context-store.d.ts.map +1 -0
- package/dist/gateways/telegram-report-context-store.js +612 -0
- package/dist/gateways/telegram-report-context-store.js.map +1 -0
- package/dist/gateways/telegram.d.ts +9 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +100 -0
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/types.d.ts +12 -0
- package/dist/gateways/types.d.ts.map +1 -1
- package/dist/multi-agent/dashboard-agent-persona.js +2 -2
- package/dist/operator/board-slot-instructions.d.ts +1 -1
- package/dist/operator/board-slot-instructions.js +1 -1
- package/dist/operator/operator-trigger-loop.d.ts +17 -1
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +87 -23
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/report-delivery-coordinator.d.ts +82 -0
- package/dist/operator/report-delivery-coordinator.d.ts.map +1 -0
- package/dist/operator/report-delivery-coordinator.js +123 -0
- package/dist/operator/report-delivery-coordinator.js.map +1 -0
- package/dist/operator/situation-report.d.ts +7 -0
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +12 -0
- package/dist/operator/situation-report.js.map +1 -1
- package/package.json +1 -2
- package/public/viewer/js/modules/system.js +183 -0
- package/public/viewer/js/utils/ui-commands.js +4 -9
- package/public/viewer/log-viewer.html +2 -192
- package/public/viewer/operator/operator.css +1 -0
- package/public/viewer/operator/operator.js +14830 -0
- package/public/viewer/src/modules/system.ts +242 -0
- package/public/viewer/src/utils/ui-commands.ts +4 -8
- package/public/viewer/sw.js +44 -10
- package/public/viewer/viewer.css +338 -331
- package/public/viewer/viewer.html +388 -1472
- package/public/ui/assets/index-BDXDGEV6.js +0 -62
- package/public/ui/assets/index-po2tN0xq.css +0 -1
- package/public/ui/index.html +0 -14
- package/public/ui/theme-init.js +0 -20
- package/public/viewer/js/modules/agents.js +0 -1153
- package/public/viewer/js/modules/chat.js +0 -1993
- package/public/viewer/js/modules/connector-feed.js +0 -352
- package/public/viewer/js/modules/dashboard.js +0 -456
- package/public/viewer/js/modules/entity-audit.js +0 -106
- package/public/viewer/js/modules/entity-review.js +0 -66
- package/public/viewer/js/modules/settings.js +0 -1257
- package/public/viewer/src/modules/agents.ts +0 -1302
- package/public/viewer/src/modules/chat.ts +0 -2426
- package/public/viewer/src/modules/connector-feed.ts +0 -391
- package/public/viewer/src/modules/dashboard.ts +0 -498
- package/public/viewer/src/modules/entity-audit.ts +0 -154
- package/public/viewer/src/modules/entity-review.ts +0 -175
- package/public/viewer/src/modules/settings.ts +0 -1547
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System views: Runtime and Connectors.
|
|
3
|
+
* @module modules/system
|
|
4
|
+
*
|
|
5
|
+
* Read-only truth, one source. Both views render `GET /api/runtime/status` and
|
|
6
|
+
* nothing else: no config read, no client-side model registry, no credential
|
|
7
|
+
* ever reaching the DOM. Editing daemon configuration from the browser is not a
|
|
8
|
+
* capability of this surface; setup is a CLI action.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* eslint-env browser */
|
|
12
|
+
|
|
13
|
+
const RUNTIME_STATUS_ENDPOINT = '/api/runtime/status';
|
|
14
|
+
|
|
15
|
+
/** Shown when the daemon booted without a health service. Never a fake score. */
|
|
16
|
+
const HEALTH_UNAVAILABLE = 'unavailable';
|
|
17
|
+
|
|
18
|
+
export type RuntimeBackend = 'claude' | 'codex' | 'cline';
|
|
19
|
+
export type RuntimeConnectorState = 'connected' | 'disconnected' | 'unknown';
|
|
20
|
+
|
|
21
|
+
export interface RuntimeConnectorStatus {
|
|
22
|
+
name: string;
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
state: RuntimeConnectorState;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RuntimeStatusSnapshot {
|
|
28
|
+
running: boolean;
|
|
29
|
+
backend: RuntimeBackend;
|
|
30
|
+
model: string;
|
|
31
|
+
startedAt: number;
|
|
32
|
+
health: { score: number; status: string } | null;
|
|
33
|
+
connectors: RuntimeConnectorStatus[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SystemViewOptions {
|
|
37
|
+
/** Test seam; defaults to fetching the authoritative endpoint. */
|
|
38
|
+
fetchStatus?: () => Promise<RuntimeStatusSnapshot>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function escapeHtml(value: unknown): string {
|
|
42
|
+
return String(value ?? '')
|
|
43
|
+
.replace(/&/g, '&')
|
|
44
|
+
.replace(/</g, '<')
|
|
45
|
+
.replace(/>/g, '>')
|
|
46
|
+
.replace(/"/g, '"')
|
|
47
|
+
.replace(/'/g, ''');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function defaultFetchStatus(): Promise<RuntimeStatusSnapshot> {
|
|
51
|
+
const response = await fetch(RUNTIME_STATUS_ENDPOINT);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error('HTTP ' + response.status);
|
|
54
|
+
}
|
|
55
|
+
return (await response.json()) as RuntimeStatusSnapshot;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function skeleton(rows: number): string {
|
|
59
|
+
let html = '<div class="mama-system-skeleton" aria-busy="true" aria-live="polite">';
|
|
60
|
+
for (let i = 0; i < rows; i++) {
|
|
61
|
+
html += '<div class="mama-system-skeleton-row"></div>';
|
|
62
|
+
}
|
|
63
|
+
html += '</div>';
|
|
64
|
+
return html;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Failure names the endpoint that failed and offers a retry. A System view that
|
|
69
|
+
* silently shows nothing is indistinguishable from a healthy empty runtime.
|
|
70
|
+
*/
|
|
71
|
+
function errorCard(error: unknown): string {
|
|
72
|
+
const detail = error instanceof Error ? error.message : String(error ?? 'Unknown error');
|
|
73
|
+
return (
|
|
74
|
+
'<div class="mama-system-error" role="alert">' +
|
|
75
|
+
'<p class="mama-system-error-title">Could not read ' +
|
|
76
|
+
escapeHtml(RUNTIME_STATUS_ENDPOINT) +
|
|
77
|
+
'</p>' +
|
|
78
|
+
'<p class="mama-system-error-detail">' +
|
|
79
|
+
escapeHtml(detail) +
|
|
80
|
+
'</p>' +
|
|
81
|
+
'<button type="button" class="mama-system-retry" data-retry>Retry</button>' +
|
|
82
|
+
'</div>'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function formatStartedAt(startedAt: number): string {
|
|
87
|
+
if (!Number.isFinite(startedAt) || startedAt <= 0) return 'unknown';
|
|
88
|
+
return new Date(startedAt).toLocaleString();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatUptime(startedAt: number): string {
|
|
92
|
+
if (!Number.isFinite(startedAt) || startedAt <= 0) return 'unknown';
|
|
93
|
+
const seconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
|
|
94
|
+
const days = Math.floor(seconds / 86400);
|
|
95
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
96
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
97
|
+
if (days > 0) return days + 'd ' + hours + 'h';
|
|
98
|
+
if (hours > 0) return hours + 'h ' + minutes + 'm';
|
|
99
|
+
return minutes + 'm';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function statTile(label: string, value: string, tone = 'neutral'): string {
|
|
103
|
+
return (
|
|
104
|
+
'<div class="mama-system-tile" data-tone="' +
|
|
105
|
+
escapeHtml(tone) +
|
|
106
|
+
'">' +
|
|
107
|
+
'<span class="mama-system-tile-label">' +
|
|
108
|
+
escapeHtml(label) +
|
|
109
|
+
'</span>' +
|
|
110
|
+
'<span class="mama-system-tile-value">' +
|
|
111
|
+
escapeHtml(value) +
|
|
112
|
+
'</span>' +
|
|
113
|
+
'</div>'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function renderRuntimeSnapshot(snapshot: RuntimeStatusSnapshot): string {
|
|
118
|
+
const health = snapshot.health;
|
|
119
|
+
const healthValue = health ? health.status + ' - ' + health.score : HEALTH_UNAVAILABLE;
|
|
120
|
+
const healthTone = health
|
|
121
|
+
? health.status === 'healthy'
|
|
122
|
+
? 'ok'
|
|
123
|
+
: health.status === 'degraded'
|
|
124
|
+
? 'warn'
|
|
125
|
+
: 'bad'
|
|
126
|
+
: 'muted';
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
'<section class="mama-system-panel">' +
|
|
130
|
+
'<h2 class="mama-system-heading">Runtime</h2>' +
|
|
131
|
+
'<p class="mama-system-note">Reported by the running daemon; some fields reflect boot-time state. Configuration is edited via the CLI or the config file.</p>' +
|
|
132
|
+
'<div class="mama-system-tiles">' +
|
|
133
|
+
statTile('Daemon', snapshot.running ? 'running' : 'stopped', snapshot.running ? 'ok' : 'bad') +
|
|
134
|
+
statTile('Backend', snapshot.backend) +
|
|
135
|
+
statTile('Model', snapshot.model) +
|
|
136
|
+
statTile('Health', healthValue, healthTone) +
|
|
137
|
+
statTile('Uptime', formatUptime(snapshot.startedAt)) +
|
|
138
|
+
statTile('Started', formatStartedAt(snapshot.startedAt)) +
|
|
139
|
+
'</div>' +
|
|
140
|
+
'</section>'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const CONNECTOR_STATE_TONE: Record<RuntimeConnectorState, string> = {
|
|
145
|
+
connected: 'ok',
|
|
146
|
+
disconnected: 'muted',
|
|
147
|
+
unknown: 'warn',
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* What a connector state actually means. 'connected' is not a live probe: it
|
|
152
|
+
* says the connector registered when the daemon booted, so the chip carries the
|
|
153
|
+
* narrower claim rather than letting the word oversell itself.
|
|
154
|
+
*/
|
|
155
|
+
const CONNECTOR_STATE_TITLE: Record<RuntimeConnectorState, string> = {
|
|
156
|
+
connected: 'Registered at daemon boot',
|
|
157
|
+
disconnected: 'Disabled in config',
|
|
158
|
+
unknown: 'Enabled in config but not registered at boot',
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export function renderConnectorsSnapshot(snapshot: RuntimeStatusSnapshot): string {
|
|
162
|
+
const connectors = snapshot.connectors ?? [];
|
|
163
|
+
if (connectors.length === 0) {
|
|
164
|
+
return (
|
|
165
|
+
'<section class="mama-system-panel">' +
|
|
166
|
+
'<h2 class="mama-system-heading">Connectors</h2>' +
|
|
167
|
+
'<div class="mama-system-empty">' +
|
|
168
|
+
'<p class="mama-system-empty-title">No connectors configured</p>' +
|
|
169
|
+
'<p class="mama-system-empty-detail">Add one from a terminal:</p>' +
|
|
170
|
+
'<code class="mama-system-code">mama connector add <name></code>' +
|
|
171
|
+
'</div>' +
|
|
172
|
+
'</section>'
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let rows = '';
|
|
177
|
+
for (const connector of connectors) {
|
|
178
|
+
rows +=
|
|
179
|
+
'<li class="mama-system-row" data-tone="' +
|
|
180
|
+
escapeHtml(CONNECTOR_STATE_TONE[connector.state] ?? 'muted') +
|
|
181
|
+
'">' +
|
|
182
|
+
'<span class="mama-system-dot"></span>' +
|
|
183
|
+
'<span class="mama-system-row-name">' +
|
|
184
|
+
escapeHtml(connector.name) +
|
|
185
|
+
'</span>' +
|
|
186
|
+
'<span class="mama-system-row-state" title="' +
|
|
187
|
+
escapeHtml(CONNECTOR_STATE_TITLE[connector.state] ?? 'State reported at daemon boot') +
|
|
188
|
+
'">' +
|
|
189
|
+
escapeHtml(connector.state) +
|
|
190
|
+
'</span>' +
|
|
191
|
+
(connector.enabled ? '' : '<span class="mama-system-badge">off</span>') +
|
|
192
|
+
'</li>';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
'<section class="mama-system-panel">' +
|
|
197
|
+
'<h2 class="mama-system-heading">Connectors</h2>' +
|
|
198
|
+
'<p class="mama-system-note">Read-only. Add or remove connectors with <code>mama connector add</code>.</p>' +
|
|
199
|
+
'<ul class="mama-system-list">' +
|
|
200
|
+
rows +
|
|
201
|
+
'</ul>' +
|
|
202
|
+
'</section>'
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function renderView(
|
|
207
|
+
container: HTMLElement | null,
|
|
208
|
+
options: SystemViewOptions,
|
|
209
|
+
toHtml: (snapshot: RuntimeStatusSnapshot) => string,
|
|
210
|
+
skeletonRows: number
|
|
211
|
+
): Promise<void> {
|
|
212
|
+
if (!container) return;
|
|
213
|
+
const fetchStatus = options.fetchStatus ?? defaultFetchStatus;
|
|
214
|
+
|
|
215
|
+
container.innerHTML = skeleton(skeletonRows);
|
|
216
|
+
try {
|
|
217
|
+
const snapshot = await fetchStatus();
|
|
218
|
+
container.innerHTML = toHtml(snapshot);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
container.innerHTML = errorCard(error);
|
|
221
|
+
const retry = container.querySelector('[data-retry]');
|
|
222
|
+
if (retry) {
|
|
223
|
+
retry.addEventListener('click', () => {
|
|
224
|
+
void renderView(container, options, toHtml, skeletonRows);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function renderRuntimeView(
|
|
231
|
+
container: HTMLElement | null,
|
|
232
|
+
options: SystemViewOptions = {}
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
return renderView(container, options, renderRuntimeSnapshot, 3);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function renderConnectorsView(
|
|
238
|
+
container: HTMLElement | null,
|
|
239
|
+
options: SystemViewOptions = {}
|
|
240
|
+
): Promise<void> {
|
|
241
|
+
return renderView(container, options, renderConnectorsSnapshot, 5);
|
|
242
|
+
}
|
|
@@ -26,15 +26,11 @@ type StoredPageContext = {
|
|
|
26
26
|
let lastReportedPageContext: StoredPageContext | null = null;
|
|
27
27
|
let lastPageContextPublishAt = 0;
|
|
28
28
|
|
|
29
|
+
// The Viewer always reports into the one frontdoor channel. The per-chat
|
|
30
|
+
// session override this used to read is gone with the Chat tab that wrote it:
|
|
31
|
+
// nothing writes `mama_chat_session_id` any more, so reading it could only ever
|
|
32
|
+
// return a stale value.
|
|
29
33
|
function getViewerChannelId(): string {
|
|
30
|
-
try {
|
|
31
|
-
const sessionId = window.localStorage.getItem('mama_chat_session_id');
|
|
32
|
-
if (sessionId && !sessionId.startsWith('session_')) {
|
|
33
|
-
return sessionId;
|
|
34
|
-
}
|
|
35
|
-
} catch {
|
|
36
|
-
/* ignore */
|
|
37
|
-
}
|
|
38
34
|
return VIEWER_FRONTDOOR_CHANNEL_ID;
|
|
39
35
|
}
|
|
40
36
|
|
package/public/viewer/sw.js
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Service Worker for MAMA
|
|
3
|
-
* @version
|
|
2
|
+
* @fileoverview Service Worker for the MAMA Viewer PWA
|
|
3
|
+
* @version 2.0.0
|
|
4
4
|
*
|
|
5
5
|
* Provides offline caching for static assets using cache-first strategy.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/* eslint-env serviceworker */
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Bumped for the unified Viewer: the retired chat/feed/agents/dashboard/settings
|
|
11
|
+
// modules no longer exist, so a client holding the old cache would keep serving
|
|
12
|
+
// 404-shaped entries for them. A new cache name is what evicts them.
|
|
13
|
+
const CACHE_NAME = 'mama-viewer-v2';
|
|
11
14
|
const STATIC_ASSETS = [
|
|
12
15
|
'/viewer',
|
|
13
16
|
'/viewer/viewer.css',
|
|
14
17
|
'/viewer/manifest.json',
|
|
15
18
|
'/viewer/js/modules/graph.js',
|
|
16
|
-
'/viewer/js/modules/chat.js',
|
|
17
19
|
'/viewer/js/modules/memory.js',
|
|
18
|
-
'/viewer/js/modules/
|
|
19
|
-
'/viewer/js/modules/
|
|
20
|
+
'/viewer/js/modules/wiki.js',
|
|
21
|
+
'/viewer/js/modules/system.js',
|
|
22
|
+
'/viewer/js/utils/debug-logger.js',
|
|
20
23
|
'/viewer/js/utils/dom.js',
|
|
21
24
|
'/viewer/js/utils/format.js',
|
|
22
25
|
'/viewer/js/utils/api.js',
|
|
26
|
+
'/viewer/js/utils/markdown.js',
|
|
27
|
+
'/viewer/js/utils/ui-commands.js',
|
|
28
|
+
'/viewer/operator/operator.js',
|
|
29
|
+
'/viewer/operator/operator.css',
|
|
23
30
|
'/viewer/icons/icon-192.png',
|
|
24
31
|
'/viewer/icons/icon-512.png',
|
|
25
32
|
'/viewer/icons/mama-icon.svg',
|
|
@@ -92,6 +99,32 @@ self.addEventListener('fetch', (event) => {
|
|
|
92
99
|
return;
|
|
93
100
|
}
|
|
94
101
|
|
|
102
|
+
// Network-first for the operator bundle. It ships under stable filenames
|
|
103
|
+
// (/viewer/operator/operator.js), so a cache-first hit would pin one build
|
|
104
|
+
// forever until CACHE_NAME changes. The cache stays the offline fallback.
|
|
105
|
+
// Must be checked before the generic '/viewer' prefix match below.
|
|
106
|
+
if (url.pathname.startsWith('/viewer/operator/')) {
|
|
107
|
+
event.respondWith(
|
|
108
|
+
fetch(request)
|
|
109
|
+
.then((response) => {
|
|
110
|
+
if (response.ok) {
|
|
111
|
+
const responseClone = response.clone();
|
|
112
|
+
// The write is held open by the event, not by respondWith: the
|
|
113
|
+
// response still returns immediately, but the worker may not be
|
|
114
|
+
// killed mid-put, which would leave a truncated cache entry.
|
|
115
|
+
event.waitUntil(
|
|
116
|
+
caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone))
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return response;
|
|
120
|
+
})
|
|
121
|
+
.catch(() =>
|
|
122
|
+
caches.match(request).then((cachedResponse) => cachedResponse || caches.match('/viewer'))
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
// Cache-first for static assets
|
|
96
129
|
if (STATIC_ASSETS.some((asset) => url.pathname.startsWith(asset.split('?')[0]))) {
|
|
97
130
|
event.respondWith(
|
|
@@ -100,12 +133,13 @@ self.addEventListener('fetch', (event) => {
|
|
|
100
133
|
return cachedResponse;
|
|
101
134
|
}
|
|
102
135
|
return fetch(request).then((response) => {
|
|
103
|
-
// Cache successful responses
|
|
136
|
+
// Cache successful responses. Same detached-put hazard as the
|
|
137
|
+
// operator branch above, so it is held open the same way.
|
|
104
138
|
if (response.ok) {
|
|
105
139
|
const responseClone = response.clone();
|
|
106
|
-
|
|
107
|
-
cache.put(request, responseClone)
|
|
108
|
-
|
|
140
|
+
event.waitUntil(
|
|
141
|
+
caches.open(CACHE_NAME).then((cache) => cache.put(request, responseClone))
|
|
142
|
+
);
|
|
109
143
|
}
|
|
110
144
|
return response;
|
|
111
145
|
});
|