From 928292b320d28cb4ba01e71b154fb934b3f7d05a Mon Sep 17 00:00:00 2001 From: Artem Denysov Date: Mon, 17 Mar 2025 18:23:53 +0200 Subject: [PATCH 1/2] init --- .gitignore | 2 + generate-token.mjs | 21 ++++++ index.html | 16 +++++ main.js | 155 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 9 +++ 5 files changed, 203 insertions(+) create mode 100644 .gitignore create mode 100644 generate-token.mjs create mode 100644 index.html create mode 100644 main.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6582234 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Local Netlify folder +.netlify diff --git a/generate-token.mjs b/generate-token.mjs new file mode 100644 index 0000000..715a00e --- /dev/null +++ b/generate-token.mjs @@ -0,0 +1,21 @@ +export default async (req, context) => { + console.log(123); + try { + const response = await fetch( + "https://app.netlify.com/access-control/generate-access-control-token", + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.NETLIFY_ACCESS_CONTROL_TOKEN}`, + }, + } + ); + const data = await response.json(); + console.log(data); + return new Response(JSON.stringify(data)); + } catch (error) { + console.error(error); + return new Response("Error generating token", { status: 500 }); + } +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..b5aae22 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + Document + + + +

Netlify Logs

+ + + +
+ + diff --git a/main.js b/main.js new file mode 100644 index 0000000..1fb9fe8 --- /dev/null +++ b/main.js @@ -0,0 +1,155 @@ +let cachedAccessControlToken = null; + +async function getAccessControlToken() { + if (cachedAccessControlToken) { + return cachedAccessControlToken; + } + + const tokenResp = await fetch(".netlify/functions/generate-token", { + credentials: "include", + }); + + if (tokenResp.status !== 200) { + cachedAccessControlToken = null; + throw new Error("failed to access control token for user"); + } + + const { accessControlToken } = await tokenResp.json(); + + cachedAccessControlToken = accessControlToken; + + return accessControlToken; +} + +class NetlifyLogsService { + constructor(options = {}) { + this.options = options; + this.logs = []; + this.shouldReconnect = true; + // this.ws = this.connect(); + } + + destroy() { + this.shouldReconnect = false; + window.clearInterval(this.reconnectTimeout); + this.notifyLogsUpdated.cancel(); + this.ws.close(); + } + + connect(options) { + this.ws = new WebSocket("wss://socketeer.services.netlify.com/build/logs"); + this.ws.addEventListener("open", () => { + getAccessControlToken() + .then((accessToken) => { + this.ws.send( + JSON.stringify({ + deploy_id: options.deployId || this.options.deployId, + site_id: options.siteId || this.options.siteId, + access_token: accessToken, + }) + ); + }) + .catch((error) => { + console.error( + "NetlifyLogsService failed to get access control token", + error + ); + }); + }); + this.ws.addEventListener("message", (event) => { + try { + const data = JSON.parse(event.data); + const ts = new Date(data.ts).getTime(); + + if (data.type === "error") { + throw data; + } + + this.logs ??= []; + this.logs.push({ + id: `${ts}${this.logs.length}`, + timestamp: ts, + message: data.message, + }); + this.notifyLogsUpdated(); + } catch (e) { + if (e?.type === "error" && e.status === 401) { + console.error("NetlifyLogsService no permission"); + this.options.onForbidden?.(); + return; + } + console.error(`NetlifyLogsService can't decode socket message`, e); + } + }); + this.ws.addEventListener("close", () => { + console.info(`NetlifyLogsService socket closed`); + if (this.shouldReconnect) { + this.reconnectTimeout = window.setTimeout( + () => this.connect(), + this.options.reconnect ?? 1000 + ); + } + }); + this.ws.addEventListener("error", (error) => { + console.error(`NetlifyLogsService socket got error`, error); + this.ws.close(); + }); + return this.ws; + } + + notifyLogsUpdated = (function () { + let timeout; + return function () { + clearTimeout(timeout); + timeout = setTimeout(() => { + this.options.onLogsUpdated?.([...(this.logs ?? [])]); + }, 250); + }; + })(); +} + +const logsService = new NetlifyLogsService({ + deployId: "your-deploy-id", + siteId: "your-site-id", + onLogsUpdated: (logs) => { + // Handle updated logs + }, + onForbidden: () => { + // Handle forbidden access + }, + reconnect: 2000, // Optional reconnect timeout in ms +}); + +// Initialize the logs service +const netlifyLogs = new NetlifyLogsService({ + onLogsUpdated: (logs) => { + // Update UI with new logs + const logContainer = document.querySelector("#logs"); + if (logContainer) { + logContainer.innerHTML = logs + .map( + (log) => ` +
+ ${new Date( + log.timestamp + ).toLocaleTimeString()} + ${log.message} +
+ ` + ) + .join(""); + } + }, + onForbidden: () => { + console.error("Access forbidden - please check your credentials"); + // Optionally show error message to user + alert("Unable to access logs - permission denied"); + }, + reconnect: 3000, +}); + +document.getElementById("connect").addEventListener("click", () => { + const deployId = document.getElementById("deployId").value; + const siteId = document.getElementById("siteId").value; + netlifyLogs.connect({ deployId, siteId }); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..e7a523d --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "netlify-logs", + "version": "1.0.0", + "description": "A simple tool to view Netlify logs", + "main": "index.html", + "scripts": { + "start": "netlify serve" + } +} From e8c823a0574dcc21541ae90bab6ecfbbd25cf424 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 16:31:13 +0000 Subject: [PATCH 2/2] Add renovate.json --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..b871545 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>netlify/renovate-config" + ] +}