pliceclogs-og/utils.js
type-two e5b8504bc6 Import pliceclogs as-is — the original Discogs seller extension
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>
2026-07-24 13:47:09 +10:00

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>`;
}
}