esp8266/Nodemcu Service Worker - An unknown error occurred when fetching the script - service-worker

I'm working on a 3d printed wifi car running on a Nodemcu (not too sure of the exact board as i got it for dirt cheap on eBay).
I'm getting the following error:
An unknown error occurred when fetching the script.
Service worker registration failed: DOMException: Failed to register a ServiceWorker for scope ('https://192.168.1.223/') with script ('https://192.168.1.223/service-worker.js'): An unknown error occurred when fetching the script.
When i go to https://192.168.1.223/service-worker.js I do see the code within the file, so it's being presented just fine. It seems that this script is unable to access /service-worker.js as manifest.json is being loaded just fine.
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('service-worker.js')
.then(reg => {
console.log('Service worker registered!', reg);
})
.catch(err => {
console.log('Service worker registration failed: ', err);
});
});
}
</script>
I've tried this in incognito tabs, set all kinds of chrome flags, and cleared my cache/unregistered service worker more times than i can count.
The code on the Nodemcu is as followed(removed car logic):
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServerSecure.h>
#include <ESP8266mDNS.h>
#ifndef STASSID
#define STASSID "XXXXXXX"
#define STAPSK "XXXXXXX"
#endif
const char html_index[] = {index.html code in C-string - see below};
const char offline_html[] = {offline.html code in C-string - did not include below};
const char manifest_js[] = {manifest.json code in C-string - see below};
const char serviceWorker_js[] = {service-worker.js code in C-string - see below};
const char tesla_192_png[] PROGMEM= {**hex for picture**};
const char tesla_512_png[] PROGMEM= {**hex for picture**};
const char* ssid = STASSID;
const char* password = STAPSK;
static const uint8_t x509[] PROGMEM = { #include "x509.h"};
static const uint8_t rsakey[] PROGMEM = { #include "key.h"};
ESP8266WebServerSecure server(443);
void setup(void)
{
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (MDNS.begin("esp8266")) {
Serial.println("MDNS responder started");
}
server.setServerKeyAndCert_P(rsakey, sizeof(rsakey), x509, sizeof(x509));
server.on("/", handleRoot);
server.on("/index.html",handleIndex);
server.on("/service-worker.js",handleServiceWorker);
server.on("/offline/offline.html",handleOffline);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTPS server started");
}
void loop(void)
{
server.handleClient();
MDNS.update();
}
void handleRoot()
{
server.sendHeader("Location","/index.html");
server.send(303);
}
void handleIndex()
{
server.send(200,"text/html",html_index);
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
void handleOffline()
{
server.send(200, "text/html",offline_html);
}
void handleManifest()
{
server.send(200,"application/js",manifest_js);
}
void handleServiceWorker()
{
server.send(200,"application/js",serviceWorker_js);
}
The Index.html is as followed (on the Nodemcu code this is converted to C-string):
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
<script src="https://kit.fontawesome.com/fef9c30a7a.js" crossorigin="anonymous"></script>
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, user-scalable=no">
<head>
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4285f4" />
<title>Tesla Truck Control</title>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"> </script>
<style>
**CSS removed for easy reading - planning on making it a separate file**
</style>
</head>
<body>
**body removed for easy reading**
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('service-worker.js')
.then(reg => {
console.log('Service worker registered!', reg);
})
.catch(err => {
console.log('Service worker registration failed: ', err);
});
});
}
</script>
</body>
</html>
The manifest.json
{
"short_name": "CyberTruck",
"name": "Tesla Cyber Truck",
"icons": [
{
"src": "/images/tesla_192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/images/tesla_512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"display": "fullscreen",
"background_color": "#3367D6"
}
And finally, the actual service-worker.js. This was copied directly from the Google codelabs with minor changes. I haven't gotten around to actually modifying this part. I have tried different versions from empty to fully redirecting to the offline.html page but nothing made a difference.
const cacheName = 'cache-v1';
const precacheResources = [
'/',
'index.html',
'offine/offline.html'
];
self.addEventListener('install', event => {
console.log('Service worker install event!');
event.waitUntil(
caches.open(cacheName)
.then(cache => {
return cache.addAll(precacheResources);
})
);
});
self.addEventListener('activate', event => {
console.log('Service worker activate event!');
});
self.addEventListener('fetch', event => {
console.log('Fetch intercepted for:', event.request.url);
event.respondWith(caches.match(event.request)
.then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request);
})
);
});

Related

How do I display console log in electron app

I'm currently creating a JS Desktop App using Electron. I'm able to get everything functional how I want it, but I want to be able to update the users on certain tasks and also display errors in the app itself.
Is there any way to add a section (terminal if you will) or something similar inside the UI, so I can log things out to the user?
Providing application feedback to your user is as simple as having a statusUpdate function in your main process send
a message (via your preload.js script) to your render process. Within your render process, listen for a message on the
assigned channel and once received, update the content of your DOM element.
main.js (main process)
// Import required electron modules
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
// Import required Node modules
const nodePath = require('path');
// Prevent garbage collection
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
statusTest();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ---
function statusTest() {
let counter = 1;
let message;
setInterval(() => {
statusUpdate(`Status message ${counter} from main process.`);
counter++;
}, 1000);
}
function statusUpdate(message) {
window.webContents.send('statusMessage', message);
}
preload.js (main process)
// Import the necessary Electron modules
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// Exposed protected methods in the render process
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods
'ipcRenderer', {
// From main to render
statusMessage: (message) => {
ipcRenderer.on('statusMessage', message);
}
}
);
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
</head>
<body>
<label for="status">Status: </label>
<textarea id="status" cols="40" rows="10" disabled></textarea>
</body>
<script>
let status = document.getElementById('status');
window.ipcRenderer.statusMessage((event, message) => {
status.value += message + '\n'; // Append message
status.scrollTop = status.scrollHeight; // Show last entry
});
</script>
</html>

sending value url from ipcRender to ipcMain

Hey i am making a project where i want to send data to ipc main
my index.js
const {app, BrowserWindow , ipcMain} = require('electron')
const ejse = require('ejs-electron')
app.on('ready', () => {
ipcMain.on("getUrl",(event,url)=>{
console.log(url);
})
mainWindow = new BrowserWindow({
autoHideMenuBar: true,
icon: __dirname + '/logo.ico',
webPreferences: {
devTools: false,
nodeIntegration:true,
webviewTag:true
}
})
mainWindow.loadURL('file://' + __dirname + '/files/index.ejs');
})
index.ejs contains a anchor tag with value as link
<button id="dw" style="display: none;"><a class="button" id="dwl" onclick="heal()" value="#">Download</a></button>
<script src=download.js></script>
download.js i am able to alert the link but ipcRender not working
const { ipcRenderer } = require("electron");
function heal(){
var url = document.getElementById("dwl").value;
alert(url);
ipcRenderer.send("getUrl",url);
}
i am not able to send url from ejs file to main index.js file alert is working in download.js but some error in sending data to main file console.log is not working
The use of contextBridge within your preload.js script safely exposes API's you approve for use in your render
scripts.
Whist understanding how all this works can be challenging at first, becoming familiar with the below content and the
following code should put you in good stead for a solid understanding of how you can wire up your application.
Context Isolation
contextBridge
Enabling Content Isolation for remote content
Inter-Process Communication
ipcMain
ipcRenderer
webContents - contents.send(channel, ...args)
Some people like to implement concrete functions within their preload.js script(s). In this example,
the preload.js script is only used to configure whitelisted channel names for use between the main process and render
process(es). This keeps your code seperated. IE: Separation of concerns.
Let's set the channel name getUrl within the ipc.render.send array.
preload.js (main process)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'getUrl' // Channel name
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Let's set the webPreferences.contextIsolation value to true and specify the webPreferences.preload path.
webPreferences.nodeIntegration can be set to false, but in doing so webPreferences.contextIsolation should be
set to true to "truly enforce strong isolation and prevent the use of Node primitives".
Lastly, let's listen for a message from the render process on the getUrl channel.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Listen for a message on the 'getUrl' channel
electronIpcMain.on('getUrl', (event, url) => {
console.log(url);
})
This file is fairly standard and easy to understand. Just adapt code for use with .ejs
Added Javascript code in between <script> tags for sake of brevity.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
</head>
<body>
<input type="button" id="dwl" value="#">
</body>
<script>
let button = document.getElementById('dwl');
button.addEventListener('click', () => {
// Send URL on the 'getUrl' channel
window.ipcRender.send('getUrl', button.value);
});
</script>
</html>

Electron.js I can't do the Button action

follow my source code, does anyone know more about Electron around here? I'm starting to study now and I'm trying to create an App that will update some files through an FTP, my problem initially I put a button on the acc.html page, but I can't do its action, I've looked for several examples but with none I can do , to work, it doesn't also arrive in the script that is defined inside the acc.html.
renderer.js
// include the ipc module to communicate with main process.
const ipcRenderer = require('electron').ipcRenderer;
console.log("Chamou O Renderer.js");
document.getElementById('buttonBaixarACCCustoms').open = () => {
//ipcRenderer.send('openFile', {})
console.log("Entrou No IPCRender");
ipcRenderer.send("btnclick", arg); // ipcRender.send will pass the information to main process
}
/*
const btnclick = document.getElementById('buttonBaixarACCCustoms');
btnclick.addEventListener('onclick', function () {
console.log("Entrou No IPCRender");
var arg ="secondparam";
//send the info to main process . we can pass any arguments as second param.
ipcRenderer.send("btnclick", arg); // ipcRender.send will pass the information to main process
});
*/
app.js
const {app, BrowserWindow, ipcMain, } = require('electron');
const url = require("url");
const path = require("path");
const ftp = require("basic-ftp");
let appWindow;
function initWindow() {
appWindow = new BrowserWindow({
width: 1000,
height: 800,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
webviewTag: true
}
})
// Electron Build Path
appWindow.loadURL('file://' + __dirname + '/electron-tabs.html');
/*
appWindow.loadURL(
url.format({
pathname: path.join(__dirname, `/dist/index.html`),
protocol: "file:",
slashes: true
})
);
*/
// Initialize the DevTools.
appWindow.webContents.openDevTools();
appWindow.on('ready-to-show', function () {
appWindow.show();
appWindow.focus();
});
appWindow.on('closed', function () {
appWindow = null
})
}
app.on('ready', initWindow)
// Close when all windows are closed.
app.on('window-all-closed', function () {
// On macOS specific close process
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if (win === null) {
initWindow();
example();
}
})
example()
async function example() {
console.log("Entrou No Example...");
const client = new ftp.Client()
client.ftp.verbose = true
try {
await client.access({
host: "127.0.0.1",
user: "topgunav",
password: "topgunav",
secure: false
})
debugger;
//console.log(await client.list());
//await client.uploadFrom("README.md", "README_FTP.md")
//await client.downloadTo("README_COPY.md", "README_FTP.md")
}
catch(err) {
console.log(err)
}
client.close()
}
//ipcMain.on will receive the “btnclick” info from renderprocess
ipcMain.on("btnclick", function (event, arg) {
console.log("Chegou Aqui No IPCMain");
//create new window
/*
var newWindow = new BrowserWindow({ width: 450, height: 300, show:
false,webPreferences: {webSecurity: false,plugins:
true,nodeIntegration: false} }); // create a new window
var facebookURL = "https://www.facebook.com"; // loading an external url. We can
load our own another html file , like how we load index.html earlier
newWindow.loadURL(facebookURL);
newWindow.show();
*/
// inform the render process that the assigned task finished. Show a message in html
// event.sender.send in ipcMain will return the reply to renderprocess
//event.sender.send("btnclick-task-finished", "yes");
});
acc.html
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body style="margin:0">
<h1 style="text-align: center;">Sicronização De CarSet's</h1>
<div class="mb-3">
<label for="caminhoACCCustoms" class="form-label">Informe O Caminho Da Pasta Customs</label>
<input type="text" class="form-control form-control-lg" type="text" id="caminhoACCCustoms" placeholder="C:\Users\eders\OneDrive\Documentos\Assetto Corsa Competizione\Customs">
</div>
<select class="form-select form-select-lg mb-3" aria-label=".form-select-lg example">
<option selected>Informe A Temporada Que Deseja Sicronizar</option>
<option value="1">T1</option>
<option value="2">T2</option>
<option value="3">T4</option>
<option value="3">T5</option>
</select>
<button type="button" class="btn btn-primary btn-lg" id="buttonBaixarACCCustoms">Baixar</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#popperjs/core#2.10.2/dist/umd/popper.min.js" integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
<script src="../../renderer.js"></script>
</html>
electron-tabs.html
<!DOCTYPE html>
<html>
<head>
<title>SuperSync</title>
<link rel="stylesheet" href="./node_modules/electron-tabs/electron-tabs.css">
</head>
<body style="margin:0">
<div class="etabs-tabgroup">
<div class="etabs-tabs"></div>
<div class="etabs-buttons"></div>
</div>
<div class="etabs-views"></div>
</body>
<script src="./renderer.js"></script>
<script>
const TabGroup = require('electron-tabs');
let tabGroup = new TabGroup();
tabGroup.addTab({
title: "Assetto Corsa Competizione",
src: "./src/acc/acc.html",
visible: true,
active: true
});
</script>
Understanding and implementing Inter-Process Communication
can be tricky. You need a good understanding of
the Process Model
and Context Isolation.
In the below code I have greatly simplified your questions code to show only what is required to get a working IPC
process.
The purpose of this preload.js script is to define 'channel names' and the implementation of IPC between the main
process and render process(es).
This preload.js script can be included in all your window creation scripts. IE: webPreferences: { preload: 'preload.js' }.
preload.js (main process)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'buttonClick' // Channel name
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
This preload.js script can be used in your main process and render process(es) as shown below.
/**
* Render --> Main
* ---------------
* Render: window.ipcRender.send('channel', data); // Data is optional.
* Main: electronIpcMain.on('channel', (event, data) => { methodName(data); })
*
* Main --> Render
* ---------------
* Main: windowName.webContents.send('channel', data); // Data is optional.
* Render: window.ipcRender.receive('channel', (data) => { methodName(data); });
*
* Render --> Main (Value) --> Render
* ----------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
*
* Render --> Main (Promise) --> Render
* ------------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', async (event, data) => {
* return await promiseFunctionName(data)
* .then(() => { return result; })
* });
*/
During window creation make sure to include your preload.js script. Then listen for a message on the 'buttonClick'
channel.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js') // Include the preload.js script
}
});
window.loadFile('index.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Listen for a message on the 'buttonClick' channel
electronIpcMain.on('buttonClick', (event, data) => {
console.log(data); // Testing
})
Lastly, listen for a click event on the button, gather the data and send it via the correct IPC channel (name) to
the main process for processing.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
</head>
<body>
<h1>Sicronização De CarSet's</h1>
<label for="textField">Informe O Caminho Da Pasta Customs</label>
<input id="textField" type="text" placeholder="C:\Custom\Path"><br>
<label for="selectField">Informe A Temporada Que Deseja Sicronizar</label>
<select id="selectField">
<option value="1">T1</option>
<option value="2">T2</option>
<option value="3">T3</option>
<option value="4">T4</option>
</select><br>
<button id="button" type="button">Baixar</button>
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
let data = {
textField: document.getElementById('textField').value,
selectField: document.getElementById('selectField').value
}
console.log(data); // Testing
window.ipcRender.send('buttonClick', data);
});
</script>
</html>

Gapi client javascript 404 Requested entity was not found

I'm using the GAPI v4 endpoint to access google sheets, I've used the example from the Google quickstart and get a 404 error of Requested entity was not found. Why would this be happening, it shouldn't be happening since I've used their example, my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Application Conversion Viewer</title>
<!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
<!-- <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>
<body>
<div id="app">
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" style="display: none;">Authorize</button>
<button id="signout_button" style="display: none;">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-resource#1.5.1"></script>
<script src="https://apis.google.com/js/api.js"></script>
<script>
new Vue({
el: '#app',
mounted () {
var authorizeButton = document.getElementById('authorize_button');
var signoutButton = document.getElementById('signout_button');
/**
* On load, called to load the auth2 library and API client library.
*/
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
function initClient() {
gapi.client.init({
apiKey: 'MY-KEY',
clientId: 'MY-CLIENT-ID',
discoveryDocs: 'https://sheets.googleapis.com/$discovery/rest?version=v4',
scope: 'https://www.googleapis.com/auth/spreadsheets.readonly'
}).then(function () {
console.log('SUCCESS')
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
}, function(error) {
console.log(error)
appendPre(JSON.stringify(error, null, 2));
});
}
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
listMajors();
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
/**
* Append a pre element to the body containing the given message
* as its text node. Used to display the results of the API call.
*
* #param {string} message Text to be placed in pre element.
*/
function appendPre(message) {
var pre = document.getElementById('content');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
/**
* Print the names and majors of students in a sample spreadsheet:
* https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
*/
function listMajors() {
gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}).then(function(response) {
var range = response.result;
if (range.values.length > 0) {
appendPre('Name, Major:');
for (i = 0; i < range.values.length; i++) {
var row = range.values[i];
// Print columns A and E, which correspond to indices 0 and 4.
appendPre(row[0] + ', ' + row[4]);
}
} else {
appendPre('No data found.');
}
}, function(response) {
appendPre('Error: ' + response.result.error.message);
});
}
console.log('LOAD')
handleClientLoad()
}
})
</script>
</body>
</html>
I've tried several approaches to get this to work, neither of which work. The quickstart seems to be wrong: https://developers.google.com/sheets/api/quickstart/js

Apollo with service worker in a Next.js project

I have a NextJS prototype live at https://www.schandillia.com/blog. The data displayed is being pulled off a Strapi installation at https://dev.schandillia.com/graphql. I also have the entire codebase up on Github at https://github.com/amitschandillia/proost/web (the frontend).
I'm using an Apollo client to interface with the graphql source. And also a service worker set up to enable PWA.
Everything's working fine except I'm unable to cache the query results at the browser. The service worker is able to cache everything else but the results of Apollo queries. Is there any way this could be enabled? The objective is:
To be able to use some kind of prefetching of query results at the server.
To be able to have the results cached at the browser via service worker.
The three files relevant to this issues are as follows:
Apollo Setup
// web/apollo/index.js
import { HttpLink } from 'apollo-link-http';
import { withData } from 'next-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
// Set up cache.
const cache = new InMemoryCache();
// Configure Apollo.
const config = {
link: new HttpLink({
uri: 'https://dev.schandillia.com/graphql', // Server URL (must be absolute)
}),
cache,
};
export default withData(config);
Query Component
// web/pages/PostsList.jsx
import ReactMarkdown from 'react-markdown';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
import { Fragment } from 'react';
import Typography from '#material-ui/core/Typography';
import CircularProgress from '#material-ui/core/CircularProgress';
const renderers = {
paragraph: props => <Typography variant="body1" gutterBottom {...props} />
};
const PostsList = ({ data: { error, posts } }) => {
let res = '';
if (error) res = (
<Typography variant="subtitle2" gutterBottom>
Error retrieving posts!
</Typography>
);
if (posts && posts.length) {
if (posts.length !== 0) {
// Payload returned
res = (
<Fragment>
{posts.map(post => (
<div>
<Typography variant="display1" gutterBottom>{post.title}</Typography>
<Typography variant="subtitle1" gutterBottom>{post.secondaryTitle}</Typography>
<Typography variant="subtitle2" gutterBottom>Post #{post._id}</Typography>
<ReactMarkdown source={post.body} renderers={renderers} />
</div>
))}
</Fragment>
);
} else {
res = (
// No payload returned
<Typography variant="subtitle2" gutterBottom>
No posts Found
</Typography>
);
}
} else {
res = (
// Retrieving payload
<CircularProgress />
);
}
return res;
};
const query = gql`
{
posts {
_id
title
secondaryTitle
body
}
}
`;
// The 'graphql' wrapper executes a GraphQL query and makes the results
// available on the 'data' prop of the wrapped component (PostsList)
export default graphql(query, {
props: ({ data }) => ({
data,
}),
})(PostsList);
Blog Page
// web/pages/blog.jsx
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { withStyles } from '#material-ui/core/styles';
import Head from 'next/head';
import Link from 'next/link';
import withRoot from '../lib/withRoot';
import PostsList from '../components/PostsList';
const styles = theme => ({
root: {
textAlign: 'center',
paddingTop: theme.spacing.unit * 20,
},
paragraph: {
fontFamily: 'Raleway',
},
});
class Blog extends PureComponent {
constructor(props) {
super(props);
}
componentDidMount() {
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }
}
render() {
const { classes } = this.props;
const title = 'Blog | Project Proost';
const description = 'This is the blog page';
return (
<Fragment>
<Head>
<title>{ title }</title>
<meta name="description" content={description} key="description" />
</Head>
<div className={classes.root}>
<Typography variant="display1" gutterBottom>
Material-UI
</Typography>
<Typography gutterBottom>
<Link href="/about">
<a>Go to the about page</a>
</Link>
</Typography>
<Typography gutterBottom>
<Link href="/blog">
<a>View posts</a>
</Link>
</Typography>
<Button variant="raised" color="primary">
Super Secret Password
</Button>
<Button variant="raised" color="secondary">
Super Secret Password
</Button>
</div>
<PostsList />
</Fragment>
);
}
}
Blog.propTypes = {
classes: PropTypes.shape({
root: PropTypes.string,
}).isRequired,
};
// Posts.propTypes = {
// classes: PropTypes.object.isRequired,
// };
export default withRoot(withStyles(styles)(Blog));
The service worker in question is as follows (redacted for brevity):
// web/offline/serviceWorker.js
const CACHE_NAME = '1b23369032b1541e45cb8e3d94206923';
const URLS_TO_CACHE = [
'/',
'/about',
'/blog',
'/index',
'apple-touch-icon.png',
'browserconfig.xml',
'favicon-16x16.png',
'favicon-194x194.png',
'favicon-32x32.png',
'favicon.ico',
'manifest.json',
];
// Call install event
self.addEventListener('install', (e) => {
e.waitUntil(
caches
.open(CACHE_NAME)
.then(cache => cache.addAll(URLS_TO_CACHE))
.then(() => self.skipWaiting())
);
});
// Call activate event
self.addEventListener('activate', (e) => {
// remove unwanted caches
e.waitUntil(
caches.keys().then((cacheNames) => {
Promise.all(
cacheNames.map((cache) => {
if (cache !== CACHE_NAME) {
return caches.delete(cache);
}
})
);
})
);
});
// Call fetch event
self.addEventListener('fetch', (e) => {
e.respondWith(
fetch(e.request).catch(() => caches.match(e.request))
);
});
Please advise!

Resources