From 4352696dbaa85920982080d51b4b7524733b58a5 Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Tue, 4 Aug 2026 14:21:17 +0800 Subject: [PATCH] refactor: use Firestore REST API --- _config.yml | 8 +- _vendors.yml | 10 -- layout/_third-party/statistics/firestore.njk | 7 +- source/js/third-party/statistics/firestore.js | 132 +++++++++++------- 4 files changed, 93 insertions(+), 64 deletions(-) diff --git a/_config.yml b/_config.yml index 239ca35..6e7789d 100644 --- a/_config.yml +++ b/_config.yml @@ -807,13 +807,13 @@ plausible: script_url: # https://plausible.io/js/script.js site_domain: # www.example.com -# Another tool to show number of visitors to each article. -# Visit https://console.firebase.google.com/u/0/ to get apiKey and projectId. -# Visit https://firebase.google.com/docs/firestore/ to get more information about firestore. +# Show the number of visitors to each article using the Firestore REST API. +# Visit https://console.firebase.google.com/ to get projectId. +# Visit https://firebase.google.com/docs/firestore/use-rest-api for more information. +# Anonymous requests are authorized by your Firestore Security Rules. firestore: enable: false collection: articles # Required, a string collection name to access firestore database - apiKey: # Required projectId: # Required # Show Views / Visitors of the website / page with busuanzi. diff --git a/_vendors.yml b/_vendors.yml index 326bfce..bf74db6 100644 --- a/_vendors.yml +++ b/_vendors.yml @@ -101,16 +101,6 @@ gitalk_css: version: 1.8.0 file: dist/gitalk.css integrity: sha256-AJnUHL7dBv6PGaeyPQJcgQPDjt/Hn/PvYZde1iqfp8U= -firebase_app: - name: firebase - version: 12.2.1 - file: firebase-app-compat.js - integrity: sha256-d21UgtIcA6c6qwr8nWk6lxs/KrpbWhknQN7qBjWA2Kk= -firebase_firestore: - name: firebase - version: 12.2.1 - file: firebase-firestore-compat.js - integrity: sha256-leHatffkFuVGIg7ABaA5BDsqsEUtktL4tftb3OdQfg0= algolia_search: name: algoliasearch version: 5.36.0 diff --git a/layout/_third-party/statistics/firestore.njk b/layout/_third-party/statistics/firestore.njk index 2bc37a1..b7e5adc 100644 --- a/layout/_third-party/statistics/firestore.njk +++ b/layout/_third-party/statistics/firestore.njk @@ -1,6 +1,7 @@ {%- if theme.firestore.enable %} - {{ next_vendors('firebase_app') }} - {{ next_vendors('firebase_firestore') }} - {{ next_data('firestore', theme.firestore) }} + {{ next_data('firestore', { + collection: theme.firestore.collection, + projectId: theme.firestore.projectId + }) }} {{ next_js('third-party/statistics/firestore.js') }} {%- endif %} diff --git a/source/js/third-party/statistics/firestore.js b/source/js/third-party/statistics/firestore.js index 043df2f..d03bbdb 100644 --- a/source/js/third-party/statistics/firestore.js +++ b/source/js/third-party/statistics/firestore.js @@ -1,57 +1,95 @@ -/* global CONFIG, firebase */ - -firebase.initializeApp({ - apiKey : CONFIG.firestore.apiKey, - projectId: CONFIG.firestore.projectId -}); +/* global CONFIG */ (function() { - const getCount = async (doc, increaseCount) => { - // IncreaseCount will be false when not in article page - const d = await doc.get(); - // Has no data, initialize count - let count = d.exists ? d.data().count : 0; - // If first view this article - if (increaseCount) { - // Increase count - count++; - doc.set({ - count - }); - } - return count; + const database = `projects/${CONFIG.firestore.projectId}/databases/(default)`; + const documents = `${database}/documents`; + const api = `https://firestore.googleapis.com/v1/${documents}`; + + const fetchFirestore = async (path, options) => { + const response = await fetch(`${api}${path}`, options); + if (response.status === 404) return null; + if (!response.ok) throw new Error(`Firestore request failed with status ${response.status}`); + return response.json(); }; - const db = firebase.firestore(); - const articles = db.collection(CONFIG.firestore.collection); + const documentName = title => `${documents}/${CONFIG.firestore.collection}/${title}`; + const getValue = value => Number(value?.integerValue ?? value?.doubleValue ?? 0); + + const getCount = async (title, increaseCount) => { + if (increaseCount) { + const data = await fetchFirestore(':commit', { + method : 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + writes: [{ + transform: { + document : documentName(title), + fieldTransforms: [{ + fieldPath: 'count', + increment: { + integerValue: '1' + } + }] + } + }] + }) + }); + return getValue(data.writeResults[0].transformResults[0]); + } + + const collection = encodeURIComponent(CONFIG.firestore.collection); + const document = await fetchFirestore(`/${collection}/${encodeURIComponent(title)}`); + return getValue(document?.fields?.count); + }; + + const getCounts = async titles => { + if (titles.length === 0) return []; + const data = await fetchFirestore(':batchGet', { + method : 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + documents: titles.map(documentName), + mask : { + fieldPaths: ['count'] + } + }) + }); + const counts = new Map(data.map(result => { + const document = result.found; + return [document?.name ?? result.missing, getValue(document?.fields?.count)]; + })); + return titles.map(title => counts.get(documentName(title)) ?? 0); + }; document.addEventListener('page:loaded', async () => { - - if (CONFIG.page.isPost) { - // Fix issue #118 - // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent - const title = document.querySelector('.post-title').textContent.trim(); - const doc = articles.doc(title); - let increaseCount = CONFIG.hostname === location.hostname; - if (localStorage.getItem(title)) { - increaseCount = false; - } else { - // Mark as visited - localStorage.setItem(title, true); + try { + if (CONFIG.page.isPost) { + // Fix issue #118 + // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent + const title = document.querySelector('.post-title').textContent.trim(); + let increaseCount = CONFIG.hostname === location.hostname; + if (localStorage.getItem(title)) { + increaseCount = false; + } else { + // Mark as visited + localStorage.setItem(title, true); + } + const count = await getCount(title, increaseCount); + document.querySelector('.firestore-visitors-count').innerText = count; + } else if (CONFIG.page.isHome) { + const titles = [...document.querySelectorAll('.post-title')].map(element => element.textContent.trim()); + const counts = await getCounts(titles); + const metas = document.querySelectorAll('.firestore-visitors-count'); + counts.forEach((val, idx) => { + metas[idx].innerText = val; + }); } - const count = await getCount(doc, increaseCount); - document.querySelector('.firestore-visitors-count').innerText = count; - } else if (CONFIG.page.isHome) { - const promises = [...document.querySelectorAll('.post-title')].map(element => { - const title = element.textContent.trim(); - const doc = articles.doc(title); - return getCount(doc); - }); - const counts = await Promise.all(promises); - const metas = document.querySelectorAll('.firestore-visitors-count'); - counts.forEach((val, idx) => { - metas[idx].innerText = val; - }); + } catch (error) { + console.warn('Failed to load Firestore visitor count:', error); } }); })();