Snapshot of the working tree exactly as it stood, no edits. This is the predecessor
PRICEGOD was rewritten from ("kept intact, untouched" per pricegod/README.md), and it
is still the only place the DYMO scale is actually implemented —
rfid-daemon/index.js:1068-1265: HID discovery, parseScaleReport, one-shot read, and a
streaming /weight + /scale/start|stop session whose JSON shape PRICEGOD's daemon.js
already speaks.
Preserved verbatim on purpose (hence -og), including the known bug: DYMO_PIDS at
index.js:1082 is [0x8003, 0x8004], so it cannot see the bench M25 (0x8009). Fix that
in whatever daemon inherits the scale, not here.
node_modules stays ignored (22M of the 24M tree). No credentials in the import: the
two PEM markers in utils.js/sheets.js only strip headers off a key read from settings,
and mrpadmin / johnking are an SSH and a Postgres username, both key/trust auth.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
142 lines
5.2 KiB
JavaScript
142 lines
5.2 KiB
JavaScript
// Cache-busting comment - Updated to fix ES6 syntax issues - v2.0
|
|
const fetchWithRetry = async (url, options, maxRetries = 3) => {
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
const response = await fetch(url, options);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const text = await response.text();
|
|
if (!text) {
|
|
throw new Error('Empty response received');
|
|
}
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch (e) {
|
|
console.error('JSON Parse Error:', e, 'Response:', text);
|
|
throw e;
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Attempt ${i + 1} failed:`, error);
|
|
if (i === maxRetries - 1) throw error;
|
|
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // Exponential backoff
|
|
}
|
|
}
|
|
};
|
|
|
|
// Helper function for getServiceAccountToken
|
|
async function getGoogleSettings() {
|
|
return new Promise((resolve) => {
|
|
chrome.storage.local.get(['googleSettings'], (result) => {
|
|
// console.log('Retrieved Google settings:', result.googleSettings);
|
|
resolve(result.googleSettings || null);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getServiceAccountToken() {
|
|
try {
|
|
const googleSettings = await getGoogleSettings();
|
|
if (!googleSettings || !googleSettings.clientEmail || !googleSettings.privateKey) {
|
|
throw new Error('Google credentials not configured.');
|
|
}
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const jwtHeader = {
|
|
alg: 'RS256',
|
|
typ: 'JWT'
|
|
};
|
|
|
|
const jwtPayload = {
|
|
iss: googleSettings.clientEmail,
|
|
sub: googleSettings.clientEmail,
|
|
aud: 'https://oauth2.googleapis.com/token',
|
|
scope: 'https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file',
|
|
exp: now + 3600,
|
|
iat: now
|
|
};
|
|
|
|
const base64Header = btoa(JSON.stringify(jwtHeader)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
const base64Payload = btoa(JSON.stringify(jwtPayload)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
|
|
const signInput = `${base64Header}.${base64Payload}`;
|
|
|
|
const pemHeader = "-----BEGIN PRIVATE KEY-----";
|
|
const pemFooter = "-----END PRIVATE KEY-----";
|
|
const pemContents = googleSettings.privateKey
|
|
.replace(/\\n/g, '\n')
|
|
.replace(pemHeader, '')
|
|
.replace(pemFooter, '')
|
|
.replace(/\s/g, '');
|
|
|
|
const binaryKey = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0));
|
|
|
|
const cryptoKey = await window.crypto.subtle.importKey(
|
|
'pkcs8',
|
|
binaryKey,
|
|
{
|
|
name: 'RSASSA-PKCS1-v1_5',
|
|
hash: { name: 'SHA-256' },
|
|
},
|
|
false,
|
|
['sign']
|
|
);
|
|
|
|
const signature = await window.crypto.subtle.sign(
|
|
'RSASSA-PKCS1-v1_5',
|
|
cryptoKey,
|
|
new TextEncoder().encode(signInput)
|
|
);
|
|
|
|
const jwt = `${signInput}.${btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')}`;
|
|
|
|
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: new URLSearchParams({
|
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
assertion: jwt
|
|
})
|
|
});
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
|
|
if (!tokenResponse.ok) {
|
|
console.error('Token Error:', tokenData);
|
|
throw new Error(`Token error: ${tokenData.error_description || tokenData.error}`);
|
|
}
|
|
|
|
return tokenData.access_token;
|
|
} catch (error) {
|
|
console.error('Detailed token error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function handlePageAction(pageType, releaseId, displayContainerId, updateButtonId, dataAction, updateFunction) {
|
|
try {
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
await chrome.scripting.executeScript({
|
|
target: { tabId: tabs[0].id },
|
|
files: ['content.js']
|
|
});
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
const response = await chrome.tabs.sendMessage(tabs[0].id, {
|
|
action: dataAction,
|
|
releaseId: releaseId
|
|
});
|
|
|
|
const displayContainer = document.getElementById(displayContainerId);
|
|
if (response) {
|
|
updateFunction(response, displayContainer, updateButtonId, releaseId);
|
|
} else {
|
|
displayContainer.innerHTML = `<div class="data-row">Error fetching ${pageType} data</div>`;
|
|
}
|
|
} catch (error) {
|
|
document.getElementById(displayContainerId).innerHTML = `<div class="data-row">Error: ${error.message || `Failed to fetch ${pageType} data`}</div>`;
|
|
}
|
|
} |