diff --git a/js/.eslintignore b/js/.eslintignore new file mode 100644 index 000000000..cd6466f7f --- /dev/null +++ b/js/.eslintignore @@ -0,0 +1 @@ +espruinotools.js diff --git a/js/.eslintrc.json b/js/.eslintrc.json index 3d7f1d578..92ffbaa09 100644 --- a/js/.eslintrc.json +++ b/js/.eslintrc.json @@ -4,7 +4,13 @@ "sourceType": "script" }, "rules": { - + "indent": [ + "warn", + 2, + { + "SwitchCase": 1 + } + ] }, "env": { "browser": true, diff --git a/js/appinfo.js b/js/appinfo.js index 22d2c2152..6e65bda18 100644 --- a/js/appinfo.js +++ b/js/appinfo.js @@ -59,7 +59,7 @@ var AppInfo = { name : storageFile.name, content : content, evaluate : storageFile.evaluate - }}); + }}); else return Promise.resolve(); })).then(fileContents => { // now we just have a list of files + contents... // filter out empty files @@ -82,7 +82,7 @@ var AppInfo = { var CHUNKSIZE = 4096; storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`; for (var i=CHUNKSIZE;i new Promise((resolve,reject) => { - Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => { - if (result===null) return reject("Connection failed"); - setTimeout(resolve,500); - }); -}), -uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `storage`) - Progress.show({title:`Uploading ${app.name}`,sticky:true}); - return AppInfo.getFiles(app, { - fileGetter : httpGet, - settings : SETTINGS - }).then(fileContents => { - return new Promise((resolve,reject) => { - console.log("uploadApp",fileContents.map(f=>f.name).join(", ")); - var maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1; - var currentBytes = 0; + reset : (opt) => new Promise((resolve,reject) => { + Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => { + if (result===null) return reject("Connection failed"); + setTimeout(resolve,500); + }); + }), + uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `storage`) + Progress.show({title:`Uploading ${app.name}`,sticky:true}); + return AppInfo.getFiles(app, { + fileGetter : httpGet, + settings : SETTINGS + }).then(fileContents => { + return new Promise((resolve,reject) => { + console.log("uploadApp",fileContents.map(f=>f.name).join(", ")); + var maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1; + var currentBytes = 0; - var appInfoFileName = app.id+".info"; - var appInfoFile = fileContents.find(f=>f.name==appInfoFileName); - if (!appInfoFile) reject(`${appInfoFileName} not found`); - var appInfo = JSON.parse(appInfoFile.content); + var appInfoFileName = app.id+".info"; + var appInfoFile = fileContents.find(f=>f.name==appInfoFileName); + if (!appInfoFile) reject(`${appInfoFileName} not found`); + var appInfo = JSON.parse(appInfoFile.content); - // Upload each file one at a time - function doUploadFiles() { + // Upload each file one at a time + function doUploadFiles() { // No files left - print 'reboot' message - if (fileContents.length==0) { - Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { - Progress.hide({sticky:true}); - if (result===null) return reject(""); - resolve(appInfo); - }); - return; - } - var f = fileContents.shift(); - console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`); - // Chould check CRC here if needed instead of returning 'OK'... - // E.CRC32(require("Storage").read(${JSON.stringify(app.name)})) - var cmds = f.cmd.split("\n"); - function uploadCmd() { - if (!cmds.length) return doUploadFiles(); - var cmd = cmds.shift(); - Progress.show({ - min:currentBytes / maxBytes, - max:(currentBytes+cmd.length) / maxBytes}); - currentBytes += cmd.length; - Puck.write(`${cmd};Bluetooth.println("OK")\n`,(result) => { - if (!result || result.trim()!="OK") { + if (fileContents.length==0) { + Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { Progress.hide({sticky:true}); - return reject("Unexpected response "+(result||"")); - } - uploadCmd(); - }, true); // wait for a newline - } - uploadCmd(); - } - // Start the upload - function doUpload() { - Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => { - if (result===null) { - Progress.hide({sticky:true}); - return reject(""); + if (result===null) return reject(""); + resolve(appInfo); + }); + return; } - doUploadFiles(); - }); - } - if (skipReset) { - doUpload(); - } else { + var f = fileContents.shift(); + console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`); + // Chould check CRC here if needed instead of returning 'OK'... + // E.CRC32(require("Storage").read(${JSON.stringify(app.name)})) + var cmds = f.cmd.split("\n"); + function uploadCmd() { + if (!cmds.length) return doUploadFiles(); + var cmd = cmds.shift(); + Progress.show({ + min:currentBytes / maxBytes, + max:(currentBytes+cmd.length) / maxBytes}); + currentBytes += cmd.length; + Puck.write(`${cmd};Bluetooth.println("OK")\n`,(result) => { + if (!result || result.trim()!="OK") { + Progress.hide({sticky:true}); + return reject("Unexpected response "+(result||"")); + } + uploadCmd(); + }, true); // wait for a newline + } + uploadCmd(); + } + // Start the upload + function doUpload() { + Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => { + if (result===null) { + Progress.hide({sticky:true}); + return reject(""); + } + doUploadFiles(); + }); + } + if (skipReset) { + doUpload(); + } else { // reset to ensure we have enough memory to upload what we need to - Comms.reset().then(doUpload, reject) - } - }); - }); -}, -getInstalledApps : () => { - Progress.show({title:`Getting app list...`,sticky:true}); - return new Promise((resolve,reject) => { - Puck.write("\x03",(result) => { - if (result===null) { - Progress.hide({sticky:true}); - return reject(""); - } - Puck.write('\x10Bluetooth.print("[");require("Storage").list(/\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);Bluetooth.print(JSON.stringify(j)+",")});Bluetooth.println("0]")\n', (appList,err) => { - Progress.hide({sticky:true}); - try { - appList = JSON.parse(appList); - // remove last element since we added a final '0' - // to make things easy on the Bangle.js side - appList = appList.slice(0,-1); - } catch (e) { - appList = null; - err = e.toString(); + Comms.reset().then(doUpload, reject) } - if (appList===null) return reject(err || ""); - console.log("getInstalledApps", appList); - resolve(appList); - }, true /* callback on newline */); + }); }); - }); -}, -removeApp : app => { // expects an appid.info structure (i.e. with `files`) - if (!app.files && !app.data) return Promise.resolve(); // nothing to erase - Progress.show({title:`Removing ${app.name}`,sticky:true}); - let cmds = '\x10const s=require("Storage");\n'; - // remove App files: regular files, exact names only - cmds += app.files.split(',').map(file => `\x10s.erase(${toJS(file)});\n`).join(""); - // remove app Data: (dataFiles and storageFiles) - const data = AppInfo.parseDataString(app.data) - const isGlob = f => /[?*]/.test(f) - // regular files, can use wildcards - cmds += data.dataFiles.map(file => { - if (!isGlob(file)) return `\x10s.erase(${toJS(file)});\n`; - const regex = new RegExp(globToRegex(file)) - return `\x10s.list(${regex}).forEach(f=>s.erase(f));\n`; - }).join(""); - // storageFiles, can use wildcards - cmds += data.storageFiles.map(file => { - if (!isGlob(file)) return `\x10s.open(${toJS(file)},'r').erase();\n`; - // storageFiles have a chunk number appended to their real name - const regex = globToRegex(file+'\u0001') - // open() doesn't want the chunk number though - let cmd = `\x10s.list(${regex}).forEach(f=>s.open(f.substring(0,f.length-1),'r').erase());\n` - // using a literal \u0001 char fails (not sure why), so escape it - return cmd.replace('\u0001', '\\x01') - }).join(""); - console.log("removeApp", cmds); - return Comms.reset().then(new Promise((resolve,reject) => { - Puck.write(`\x03\x10E.showMessage('Erasing\\n${app.id}...')${cmds}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { + }, + getInstalledApps : () => { + Progress.show({title:`Getting app list...`,sticky:true}); + return new Promise((resolve,reject) => { + Puck.write("\x03",(result) => { + if (result===null) { + Progress.hide({sticky:true}); + return reject(""); + } + Puck.write('\x10Bluetooth.print("[");require("Storage").list(/\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);Bluetooth.print(JSON.stringify(j)+",")});Bluetooth.println("0]")\n', (appList,err) => { + Progress.hide({sticky:true}); + try { + appList = JSON.parse(appList); + // remove last element since we added a final '0' + // to make things easy on the Bangle.js side + appList = appList.slice(0,-1); + } catch (e) { + appList = null; + err = e.toString(); + } + if (appList===null) return reject(err || ""); + console.log("getInstalledApps", appList); + resolve(appList); + }, true /* callback on newline */); + }); + }); + }, + removeApp : app => { // expects an appid.info structure (i.e. with `files`) + if (!app.files && !app.data) return Promise.resolve(); // nothing to erase + Progress.show({title:`Removing ${app.name}`,sticky:true}); + let cmds = '\x10const s=require("Storage");\n'; + // remove App files: regular files, exact names only + cmds += app.files.split(',').map(file => `\x10s.erase(${toJS(file)});\n`).join(""); + // remove app Data: (dataFiles and storageFiles) + const data = AppInfo.parseDataString(app.data) + const isGlob = f => /[?*]/.test(f) + // regular files, can use wildcards + cmds += data.dataFiles.map(file => { + if (!isGlob(file)) return `\x10s.erase(${toJS(file)});\n`; + const regex = new RegExp(globToRegex(file)) + return `\x10s.list(${regex}).forEach(f=>s.erase(f));\n`; + }).join(""); + // storageFiles, can use wildcards + cmds += data.storageFiles.map(file => { + if (!isGlob(file)) return `\x10s.open(${toJS(file)},'r').erase();\n`; + // storageFiles have a chunk number appended to their real name + const regex = globToRegex(file+'\u0001') + // open() doesn't want the chunk number though + let cmd = `\x10s.list(${regex}).forEach(f=>s.open(f.substring(0,f.length-1),'r').erase());\n` + // using a literal \u0001 char fails (not sure why), so escape it + return cmd.replace('\u0001', '\\x01') + }).join(""); + console.log("removeApp", cmds); + return Comms.reset().then(new Promise((resolve,reject) => { + Puck.write(`\x03\x10E.showMessage('Erasing\\n${app.id}...')${cmds}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { + Progress.hide({sticky:true}); + if (result===null) return reject(""); + resolve(); + }); + })).catch(function(reason) { Progress.hide({sticky:true}); - if (result===null) return reject(""); - resolve(); + return Promise.reject(reason); }); - })).catch(function(reason) { - Progress.hide({sticky:true}); - return Promise.reject(reason); - }); -}, -removeAllApps : () => { - Progress.show({title:"Removing all apps",progess:"animate",sticky:true}); - return new Promise((resolve,reject) => { + }, + removeAllApps : () => { + Progress.show({title:"Removing all apps",progess:"animate",sticky:true}); + return new Promise((resolve,reject) => { // Use write with newline here so we wait for it to finish - Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n', (result,err) => { - Progress.hide({sticky:true}); - if (!result || result.trim()!="OK") return reject(err || ""); - resolve(); - }, true /* wait for newline */); - }); -}, -setTime : () => { - return new Promise((resolve,reject) => { - var d = new Date(); - var tz = d.getTimezoneOffset()/-60 - var cmd = '\x03\x10setTime('+(d.getTime()/1000)+');'; - // in 1v93 we have timezones too - cmd += 'E.setTimeZone('+tz+');'; - cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n"; - Puck.write(cmd, (result) => { - if (result===null) return reject(""); - resolve(); + Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n', (result,err) => { + Progress.hide({sticky:true}); + if (!result || result.trim()!="OK") return reject(err || ""); + resolve(); + }, true /* wait for newline */); }); - }); -}, -disconnectDevice: () => { - var connection = Puck.getConnection(); - - if (!connection) return; - - connection.close(); -}, -watchConnectionChange : cb => { - var connected = Puck.isConnected(); - - //TODO Switch to an event listener when Puck will support it - var interval = setInterval(() => { - if (connected === Puck.isConnected()) return; - - connected = Puck.isConnected(); - cb(connected); - }, 1000); - - //stop watching - return () => { - clearInterval(interval); - }; -}, -listFiles : () => { - return new Promise((resolve,reject) => { - Puck.write("\x03",(result) => { - if (result===null) return reject(""); - //use encodeURIComponent to serialize octal sequence of append files - Puck.eval('require("Storage").list().map(encodeURIComponent)', (files,err) => { - if (files===null) return reject(err || ""); - files = files.map(decodeURIComponent); - console.log("listFiles", files); - resolve(files); + }, + setTime : () => { + return new Promise((resolve,reject) => { + var d = new Date(); + var tz = d.getTimezoneOffset()/-60 + var cmd = '\x03\x10setTime('+(d.getTime()/1000)+');'; + // in 1v93 we have timezones too + cmd += 'E.setTimeZone('+tz+');'; + cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n"; + Puck.write(cmd, (result) => { + if (result===null) return reject(""); + resolve(); }); }); - }); -}, -readFile : (file) => { - return new Promise((resolve,reject) => { - //encode name to avoid serialization issue due to octal sequence - const name = encodeURIComponent(file); - Puck.write("\x03",(result) => { - if (result===null) return reject(""); - //TODO: big files will not fit in RAM. - //we should loop and read chunks one by one. - //Use btoa for binary content - Puck.eval(`btoa(require("Storage").read(decodeURIComponent("${name}"))))`, (content,err) => { - if (content===null) return reject(err || ""); - resolve(atob(content)); - }); - }); - }); -}, -readStorageFile : (filename) => { // StorageFiles are different to normal storage entries - return new Promise((resolve,reject) => { - // Use "\xFF" to signal end of file (can't occur in files anyway) - var fileContent = ""; - var fileSize = undefined; + }, + disconnectDevice: () => { var connection = Puck.getConnection(); - connection.received = ""; - connection.cb = function(d) { - var finished = false; - var eofIndex = d.indexOf("\xFF"); - if (eofIndex>=0) { - finished = true; - d = d.substr(0,eofIndex); - } - fileContent += d; - if (fileSize === undefined) { - var newLineIdx = fileContent.indexOf("\n"); - if (newLineIdx>=0) { - fileSize = parseInt(fileContent.substr(0,newLineIdx)); - console.log("File size is "+fileSize); - fileContent = fileContent.substr(newLineIdx+1); - } - } else { - Progress.show({percent:100*fileContent.length / (fileSize||1000000)}); - } - if (finished) { - Progress.hide(); - connection.received = ""; - connection.cb = undefined; - resolve(fileContent); - } + + if (!connection) return; + + connection.close(); + }, + watchConnectionChange : cb => { + var connected = Puck.isConnected(); + + //TODO Switch to an event listener when Puck will support it + var interval = setInterval(() => { + if (connected === Puck.isConnected()) return; + + connected = Puck.isConnected(); + cb(connected); + }, 1000); + + //stop watching + return () => { + clearInterval(interval); }; - console.log(`Reading StorageFile ${JSON.stringify(filename)}`); - connection.write(`\x03\x10(function() { + }, + listFiles : () => { + return new Promise((resolve,reject) => { + Puck.write("\x03",(result) => { + if (result===null) return reject(""); + //use encodeURIComponent to serialize octal sequence of append files + Puck.eval('require("Storage").list().map(encodeURIComponent)', (files,err) => { + if (files===null) return reject(err || ""); + files = files.map(decodeURIComponent); + console.log("listFiles", files); + resolve(files); + }); + }); + }); + }, + readFile : (file) => { + return new Promise((resolve,reject) => { + //encode name to avoid serialization issue due to octal sequence + const name = encodeURIComponent(file); + Puck.write("\x03",(result) => { + if (result===null) return reject(""); + //TODO: big files will not fit in RAM. + //we should loop and read chunks one by one. + //Use btoa for binary content + Puck.eval(`btoa(require("Storage").read(decodeURIComponent("${name}"))))`, (content,err) => { + if (content===null) return reject(err || ""); + resolve(atob(content)); + }); + }); + }); + }, + readStorageFile : (filename) => { // StorageFiles are different to normal storage entries + return new Promise((resolve,reject) => { + // Use "\xFF" to signal end of file (can't occur in files anyway) + var fileContent = ""; + var fileSize = undefined; + var connection = Puck.getConnection(); + connection.received = ""; + connection.cb = function(d) { + var finished = false; + var eofIndex = d.indexOf("\xFF"); + if (eofIndex>=0) { + finished = true; + d = d.substr(0,eofIndex); + } + fileContent += d; + if (fileSize === undefined) { + var newLineIdx = fileContent.indexOf("\n"); + if (newLineIdx>=0) { + fileSize = parseInt(fileContent.substr(0,newLineIdx)); + console.log("File size is "+fileSize); + fileContent = fileContent.substr(newLineIdx+1); + } + } else { + Progress.show({percent:100*fileContent.length / (fileSize||1000000)}); + } + if (finished) { + Progress.hide(); + connection.received = ""; + connection.cb = undefined; + resolve(fileContent); + } + }; + console.log(`Reading StorageFile ${JSON.stringify(filename)}`); + connection.write(`\x03\x10(function() { var f = require("Storage").open(${JSON.stringify(filename)},"r"); Bluetooth.println(f.getLength()); var l = f.readLine(); while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); } Bluetooth.print("\xFF"); })()\n`,() => { - Progress.show({title:`Reading ${JSON.stringify(filename)}`,percent:0}); - console.log(`StorageFile read started...`); + Progress.show({title:`Reading ${JSON.stringify(filename)}`,percent:0}); + console.log(`StorageFile read started...`); + }); }); - }); -} + } }; diff --git a/js/index.js b/js/index.js index 24aa2e4cb..fd06e7cd0 100644 --- a/js/index.js +++ b/js/index.js @@ -40,7 +40,7 @@ function showChangeLog(appid) { showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{}); } httpGet(`apps/${appid}/ChangeLog`). - then(show).catch(()=>show("No Change Log available")); + then(show).catch(()=>show("No Change Log available")); } function showReadme(appid) { var app = appNameToApp(appid); @@ -319,9 +319,9 @@ function refreshLibrary() { } else if (icon.classList.contains("icon-download")) { handleAppInterface(app); } else if ( button.innerText == String.fromCharCode(0x2661)) { - changeAppFavourite(true, app); + changeAppFavourite(true, app); } else if ( button.innerText == String.fromCharCode(0x2665) ) { - changeAppFavourite(false, app); + changeAppFavourite(false, app); } }); }); @@ -458,13 +458,13 @@ function getAppsToUpdate() { function refreshMyApps() { var panelbody = document.querySelector("#myappscontainer .panel-body"); panelbody.innerHTML = appsInstalled.map(appInstalled => { - var app = appNameToApp(appInstalled.id); - var version = getVersionInfo(app, appInstalled); - var username = "espruino"; - var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); - if(githubMatch) username = githubMatch[1]; - var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`; - return `
+ var app = appNameToApp(appInstalled.id); + var version = getVersionInfo(app, appInstalled); + var username = "espruino"; + var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); + if(githubMatch) username = githubMatch[1]; + var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`; + return `
${escapeHtml(app.name)}
diff --git a/js/pwa.js b/js/pwa.js index ef88993bc..353039c05 100644 --- a/js/pwa.js +++ b/js/pwa.js @@ -2,41 +2,41 @@ const divInstall = document.getElementById('installContainer'); const butInstall = document.getElementById('butInstall'); window.addEventListener('beforeinstallprompt', (event) => { - console.log('👍', 'beforeinstallprompt', event); - // Stash the event so it can be triggered later. - window.deferredPrompt = event; - // Remove the 'hidden' class from the install button container - divInstall.classList.toggle('hidden', false); + console.log('👍', 'beforeinstallprompt', event); + // Stash the event so it can be triggered later. + window.deferredPrompt = event; + // Remove the 'hidden' class from the install button container + divInstall.classList.toggle('hidden', false); }); butInstall.addEventListener('click', () => { - console.log('👍', 'butInstall-clicked'); - const promptEvent = window.deferredPrompt; - if (!promptEvent) { - // The deferred prompt isn't available. - return; - } - // Show the install prompt. - promptEvent.prompt(); - // Log the result - promptEvent.userChoice.then((result) => { - console.log('👍', 'userChoice', result); - // Reset the deferred prompt variable, since - // prompt() can only be called once. - window.deferredPrompt = null; - // Hide the install button. - divInstall.classList.toggle('hidden', true); - }); + console.log('👍', 'butInstall-clicked'); + const promptEvent = window.deferredPrompt; + if (!promptEvent) { + // The deferred prompt isn't available. + return; + } + // Show the install prompt. + promptEvent.prompt(); + // Log the result + promptEvent.userChoice.then((result) => { + console.log('👍', 'userChoice', result); + // Reset the deferred prompt variable, since + // prompt() can only be called once. + window.deferredPrompt = null; + // Hide the install button. + divInstall.classList.toggle('hidden', true); + }); }); window.addEventListener('appinstalled', (event) => { - console.log('👍', 'appinstalled', event); + console.log('👍', 'appinstalled', event); }); /* Only register a service worker if it's supported */ if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('js/service-worker.js'); + navigator.serviceWorker.register('js/service-worker.js'); } /** @@ -46,8 +46,8 @@ if ('serviceWorker' in navigator) { * if the page isn't served over HTTPS, the service worker won't load. */ if (window.location.protocol === 'http:' && window.location.hostname!="localhost") { - const requireHTTPS = document.getElementById('requireHTTPS'); - const link = requireHTTPS.querySelector('a'); - link.href = window.location.href.replace('http://', 'https://'); - requireHTTPS.classList.remove('hidden'); + const requireHTTPS = document.getElementById('requireHTTPS'); + const link = requireHTTPS.querySelector('a'); + link.href = window.location.href.replace('http://', 'https://'); + requireHTTPS.classList.remove('hidden'); } diff --git a/js/service-worker.js b/js/service-worker.js index 2581bc9d1..860d126f9 100644 --- a/js/service-worker.js +++ b/js/service-worker.js @@ -1,14 +1,14 @@ self.addEventListener('install', (event) => { - console.log('👷', 'install', event); - self.skipWaiting(); + console.log('👷', 'install', event); + self.skipWaiting(); }); self.addEventListener('activate', (event) => { - console.log('👷', 'activate', event); - return self.clients.claim(); + console.log('👷', 'activate', event); + return self.clients.claim(); }); self.addEventListener('fetch', function(event) { - // console.log('👷', 'fetch', event); - event.respondWith(fetch(event.request)); + // console.log('👷', 'fetch', event); + event.respondWith(fetch(event.request)); }); \ No newline at end of file