v0.2
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const browser = globalThis.browser || globalThis.chrome;
|
||||
|
||||
if (window.__takeid_content_script__) return;
|
||||
window.__takeid_content_script__ = true;
|
||||
|
||||
// Cache des items recus du script injecte: [{index, id, code}]
|
||||
let dataCache = [];
|
||||
let lastCacheTimestamp = 0;
|
||||
|
||||
// ----- Injection de injected.js dans le contexte page -----
|
||||
function injectPageScript() {
|
||||
try {
|
||||
const script = document.createElement('script');
|
||||
script.src = browser.runtime.getURL('content/injected.js');
|
||||
script.async = false;
|
||||
script.onload = function () {
|
||||
// Une fois execute, on retire l'element pour ne pas polluer le DOM
|
||||
if (script.parentNode) script.parentNode.removeChild(script);
|
||||
};
|
||||
(document.head || document.documentElement).appendChild(script);
|
||||
} catch (e) {
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
injectPageScript();
|
||||
|
||||
// ----- Reception des donnees de injected.js -----
|
||||
// Note: on ne filtre PAS sur event.source car sur Firefox le content script
|
||||
// utilise un Xray wrapper et event.source !== window meme si c'est la meme page.
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
if (!data || typeof data !== 'object') return;
|
||||
if (data.type !== 'TAKEID_DATA') return;
|
||||
if (!Array.isArray(data.items)) return;
|
||||
|
||||
// Ne pas ecraser le cache avec une reponse vide (certains appels getDataViewList
|
||||
// sont des requetes de count/metadata qui retournent datos=[])
|
||||
if (data.items.length === 0) {
|
||||
console.log('[TakeID] Ignoring empty getDataViewList response (keeping', dataCache.length, 'items in cache)');
|
||||
return;
|
||||
}
|
||||
dataCache = data.items;
|
||||
lastCacheTimestamp = data.timestamp || Date.now();
|
||||
console.log('[TakeID] Cache updated:', dataCache.length, 'items');
|
||||
});
|
||||
|
||||
// ----- Lecture du DOM : trouver les lignes selectionnees -----
|
||||
function normalizeCode(s) {
|
||||
if (s === null || s === undefined) return '';
|
||||
return String(s).trim();
|
||||
}
|
||||
|
||||
function findGridRoot() {
|
||||
// Heuristique : chercher un conteneur de grille
|
||||
const candidates = [
|
||||
'.k-grid',
|
||||
'[role="grid"]',
|
||||
'table.grid',
|
||||
'.smartui-grid',
|
||||
'table'
|
||||
];
|
||||
for (const sel of candidates) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) return el;
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
function getAllDataRows(root) {
|
||||
// Recupere toutes les lignes "data" (potentiellement tr ou divs)
|
||||
let rows = root.querySelectorAll('tr[role="row"]');
|
||||
if (rows && rows.length) return Array.from(rows);
|
||||
|
||||
rows = root.querySelectorAll('tbody > tr');
|
||||
if (rows && rows.length) return Array.from(rows);
|
||||
|
||||
rows = root.querySelectorAll('[role="row"]');
|
||||
if (rows && rows.length) return Array.from(rows);
|
||||
|
||||
rows = root.querySelectorAll('.k-grid-content tr');
|
||||
if (rows && rows.length) return Array.from(rows);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function isHeaderRow(row) {
|
||||
if (!row) return false;
|
||||
if (row.matches && (row.matches('thead tr') || row.matches('[role="row"][aria-rowindex="1"]'))) return true;
|
||||
// ligne sans cellules <td>, juste des <th>
|
||||
if (row.querySelector && row.querySelector('th') && !row.querySelector('td')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function rowIsSelected(row) {
|
||||
if (!row) return false;
|
||||
|
||||
// 1. classes communes
|
||||
const selectedClasses = ['selected', 'k-state-selected', 'k-selected', 'is-selected', 'row-selected', 'active'];
|
||||
if (row.classList) {
|
||||
for (const cls of selectedClasses) {
|
||||
if (row.classList.contains(cls)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. aria-selected
|
||||
if (row.getAttribute && row.getAttribute('aria-selected') === 'true') return true;
|
||||
|
||||
// 3. checkbox cochee a l'interieur de la ligne
|
||||
const cb = row.querySelector && row.querySelector('input[type="checkbox"]');
|
||||
if (cb && cb.checked) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractCodeFromRow(row) {
|
||||
if (!row) return null;
|
||||
|
||||
// 1. data-attribute eventuel
|
||||
const dataAttrs = ['data-code', 'data-key', 'data-id'];
|
||||
for (const a of dataAttrs) {
|
||||
const v = row.getAttribute && row.getAttribute(a);
|
||||
if (v) return normalizeCode(v);
|
||||
}
|
||||
|
||||
// 2. premier lien clickable (souvent la colonne Code dans SmartUI)
|
||||
const link = row.querySelector && row.querySelector('a');
|
||||
if (link && link.textContent) {
|
||||
const t = normalizeCode(link.textContent);
|
||||
if (t) return t;
|
||||
}
|
||||
|
||||
// 3. premiere cellule "non checkbox"
|
||||
const cells = row.querySelectorAll ? row.querySelectorAll('td') : [];
|
||||
for (const cell of cells) {
|
||||
if (cell.querySelector('input[type="checkbox"]')) continue;
|
||||
const t = normalizeCode(cell.textContent);
|
||||
if (t) return t;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function indexOfDataRow(row, allRows) {
|
||||
// Trouve l'index de la ligne dans la sequence des lignes "data" (hors header)
|
||||
const dataRows = allRows.filter((r) => !isHeaderRow(r));
|
||||
return dataRows.indexOf(row);
|
||||
}
|
||||
|
||||
function getSelectedIds() {
|
||||
const root = findGridRoot();
|
||||
const allRows = getAllDataRows(root);
|
||||
const dataRows = allRows.filter((r) => !isHeaderRow(r));
|
||||
|
||||
const selectedRows = dataRows.filter(rowIsSelected);
|
||||
|
||||
// Strategie A : trouver via le code (lookup dans le cache)
|
||||
const cacheByCode = new Map();
|
||||
for (const item of dataCache) {
|
||||
if (item.code) cacheByCode.set(normalizeCode(item.code), item.id);
|
||||
}
|
||||
|
||||
const pairs = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const row of selectedRows) {
|
||||
let id = null;
|
||||
let resolvedCode = null;
|
||||
|
||||
const code = extractCodeFromRow(row);
|
||||
if (code && cacheByCode.has(code)) {
|
||||
id = cacheByCode.get(code);
|
||||
resolvedCode = code;
|
||||
}
|
||||
|
||||
// Strategie B : par index si le code n'a pas matche
|
||||
if (!id) {
|
||||
const idx = dataRows.indexOf(row);
|
||||
if (idx >= 0 && idx < dataCache.length) {
|
||||
id = dataCache[idx].id;
|
||||
resolvedCode = dataCache[idx].code || code || null;
|
||||
}
|
||||
}
|
||||
|
||||
if (id && !seen.has(id)) {
|
||||
seen.add(id);
|
||||
pairs.push({ id, code: resolvedCode });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pairs,
|
||||
ids: pairs.map((p) => p.id),
|
||||
selectedCount: selectedRows.length,
|
||||
cacheSize: dataCache.length,
|
||||
cacheTimestamp: lastCacheTimestamp
|
||||
};
|
||||
}
|
||||
|
||||
// ----- Reception des messages du popup -----
|
||||
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (!msg || typeof msg !== 'object') return false;
|
||||
|
||||
if (msg.type === 'TAKEID_PING') {
|
||||
sendResponse({ ok: true, cacheSize: dataCache.length });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.type === 'GET_SELECTED_IDS') {
|
||||
try {
|
||||
const result = getSelectedIds();
|
||||
sendResponse({ ok: true, ...result });
|
||||
} catch (e) {
|
||||
sendResponse({ ok: false, error: String(e && e.message ? e.message : e) });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,155 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (window.__takeid_injected__) return;
|
||||
window.__takeid_injected__ = true;
|
||||
|
||||
const TARGET_SUBSTR = 'getDataViewList';
|
||||
const LOG_PREFIX = '[TakeID]';
|
||||
|
||||
function extractValue(field) {
|
||||
if (field === null || field === undefined) return null;
|
||||
if (typeof field === 'string') return field;
|
||||
if (typeof field === 'number' || typeof field === 'boolean') return String(field);
|
||||
if (typeof field === 'object') {
|
||||
if (Object.prototype.hasOwnProperty.call(field, 'value')) {
|
||||
return extractValue(field.value);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseAndPost(responseText) {
|
||||
if (!responseText || typeof responseText !== 'string') return;
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
if (!data || !Array.isArray(data.datos)) return;
|
||||
|
||||
const items = data.datos
|
||||
.map((row, index) => {
|
||||
if (!row || typeof row !== 'object') return null;
|
||||
const id = extractValue(row.id);
|
||||
const code = extractValue(row.code);
|
||||
return { index, id, code };
|
||||
})
|
||||
.filter((item) => item && item.id);
|
||||
|
||||
console.log(LOG_PREFIX, 'Posting', items.length, 'items to content script');
|
||||
window.postMessage(
|
||||
{
|
||||
type: 'TAKEID_DATA',
|
||||
items: items,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
'*'
|
||||
);
|
||||
}
|
||||
|
||||
// Verifie si l'URL OU le body contient getDataViewList
|
||||
function isTargetRequest(url, body) {
|
||||
if (url && url.indexOf(TARGET_SUBSTR) !== -1) return true;
|
||||
if (body && typeof body === 'string' && body.indexOf(TARGET_SUBSTR) !== -1) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Monkey-patch XMLHttpRequest ---
|
||||
const originalOpen = XMLHttpRequest.prototype.open;
|
||||
const originalSend = XMLHttpRequest.prototype.send;
|
||||
|
||||
XMLHttpRequest.prototype.open = function (method, url) {
|
||||
try {
|
||||
this.__takeid_url = typeof url === 'string' ? url : (url && url.toString ? url.toString() : '');
|
||||
} catch (e) {
|
||||
this.__takeid_url = '';
|
||||
}
|
||||
return originalOpen.apply(this, arguments);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function (body) {
|
||||
try {
|
||||
// Convertir le body en string pour la recherche
|
||||
let bodyStr = '';
|
||||
if (body) {
|
||||
if (typeof body === 'string') {
|
||||
bodyStr = body;
|
||||
} else if (body instanceof FormData) {
|
||||
// FormData: on ne peut pas facilement chercher dedans, skip
|
||||
bodyStr = '';
|
||||
} else {
|
||||
try { bodyStr = JSON.stringify(body); } catch (e) { bodyStr = ''; }
|
||||
}
|
||||
}
|
||||
|
||||
if (isTargetRequest(this.__takeid_url, bodyStr)) {
|
||||
console.log(LOG_PREFIX, 'Intercepted getDataViewList - URL:', this.__takeid_url);
|
||||
this.addEventListener('load', function () {
|
||||
try {
|
||||
let text = null;
|
||||
if (this.responseType === '' || this.responseType === 'text') {
|
||||
text = this.responseText;
|
||||
} else if (this.responseType === 'json' && this.response) {
|
||||
try {
|
||||
text = JSON.stringify(this.response);
|
||||
} catch (e) {
|
||||
text = null;
|
||||
}
|
||||
}
|
||||
if (text) {
|
||||
console.log(LOG_PREFIX, 'Response received, length:', text.length);
|
||||
parseAndPost(text);
|
||||
} else {
|
||||
console.warn(LOG_PREFIX, 'Could not read response text, responseType:', this.responseType);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(LOG_PREFIX, 'Error reading response:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// swallow
|
||||
}
|
||||
return originalSend.apply(this, arguments);
|
||||
};
|
||||
|
||||
// --- Monkey-patch fetch (au cas ou SmartUI l'utilise) ---
|
||||
if (typeof window.fetch === 'function') {
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function (input, init) {
|
||||
let url = '';
|
||||
try {
|
||||
if (typeof input === 'string') {
|
||||
url = input;
|
||||
} else if (input && typeof input.url === 'string') {
|
||||
url = input.url;
|
||||
}
|
||||
} catch (e) {
|
||||
url = '';
|
||||
}
|
||||
|
||||
const promise = originalFetch.apply(this, arguments);
|
||||
|
||||
if (url && url.indexOf(TARGET_SUBSTR) !== -1) {
|
||||
promise
|
||||
.then((response) => {
|
||||
try {
|
||||
const clone = response.clone();
|
||||
clone
|
||||
.text()
|
||||
.then((text) => parseAndPost(text))
|
||||
.catch(() => {});
|
||||
} catch (e) {
|
||||
// swallow
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user