esp32tool 1.1.9 → 1.3.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/.nojekyll +0 -0
- package/README.md +100 -6
- package/apple-touch-icon.png +0 -0
- package/build-electron-cli.cjs +177 -0
- package/build-single-binary.cjs +295 -0
- package/css/light.css +11 -0
- package/css/style.css +261 -41
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +458 -0
- package/dist/console.d.ts +15 -0
- package/dist/console.js +237 -0
- package/dist/const.d.ts +99 -0
- package/dist/const.js +129 -8
- package/dist/esp_loader.d.ts +244 -22
- package/dist/esp_loader.js +1960 -251
- package/dist/index.d.ts +2 -1
- package/dist/index.js +37 -4
- package/dist/node-usb-adapter.d.ts +47 -0
- package/dist/node-usb-adapter.js +725 -0
- package/dist/stubs/index.d.ts +1 -2
- package/dist/stubs/index.js +4 -0
- package/dist/util/console-color.d.ts +19 -0
- package/dist/util/console-color.js +272 -0
- package/dist/util/line-break-transformer.d.ts +5 -0
- package/dist/util/line-break-transformer.js +17 -0
- package/dist/web/index.js +1 -1
- package/electron/cli-main.cjs +74 -0
- package/electron/main.cjs +338 -0
- package/electron/main.js +7 -2
- package/favicon.ico +0 -0
- package/fix-cli-imports.cjs +127 -0
- package/generate-icons.sh +89 -0
- package/icons/icon-128.png +0 -0
- package/icons/icon-144.png +0 -0
- package/icons/icon-152.png +0 -0
- package/icons/icon-192.png +0 -0
- package/icons/icon-384.png +0 -0
- package/icons/icon-512.png +0 -0
- package/icons/icon-72.png +0 -0
- package/icons/icon-96.png +0 -0
- package/index.html +143 -73
- package/install-android.html +411 -0
- package/js/console.js +269 -0
- package/js/modules/esptool.js +1 -1
- package/js/script.js +750 -175
- package/js/util/console-color.js +282 -0
- package/js/util/line-break-transformer.js +19 -0
- package/js/webusb-serial.js +1017 -0
- package/license.md +1 -1
- package/manifest.json +89 -0
- package/package.cli.json +29 -0
- package/package.json +35 -24
- package/screenshots/desktop.png +0 -0
- package/screenshots/mobile.png +0 -0
- package/src/cli.ts +618 -0
- package/src/console.ts +278 -0
- package/src/const.ts +165 -8
- package/src/esp_loader.ts +2354 -302
- package/src/index.ts +69 -3
- package/src/node-usb-adapter.ts +924 -0
- package/src/stubs/index.ts +4 -1
- package/src/util/console-color.ts +290 -0
- package/src/util/line-break-transformer.ts +20 -0
- package/sw.js +155 -0
package/src/stubs/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ interface LoadedStub {
|
|
|
25
25
|
data_start: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
interface Stub {
|
|
28
|
+
export interface Stub {
|
|
29
29
|
text: number[];
|
|
30
30
|
data: number[];
|
|
31
31
|
text_start: number;
|
|
@@ -75,6 +75,9 @@ export const getStubCode = async (
|
|
|
75
75
|
} else {
|
|
76
76
|
stubcode = await import("./esp32p4.json");
|
|
77
77
|
}
|
|
78
|
+
} else {
|
|
79
|
+
// Unknown chip family - no stub available
|
|
80
|
+
return null;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
// Base64 decode the text and data
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
interface ConsoleState {
|
|
2
|
+
bold: boolean;
|
|
3
|
+
italic: boolean;
|
|
4
|
+
underline: boolean;
|
|
5
|
+
strikethrough: boolean;
|
|
6
|
+
foregroundColor: string | null;
|
|
7
|
+
backgroundColor: string | null;
|
|
8
|
+
carriageReturn: boolean;
|
|
9
|
+
secret: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ColoredConsole {
|
|
13
|
+
public state: ConsoleState = {
|
|
14
|
+
bold: false,
|
|
15
|
+
italic: false,
|
|
16
|
+
underline: false,
|
|
17
|
+
strikethrough: false,
|
|
18
|
+
foregroundColor: null,
|
|
19
|
+
backgroundColor: null,
|
|
20
|
+
carriageReturn: false,
|
|
21
|
+
secret: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
constructor(public targetElement: HTMLElement) {}
|
|
25
|
+
|
|
26
|
+
logs(): string {
|
|
27
|
+
return this.targetElement.innerText;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addLine(line: string) {
|
|
31
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
|
|
32
|
+
const re = /(?:\x1B|\\x1B)(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1B\\))/g;
|
|
33
|
+
let i = 0;
|
|
34
|
+
|
|
35
|
+
if (this.state.carriageReturn) {
|
|
36
|
+
if (line !== "\n") {
|
|
37
|
+
// don't remove if \r\n
|
|
38
|
+
if (this.targetElement.lastChild) {
|
|
39
|
+
this.targetElement.removeChild(this.targetElement.lastChild);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.state.carriageReturn = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const hasBareCR = line.endsWith("\r") && !line.endsWith("\r\n");
|
|
46
|
+
if (hasBareCR) {
|
|
47
|
+
this.state.carriageReturn = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const lineSpan = document.createElement("span");
|
|
51
|
+
lineSpan.classList.add("line");
|
|
52
|
+
this.targetElement.appendChild(lineSpan);
|
|
53
|
+
|
|
54
|
+
const addSpan = (content: string) => {
|
|
55
|
+
if (content === "") return;
|
|
56
|
+
|
|
57
|
+
const span = document.createElement("span");
|
|
58
|
+
if (this.state.bold) span.classList.add("log-bold");
|
|
59
|
+
if (this.state.italic) span.classList.add("log-italic");
|
|
60
|
+
if (this.state.underline) span.classList.add("log-underline");
|
|
61
|
+
if (this.state.strikethrough) span.classList.add("log-strikethrough");
|
|
62
|
+
if (this.state.secret) span.classList.add("log-secret");
|
|
63
|
+
if (this.state.foregroundColor !== null)
|
|
64
|
+
span.classList.add(`log-fg-${this.state.foregroundColor}`);
|
|
65
|
+
if (this.state.backgroundColor !== null)
|
|
66
|
+
span.classList.add(`log-bg-${this.state.backgroundColor}`);
|
|
67
|
+
span.appendChild(document.createTextNode(content));
|
|
68
|
+
lineSpan.appendChild(span);
|
|
69
|
+
|
|
70
|
+
if (this.state.secret) {
|
|
71
|
+
const redacted = document.createElement("span");
|
|
72
|
+
redacted.classList.add("log-secret-redacted");
|
|
73
|
+
redacted.appendChild(document.createTextNode("[redacted]"));
|
|
74
|
+
lineSpan.appendChild(redacted);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
while (true) {
|
|
79
|
+
const match = re.exec(line);
|
|
80
|
+
if (match === null) break;
|
|
81
|
+
|
|
82
|
+
const j = match.index;
|
|
83
|
+
addSpan(line.substring(i, j));
|
|
84
|
+
i = j + match[0].length;
|
|
85
|
+
|
|
86
|
+
if (match[1] === undefined) continue;
|
|
87
|
+
|
|
88
|
+
for (const colorCode of match[1].split(";")) {
|
|
89
|
+
switch (parseInt(colorCode)) {
|
|
90
|
+
case 0:
|
|
91
|
+
// reset
|
|
92
|
+
this.state.bold = false;
|
|
93
|
+
this.state.italic = false;
|
|
94
|
+
this.state.underline = false;
|
|
95
|
+
this.state.strikethrough = false;
|
|
96
|
+
this.state.foregroundColor = null;
|
|
97
|
+
this.state.backgroundColor = null;
|
|
98
|
+
this.state.secret = false;
|
|
99
|
+
break;
|
|
100
|
+
case 1:
|
|
101
|
+
this.state.bold = true;
|
|
102
|
+
break;
|
|
103
|
+
case 3:
|
|
104
|
+
this.state.italic = true;
|
|
105
|
+
break;
|
|
106
|
+
case 4:
|
|
107
|
+
this.state.underline = true;
|
|
108
|
+
break;
|
|
109
|
+
case 5:
|
|
110
|
+
this.state.secret = true;
|
|
111
|
+
break;
|
|
112
|
+
case 6:
|
|
113
|
+
this.state.secret = false;
|
|
114
|
+
break;
|
|
115
|
+
case 9:
|
|
116
|
+
this.state.strikethrough = true;
|
|
117
|
+
break;
|
|
118
|
+
case 22:
|
|
119
|
+
this.state.bold = false;
|
|
120
|
+
break;
|
|
121
|
+
case 23:
|
|
122
|
+
this.state.italic = false;
|
|
123
|
+
break;
|
|
124
|
+
case 24:
|
|
125
|
+
this.state.underline = false;
|
|
126
|
+
break;
|
|
127
|
+
case 29:
|
|
128
|
+
this.state.strikethrough = false;
|
|
129
|
+
break;
|
|
130
|
+
case 30:
|
|
131
|
+
this.state.foregroundColor = "black";
|
|
132
|
+
break;
|
|
133
|
+
case 31:
|
|
134
|
+
this.state.foregroundColor = "red";
|
|
135
|
+
break;
|
|
136
|
+
case 32:
|
|
137
|
+
this.state.foregroundColor = "green";
|
|
138
|
+
break;
|
|
139
|
+
case 33:
|
|
140
|
+
this.state.foregroundColor = "yellow";
|
|
141
|
+
break;
|
|
142
|
+
case 34:
|
|
143
|
+
this.state.foregroundColor = "blue";
|
|
144
|
+
break;
|
|
145
|
+
case 35:
|
|
146
|
+
this.state.foregroundColor = "magenta";
|
|
147
|
+
break;
|
|
148
|
+
case 36:
|
|
149
|
+
this.state.foregroundColor = "cyan";
|
|
150
|
+
break;
|
|
151
|
+
case 37:
|
|
152
|
+
this.state.foregroundColor = "white";
|
|
153
|
+
break;
|
|
154
|
+
case 39:
|
|
155
|
+
this.state.foregroundColor = null;
|
|
156
|
+
break;
|
|
157
|
+
case 41:
|
|
158
|
+
this.state.backgroundColor = "red";
|
|
159
|
+
break;
|
|
160
|
+
case 42:
|
|
161
|
+
this.state.backgroundColor = "green";
|
|
162
|
+
break;
|
|
163
|
+
case 43:
|
|
164
|
+
this.state.backgroundColor = "yellow";
|
|
165
|
+
break;
|
|
166
|
+
case 44:
|
|
167
|
+
this.state.backgroundColor = "blue";
|
|
168
|
+
break;
|
|
169
|
+
case 45:
|
|
170
|
+
this.state.backgroundColor = "magenta";
|
|
171
|
+
break;
|
|
172
|
+
case 46:
|
|
173
|
+
this.state.backgroundColor = "cyan";
|
|
174
|
+
break;
|
|
175
|
+
case 47:
|
|
176
|
+
this.state.backgroundColor = "white";
|
|
177
|
+
break;
|
|
178
|
+
case 40:
|
|
179
|
+
this.state.backgroundColor = "black";
|
|
180
|
+
break;
|
|
181
|
+
case 49:
|
|
182
|
+
this.state.backgroundColor = null;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const atBottom =
|
|
188
|
+
this.targetElement.scrollTop >
|
|
189
|
+
this.targetElement.scrollHeight - this.targetElement.offsetHeight - 50;
|
|
190
|
+
|
|
191
|
+
addSpan(line.substring(i));
|
|
192
|
+
|
|
193
|
+
// Keep scroll at bottom
|
|
194
|
+
if (atBottom) {
|
|
195
|
+
this.targetElement.scrollTop = this.targetElement.scrollHeight;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const coloredConsoleStyles = `
|
|
201
|
+
.log {
|
|
202
|
+
flex: 1;
|
|
203
|
+
background-color: #1c1c1c;
|
|
204
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier,
|
|
205
|
+
monospace;
|
|
206
|
+
font-size: 12px;
|
|
207
|
+
padding: 16px;
|
|
208
|
+
overflow: auto;
|
|
209
|
+
line-height: 1.45;
|
|
210
|
+
border-radius: 3px;
|
|
211
|
+
white-space: pre-wrap;
|
|
212
|
+
overflow-wrap: break-word;
|
|
213
|
+
color: #ddd;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.log-bold {
|
|
217
|
+
font-weight: bold;
|
|
218
|
+
}
|
|
219
|
+
.log-italic {
|
|
220
|
+
font-style: italic;
|
|
221
|
+
}
|
|
222
|
+
.log-underline {
|
|
223
|
+
text-decoration: underline;
|
|
224
|
+
}
|
|
225
|
+
.log-strikethrough {
|
|
226
|
+
text-decoration: line-through;
|
|
227
|
+
}
|
|
228
|
+
.log-underline.log-strikethrough {
|
|
229
|
+
text-decoration: underline line-through;
|
|
230
|
+
}
|
|
231
|
+
.log-secret {
|
|
232
|
+
-webkit-user-select: none;
|
|
233
|
+
-moz-user-select: none;
|
|
234
|
+
-ms-user-select: none;
|
|
235
|
+
user-select: none;
|
|
236
|
+
}
|
|
237
|
+
.log-secret-redacted {
|
|
238
|
+
opacity: 0;
|
|
239
|
+
width: 1px;
|
|
240
|
+
font-size: 1px;
|
|
241
|
+
}
|
|
242
|
+
.log-fg-black {
|
|
243
|
+
color: rgb(128, 128, 128);
|
|
244
|
+
}
|
|
245
|
+
.log-fg-red {
|
|
246
|
+
color: rgb(255, 0, 0);
|
|
247
|
+
}
|
|
248
|
+
.log-fg-green {
|
|
249
|
+
color: rgb(0, 255, 0);
|
|
250
|
+
}
|
|
251
|
+
.log-fg-yellow {
|
|
252
|
+
color: rgb(255, 255, 0);
|
|
253
|
+
}
|
|
254
|
+
.log-fg-blue {
|
|
255
|
+
color: rgb(0, 0, 255);
|
|
256
|
+
}
|
|
257
|
+
.log-fg-magenta {
|
|
258
|
+
color: rgb(255, 0, 255);
|
|
259
|
+
}
|
|
260
|
+
.log-fg-cyan {
|
|
261
|
+
color: rgb(0, 255, 255);
|
|
262
|
+
}
|
|
263
|
+
.log-fg-white {
|
|
264
|
+
color: rgb(187, 187, 187);
|
|
265
|
+
}
|
|
266
|
+
.log-bg-black {
|
|
267
|
+
background-color: rgb(0, 0, 0);
|
|
268
|
+
}
|
|
269
|
+
.log-bg-red {
|
|
270
|
+
background-color: rgb(255, 0, 0);
|
|
271
|
+
}
|
|
272
|
+
.log-bg-green {
|
|
273
|
+
background-color: rgb(0, 255, 0);
|
|
274
|
+
}
|
|
275
|
+
.log-bg-yellow {
|
|
276
|
+
background-color: rgb(255, 255, 0);
|
|
277
|
+
}
|
|
278
|
+
.log-bg-blue {
|
|
279
|
+
background-color: rgb(0, 0, 255);
|
|
280
|
+
}
|
|
281
|
+
.log-bg-magenta {
|
|
282
|
+
background-color: rgb(255, 0, 255);
|
|
283
|
+
}
|
|
284
|
+
.log-bg-cyan {
|
|
285
|
+
background-color: rgb(0, 255, 255);
|
|
286
|
+
}
|
|
287
|
+
.log-bg-white {
|
|
288
|
+
background-color: rgb(255, 255, 255);
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class LineBreakTransformer implements Transformer<string, string> {
|
|
2
|
+
private chunks = "";
|
|
3
|
+
|
|
4
|
+
transform(
|
|
5
|
+
chunk: string,
|
|
6
|
+
controller: TransformStreamDefaultController<string>,
|
|
7
|
+
) {
|
|
8
|
+
// Append new chunks to existing chunks.
|
|
9
|
+
this.chunks += chunk;
|
|
10
|
+
// For each line breaks in chunks, send the parsed lines out.
|
|
11
|
+
const lines = this.chunks.split("\r\n");
|
|
12
|
+
this.chunks = lines.pop()!;
|
|
13
|
+
lines.forEach((line) => controller.enqueue(line + "\r\n"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
flush(controller: TransformStreamDefaultController<string>) {
|
|
17
|
+
// When the stream is closed, flush any remaining chunks out.
|
|
18
|
+
controller.enqueue(this.chunks);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/sw.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Service Worker for ESP32Tool PWA
|
|
2
|
+
const CACHE_NAME = 'esp32tool-v1.2.0';
|
|
3
|
+
const RUNTIME_CACHE = 'esp32tool-runtime';
|
|
4
|
+
|
|
5
|
+
// Core files to cache on install (relative paths work for any deployment path)
|
|
6
|
+
// This ensures the app works completely offline after installation
|
|
7
|
+
const CORE_ASSETS = [
|
|
8
|
+
// App shell
|
|
9
|
+
'./',
|
|
10
|
+
'./index.html',
|
|
11
|
+
'./install-android.html',
|
|
12
|
+
|
|
13
|
+
// Stylesheets
|
|
14
|
+
'./css/style.css',
|
|
15
|
+
'./css/light.css',
|
|
16
|
+
'./css/dark.css',
|
|
17
|
+
|
|
18
|
+
// JavaScript
|
|
19
|
+
'./js/script.js',
|
|
20
|
+
'./js/utilities.js',
|
|
21
|
+
'./js/webusb-serial.js',
|
|
22
|
+
'./js/modules/esptool.js',
|
|
23
|
+
|
|
24
|
+
// PWA manifest
|
|
25
|
+
'./manifest.json',
|
|
26
|
+
|
|
27
|
+
// Icons (all sizes referenced in manifest)
|
|
28
|
+
'./icons/icon-72.png',
|
|
29
|
+
'./icons/icon-96.png',
|
|
30
|
+
'./icons/icon-128.png',
|
|
31
|
+
'./icons/icon-144.png',
|
|
32
|
+
'./icons/icon-152.png',
|
|
33
|
+
'./icons/icon-192.png',
|
|
34
|
+
'./icons/icon-384.png',
|
|
35
|
+
'./icons/icon-512.png',
|
|
36
|
+
'./apple-touch-icon.png',
|
|
37
|
+
'./favicon.ico',
|
|
38
|
+
|
|
39
|
+
// WASM modules (required for filesystem operations)
|
|
40
|
+
'./src/wasm/littlefs/index.js',
|
|
41
|
+
'./src/wasm/littlefs/littlefs.js',
|
|
42
|
+
'./src/wasm/littlefs/littlefs.wasm',
|
|
43
|
+
'./src/wasm/fatfs/index.js',
|
|
44
|
+
'./src/wasm/fatfs/fatfs.wasm'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// Install event - cache core assets
|
|
48
|
+
self.addEventListener('install', (event) => {
|
|
49
|
+
console.log('[SW] Installing service worker...');
|
|
50
|
+
event.waitUntil(
|
|
51
|
+
caches.open(CACHE_NAME)
|
|
52
|
+
.then((cache) => {
|
|
53
|
+
console.log('[SW] Caching core assets');
|
|
54
|
+
return cache.addAll(CORE_ASSETS);
|
|
55
|
+
})
|
|
56
|
+
.then(() => {
|
|
57
|
+
console.log('[SW] Skipping waiting - activating immediately');
|
|
58
|
+
return self.skipWaiting();
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Activate event - clean up old caches
|
|
64
|
+
self.addEventListener('activate', (event) => {
|
|
65
|
+
console.log('[SW] Activating service worker...');
|
|
66
|
+
event.waitUntil(
|
|
67
|
+
caches.keys().then((cacheNames) => {
|
|
68
|
+
return Promise.all(
|
|
69
|
+
cacheNames
|
|
70
|
+
.filter((name) => name !== CACHE_NAME && name !== RUNTIME_CACHE)
|
|
71
|
+
.map((name) => {
|
|
72
|
+
console.log('[SW] Deleting old cache:', name);
|
|
73
|
+
return caches.delete(name);
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}).then(() => self.clients.claim())
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Fetch event - network first, fallback to cache
|
|
81
|
+
self.addEventListener('fetch', (event) => {
|
|
82
|
+
const { request } = event;
|
|
83
|
+
const url = new URL(request.url);
|
|
84
|
+
|
|
85
|
+
// Skip non-GET requests
|
|
86
|
+
if (request.method !== 'GET') {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Skip chrome-extension and other non-http(s) requests
|
|
91
|
+
if (!url.protocol.startsWith('http')) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Network first strategy for HTML and API calls
|
|
96
|
+
if (request.headers.get('accept')?.includes('text/html')) {
|
|
97
|
+
event.respondWith(
|
|
98
|
+
fetch(request)
|
|
99
|
+
.then((response) => {
|
|
100
|
+
// Only cache successful responses
|
|
101
|
+
if (response && response.ok) {
|
|
102
|
+
// Clone and cache the response
|
|
103
|
+
const responseClone = response.clone();
|
|
104
|
+
caches.open(RUNTIME_CACHE).then((cache) => {
|
|
105
|
+
cache.put(request, responseClone);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return response;
|
|
109
|
+
})
|
|
110
|
+
.catch(() => {
|
|
111
|
+
// Fallback to cache
|
|
112
|
+
return caches.match(request);
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Cache first strategy for static assets
|
|
119
|
+
event.respondWith(
|
|
120
|
+
caches.match(request)
|
|
121
|
+
.then((cachedResponse) => {
|
|
122
|
+
if (cachedResponse) {
|
|
123
|
+
return cachedResponse;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return fetch(request).then((response) => {
|
|
127
|
+
// Don't cache non-successful responses
|
|
128
|
+
if (!response || response.status !== 200 || response.type === 'error') {
|
|
129
|
+
return response;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Clone and cache the response
|
|
133
|
+
const responseClone = response.clone();
|
|
134
|
+
caches.open(RUNTIME_CACHE).then((cache) => {
|
|
135
|
+
cache.put(request, responseClone);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// CRITICAL: Return the response after caching
|
|
139
|
+
return response;
|
|
140
|
+
}).catch(() => {
|
|
141
|
+
// Network failed and not in cache - return a basic error response
|
|
142
|
+
// or optionally return an offline fallback
|
|
143
|
+
console.warn('[SW] Network request failed for:', request.url);
|
|
144
|
+
return new Response('Network unavailable', { status: 503 });
|
|
145
|
+
});
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Handle messages from clients
|
|
151
|
+
self.addEventListener('message', (event) => {
|
|
152
|
+
if (event.data && event.data.type === 'SKIP_WAITING') {
|
|
153
|
+
self.skipWaiting();
|
|
154
|
+
}
|
|
155
|
+
});
|