fix: guard unavailable APIs for Firefox Android
- Replace broken 'let chrome = browser' shim with proper API fallback - Make contextMenus and notifications calls conditional - Broadcast notify via runtime.sendMessage as fallback for Android - Add try/catch around permissions.request and tabs.query - Fix openOptionsPage fallback using tabs.create on Android
Cette révision appartient à :
+83
-71
@@ -1,64 +1,91 @@
|
|||||||
const str_keys = ["instance_url", "preset", "username", "password"]
|
const str_keys = ["instance_url", "preset", "username", "password"]
|
||||||
const bool_keys = ["showContextMenu"]
|
const bool_keys = ["showContextMenu"]
|
||||||
|
|
||||||
if (typeof chrome === 'undefined') {
|
// Compatibilité Firefox / Chrome
|
||||||
let chrome = browser
|
const api = typeof browser !== 'undefined' ? browser : chrome;
|
||||||
}
|
|
||||||
|
|
||||||
const notify = message => chrome.notifications.create({
|
// --- Détection des capacités ---
|
||||||
"type": "basic",
|
const hasNotifications = !!(api.notifications && api.notifications.create);
|
||||||
"iconUrl": chrome.runtime.getURL("icons/icon-128.png"),
|
const hasContextMenus = !!(api.contextMenus && api.contextMenus.create);
|
||||||
"title": 'YTPTube Extension',
|
|
||||||
"message": message,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onMenuCreated = () => {
|
/**
|
||||||
if (chrome.runtime.lastError) {
|
* Notifie l'utilisateur. Sur Firefox Android, les notifications système
|
||||||
console.log(`error creating menu item. ${chrome.runtime.lastError}`);
|
* ne sont pas disponibles : on envoie un message au popup à la place.
|
||||||
|
*/
|
||||||
|
const notify = message => {
|
||||||
|
if (hasNotifications) {
|
||||||
|
try {
|
||||||
|
api.notifications.create({
|
||||||
|
"type": "basic",
|
||||||
|
"iconUrl": api.runtime.getURL("icons/icon-128.png"),
|
||||||
|
"title": 'YTPTube Extension',
|
||||||
|
"message": message,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Notifications non disponibles:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
syncContextMenu().then(_ => '').catch(console.error);
|
// Toujours diffuser via runtime pour que le popup puisse afficher le message
|
||||||
}
|
try {
|
||||||
|
api.runtime.sendMessage({ command: "notify", message: message }).catch(() => {});
|
||||||
|
} catch (_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Menu contextuel (optionnel sur Android) ---
|
||||||
|
const onMenuCreated = () => {
|
||||||
|
if (api.runtime.lastError) {
|
||||||
|
console.log(`Erreur création menu: ${api.runtime.lastError.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncContextMenu().catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
const shouldShowContextMenu = async () => {
|
const shouldShowContextMenu = async () => {
|
||||||
let item = await chrome.storage.sync.get("showContextMenu");
|
let item = await api.storage.sync.get("showContextMenu");
|
||||||
return 'showContextMenu' in item ? item.showContextMenu : true;
|
return 'showContextMenu' in item ? item.showContextMenu : true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncContextMenu = async () => {
|
const syncContextMenu = async () => {
|
||||||
let showContextMenu = await shouldShowContextMenu();
|
if (!hasContextMenus) return;
|
||||||
chrome.contextMenus.update("send-to-ytptube", { visible: showContextMenu });
|
try {
|
||||||
|
let showContextMenu = await shouldShowContextMenu();
|
||||||
|
api.contextMenus.update("send-to-ytptube", { visible: showContextMenu });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('contextMenus.update indisponible:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasContextMenus) {
|
||||||
|
try {
|
||||||
|
api.contextMenus.create({
|
||||||
|
id: "send-to-ytptube",
|
||||||
|
title: "Send to YTPTube",
|
||||||
|
contexts: ["link"]
|
||||||
|
}, onMenuCreated);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('contextMenus.create indisponible:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.contextMenus.create({
|
// --- Utilitaires ---
|
||||||
id: "send-to-ytptube",
|
const getCurrentUrl = async () => {
|
||||||
title: "Send to YTPTube",
|
const tabs = await api.tabs.query({ currentWindow: true, active: true });
|
||||||
contexts: ["link"]
|
return tabs && tabs[0] ? tabs[0].url : null;
|
||||||
}, onMenuCreated);
|
};
|
||||||
|
|
||||||
const getCurrentUrl = async () => (await chrome.tabs.query({ currentWindow: true, active: true }))[0].url
|
|
||||||
|
|
||||||
const getOption = async key => {
|
const getOption = async key => {
|
||||||
let item = await chrome.storage.sync.get(key);
|
let item = await api.storage.sync.get(key);
|
||||||
if (str_keys.includes(key)) {
|
if (str_keys.includes(key)) return item[key] ?? '';
|
||||||
return item[key] ?? '';
|
if (bool_keys.includes(key)) return item[key] ?? false;
|
||||||
}
|
};
|
||||||
if (bool_keys.includes(key)) {
|
|
||||||
return item[key] ?? false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendRequest = async (path, data) => {
|
const sendRequest = async (path, data) => {
|
||||||
let instanceUrl = await getOption("instance_url");
|
let instanceUrl = await getOption("instance_url");
|
||||||
if (!instanceUrl) {
|
if (!instanceUrl) throw new Error('YTPTube instance url not configured.');
|
||||||
throw new Error('YTPTube instance url not configured.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instanceUrl.endsWith('/')) {
|
if (instanceUrl.endsWith('/')) instanceUrl = instanceUrl.slice(0, -1);
|
||||||
instanceUrl = instanceUrl.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let headers = {};
|
let headers = {};
|
||||||
|
|
||||||
const auth_username = await getOption("username");
|
const auth_username = await getOption("username");
|
||||||
const auth_password = await getOption("password");
|
const auth_password = await getOption("password");
|
||||||
if (auth_username && auth_password) {
|
if (auth_username && auth_password) {
|
||||||
@@ -68,15 +95,16 @@ const sendRequest = async (path, data) => {
|
|||||||
const url = new URL(instanceUrl);
|
const url = new URL(instanceUrl);
|
||||||
url.pathname = path;
|
url.pathname = path;
|
||||||
|
|
||||||
let opts = { method: Object.keys(data).length > 0 ? 'POST' : 'GET', headers: headers };
|
let opts = {
|
||||||
|
method: Object.keys(data).length > 0 ? 'POST' : 'GET',
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
|
||||||
if (data) {
|
if (data && Object.keys(data).length > 0) {
|
||||||
opts.headers['Content-Type'] = 'application/json';
|
opts.headers['Content-Type'] = 'application/json';
|
||||||
opts.body = JSON.stringify(data);
|
opts.body = JSON.stringify(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`Sending to '${instanceUrl}'.`, opts, headers.length > 0 ? 'with auth header' : 'without auth header');
|
|
||||||
|
|
||||||
const req = await fetch(url, opts);
|
const req = await fetch(url, opts);
|
||||||
return { status: req.status, statusText: req.statusText, data: req };
|
return { status: req.status, statusText: req.statusText, data: req };
|
||||||
};
|
};
|
||||||
@@ -84,11 +112,7 @@ const sendRequest = async (path, data) => {
|
|||||||
const sendUrl = async (user_url, preset = null) => {
|
const sendUrl = async (user_url, preset = null) => {
|
||||||
try {
|
try {
|
||||||
const requestData = { url: user_url };
|
const requestData = { url: user_url };
|
||||||
if (preset) {
|
if (preset) requestData.preset = preset;
|
||||||
requestData.preset = preset;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug('Sending request data:', requestData);
|
|
||||||
|
|
||||||
const data = await sendRequest('/api/history', requestData);
|
const data = await sendRequest('/api/history', requestData);
|
||||||
if ([200, 201, 202].includes(data.status)) {
|
if ([200, 201, 202].includes(data.status)) {
|
||||||
@@ -106,35 +130,26 @@ const sendUrl = async (user_url, preset = null) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
chrome.contextMenus.onClicked.addListener(async (info, _) => {
|
// --- Écouteurs ---
|
||||||
if (info.menuItemId !== "send-to-ytptube") {
|
if (hasContextMenus) {
|
||||||
return;
|
api.contextMenus.onClicked.addListener(async (info, _) => {
|
||||||
}
|
if (info.menuItemId !== "send-to-ytptube") return;
|
||||||
if (!info.linkUrl) {
|
if (!info.linkUrl) { notify('No link url found'); return; }
|
||||||
notify('No link url found');
|
const defaultPreset = await getOption("preset");
|
||||||
return;
|
await sendUrl(info.linkUrl, defaultPreset);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get the default preset for context menu actions
|
api.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
const defaultPreset = await getOption("preset");
|
if (message.command !== "send-to-ytptube") return;
|
||||||
await sendUrl(info.linkUrl, defaultPreset);
|
|
||||||
});
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
||||||
if (message.command !== "send-to-ytptube") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
let url = message.url || await getCurrentUrl();
|
let url = message.url || await getCurrentUrl();
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
await notify('No url found');
|
|
||||||
sendResponse({ success: false, message: 'No url found' });
|
sendResponse({ success: false, message: 'No url found' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await sendUrl(url, message.preset);
|
const result = await sendUrl(url, message.preset);
|
||||||
sendResponse(result);
|
sendResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -145,6 +160,3 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+75
-61
@@ -3,23 +3,38 @@
|
|||||||
const str_keys = ["instance_url", "preset", "username", "password"]
|
const str_keys = ["instance_url", "preset", "username", "password"]
|
||||||
let storedOriginPattern = null;
|
let storedOriginPattern = null;
|
||||||
|
|
||||||
if (typeof chrome === 'undefined') {
|
const api = typeof browser !== 'undefined' ? browser : chrome;
|
||||||
let chrome = browser
|
|
||||||
}
|
|
||||||
|
|
||||||
const notify = (message, no_inline) => {
|
const notify = (message, no_inline) => {
|
||||||
if (!no_inline) {
|
if (!no_inline) {
|
||||||
document.querySelector('#error_msg').innerText = message;
|
const el = document.querySelector('#error_msg');
|
||||||
|
if (el) el.innerText = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.notifications.create({
|
// Notifications système optionnelles (non disponibles sur Firefox Android)
|
||||||
"type": "basic",
|
if (api.notifications && api.notifications.create) {
|
||||||
"iconUrl": chrome.runtime.getURL("icons/icon-128.png"),
|
try {
|
||||||
"title": 'YTPTube Extension',
|
api.notifications.create({
|
||||||
"message": message,
|
"type": "basic",
|
||||||
});
|
"iconUrl": api.runtime.getURL("icons/icon-128.png"),
|
||||||
|
"title": 'YTPTube Extension',
|
||||||
|
"message": message,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Notifications indisponibles:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showSaveStatus = (message, isSuccess = true) => {
|
||||||
|
const el = document.querySelector('#save_status');
|
||||||
|
if (!el) return;
|
||||||
|
el.textContent = message;
|
||||||
|
el.className = `notification ${isSuccess ? 'is-success' : 'is-danger'} mt-2`;
|
||||||
|
el.classList.remove('is-hidden');
|
||||||
|
setTimeout(() => el.classList.add('is-hidden'), 3000);
|
||||||
|
};
|
||||||
|
|
||||||
const buildOriginPattern = (instanceUrl) => {
|
const buildOriginPattern = (instanceUrl) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(instanceUrl);
|
const url = new URL(instanceUrl);
|
||||||
@@ -30,32 +45,30 @@ const buildOriginPattern = (instanceUrl) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ensureOriginPermission = async (originPattern) => {
|
const ensureOriginPermission = async (originPattern) => {
|
||||||
if (!originPattern) {
|
if (!originPattern) return false;
|
||||||
return false;
|
if (!api.permissions || !api.permissions.request) return true;
|
||||||
|
try {
|
||||||
|
return await api.permissions.request({ origins: [originPattern] });
|
||||||
|
} catch (e) {
|
||||||
|
// Sur Firefox Android, permissions.request peut échouer si pas depuis un geste utilisateur
|
||||||
|
console.warn('permissions.request échoué:', e);
|
||||||
|
return true; // On continue quand même
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chrome.permissions || !chrome.permissions.request) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await chrome.permissions.request({ origins: [originPattern] });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeOriginPermission = async (originPattern) => {
|
const removeOriginPermission = async (originPattern) => {
|
||||||
if (!originPattern) {
|
if (!originPattern || !api.permissions) return;
|
||||||
return;
|
try {
|
||||||
|
const hasPermission = await api.permissions.contains({ origins: [originPattern] });
|
||||||
|
if (hasPermission) await api.permissions.remove({ origins: [originPattern] });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('removeOriginPermission échoué:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasPermission = await chrome.permissions.contains({ origins: [originPattern] });
|
|
||||||
if (!hasPermission) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await chrome.permissions.remove({ origins: [originPattern] });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testConfig = async () => {
|
const testConfig = async () => {
|
||||||
document.querySelector('#error_msg').innerText = "";
|
const errEl = document.querySelector('#error_msg');
|
||||||
|
if (errEl) errEl.innerText = "";
|
||||||
|
|
||||||
let instance_url = document.querySelector("#instance_url").value.trim();
|
let instance_url = document.querySelector("#instance_url").value.trim();
|
||||||
if (!instance_url) {
|
if (!instance_url) {
|
||||||
@@ -63,10 +76,7 @@ const testConfig = async () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance_url.endsWith('/')) {
|
if (instance_url.endsWith('/')) instance_url = instance_url.slice(0, -1);
|
||||||
instance_url = instance_url.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector("#instance_url").value = instance_url;
|
document.querySelector("#instance_url").value = instance_url;
|
||||||
|
|
||||||
const originPattern = buildOriginPattern(instance_url);
|
const originPattern = buildOriginPattern(instance_url);
|
||||||
@@ -83,57 +93,60 @@ const testConfig = async () => {
|
|||||||
|
|
||||||
let username = document.querySelector("#username").value;
|
let username = document.querySelector("#username").value;
|
||||||
let password = document.querySelector("#password").value;
|
let password = document.querySelector("#password").value;
|
||||||
|
|
||||||
let headers = {}
|
let headers = {}
|
||||||
if (username && password) {
|
if (username && password) {
|
||||||
headers['Authorization'] = 'Basic ' + btoa(username + ':' + password);
|
headers['Authorization'] = 'Basic ' + btoa(username + ':' + password);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const req = await fetch(`${instance_url}/api/ping`, {
|
const req = await fetch(`${instance_url}/api/ping`, { method: 'GET', headers: headers });
|
||||||
method: 'GET',
|
|
||||||
headers: headers
|
|
||||||
});
|
|
||||||
|
|
||||||
if (200 === req.status) {
|
if (200 === req.status) {
|
||||||
notify("Connection successful.", true);
|
notify("Connection successful.", true);
|
||||||
|
showSaveStatus("Connection successful!", true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
let errorMsg = "Unknown error.";
|
||||||
const json = await req.json();
|
try { const json = await req.json(); errorMsg = json.error ?? errorMsg; } catch (_) {}
|
||||||
notify(json.error ?? "Unknown error.");
|
notify(errorMsg);
|
||||||
|
return false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notify("Error: " + e);
|
notify("Error: " + e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
function onError(error) {
|
function onError(error) { console.log(`Error: ${error}`); }
|
||||||
console.log(`Error: ${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_keys.forEach(k => {
|
str_keys.forEach(k => {
|
||||||
chrome.storage.sync.get(k).then(r => document.querySelector(`#${k}`).value = r[k] || "", onError);
|
api.storage.sync.get(k).then(r => {
|
||||||
|
const el = document.querySelector(`#${k}`);
|
||||||
|
if (el) el.value = r[k] || "";
|
||||||
|
}, onError);
|
||||||
});
|
});
|
||||||
|
|
||||||
chrome.storage.sync.get(["showContextMenu", "instance_origin"]).then(r => {
|
api.storage.sync.get(["showContextMenu", "instance_origin"]).then(r => {
|
||||||
document.querySelector("#showContextMenu").checked = r.showContextMenu || false;
|
const el = document.querySelector("#showContextMenu");
|
||||||
|
if (el) el.checked = r.showContextMenu || false;
|
||||||
storedOriginPattern = r.instance_origin || null;
|
storedOriginPattern = r.instance_origin || null;
|
||||||
}, onError);
|
}, onError);
|
||||||
|
|
||||||
|
// Cacher la section "menu contextuel" si non disponible (Firefox Android)
|
||||||
|
const contextMenuSection = document.querySelector('#context-menu-section');
|
||||||
|
if (contextMenuSection && !(api.contextMenus)) {
|
||||||
|
contextMenuSection.style.display = 'none';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("ytptube_options").addEventListener("submit", async e => {
|
document.getElementById("ytptube_options").addEventListener("submit", async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (false === (await testConfig())) return false;
|
||||||
|
|
||||||
if (false === (await testConfig())) {
|
let showContextMenu = false;
|
||||||
return false;
|
const showContextMenuEl = document.querySelector("#showContextMenu");
|
||||||
}
|
if (showContextMenuEl) showContextMenu = showContextMenuEl.checked;
|
||||||
|
|
||||||
let showContextMenu = document.querySelector("#showContextMenu").checked;
|
|
||||||
|
|
||||||
let data = { presets: { presets: ['default'], last_updated: 0 } }
|
let data = { presets: { presets: ['default'], last_updated: 0 } }
|
||||||
|
|
||||||
str_keys.forEach(key => data[key] = document.querySelector(`#${key}`).value)
|
str_keys.forEach(key => data[key] = document.querySelector(`#${key}`).value)
|
||||||
data["showContextMenu"] = showContextMenu;
|
data["showContextMenu"] = showContextMenu;
|
||||||
|
|
||||||
@@ -145,17 +158,18 @@ document.getElementById("ytptube_options").addEventListener("submit", async e =>
|
|||||||
data.instance_origin = newOriginPattern;
|
data.instance_origin = newOriginPattern;
|
||||||
storedOriginPattern = newOriginPattern;
|
storedOriginPattern = newOriginPattern;
|
||||||
|
|
||||||
chrome.storage.sync.set(data);
|
await api.storage.sync.set(data);
|
||||||
chrome.contextMenus.update("send-to-ytptube", { visible: showContextMenu });
|
|
||||||
|
|
||||||
notify("Options saved.", true);
|
if (api.contextMenus && api.contextMenus.update) {
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
try {
|
||||||
window.close()
|
api.contextMenus.update("send-to-ytptube", { visible: showContextMenu });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.warn('contextMenus.update indisponible:', e);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}
|
||||||
|
|
||||||
|
showSaveStatus("Options saved.", true);
|
||||||
|
setTimeout(() => { try { window.close() } catch (e) {} }, 1500);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("test_config").addEventListener("click", async e => {
|
document.getElementById("test_config").addEventListener("click", async e => {
|
||||||
|
|||||||
+57
-61
@@ -2,25 +2,23 @@
|
|||||||
|
|
||||||
const $ = s => document.querySelector(s)
|
const $ = s => document.querySelector(s)
|
||||||
|
|
||||||
if (typeof chrome === 'undefined') {
|
const api = typeof browser !== 'undefined' ? browser : chrome;
|
||||||
let chrome = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOption = async (key, default_data) => {
|
const getOption = async (key, default_data) => {
|
||||||
let item = await chrome.storage.sync.get(key);
|
let item = await api.storage.sync.get(key);
|
||||||
return item[key] || default_data;
|
return item[key] ?? default_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const notify = message => chrome.notifications.create({
|
// Écoute les notifications du background (remplace chrome.notifications sur Android)
|
||||||
"type": "basic",
|
api.runtime.onMessage.addListener((message) => {
|
||||||
"iconUrl": chrome.runtime.getURL("icons/icon-128.png"),
|
if (message.command === 'notify') {
|
||||||
"title": 'YTPTube Extension',
|
// On ne sait pas si c'est succès ou erreur ici, on affiche neutre
|
||||||
"message": message
|
showStatusMessage(message.message, !message.message.toLowerCase().includes('fail') && !message.message.toLowerCase().includes('error'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const showLoading = (isLoading) => {
|
const showLoading = (isLoading) => {
|
||||||
const submitBtn = $('#submit-btn')
|
const submitBtn = $('#submit-btn')
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
submitBtn.disabled = true
|
submitBtn.disabled = true
|
||||||
submitBtn.classList.add('is-loading')
|
submitBtn.classList.add('is-loading')
|
||||||
@@ -35,11 +33,7 @@ const showStatusMessage = (message, isSuccess = true) => {
|
|||||||
statusDiv.textContent = message
|
statusDiv.textContent = message
|
||||||
statusDiv.className = `notification ${isSuccess ? 'is-success' : 'is-danger'}`
|
statusDiv.className = `notification ${isSuccess ? 'is-success' : 'is-danger'}`
|
||||||
statusDiv.classList.remove('is-hidden')
|
statusDiv.classList.remove('is-hidden')
|
||||||
|
setTimeout(() => statusDiv.classList.add('is-hidden'), 4000)
|
||||||
// Hide the message after 3 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
statusDiv.classList.add('is-hidden')
|
|
||||||
}, 3000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#ytptube_popup").addEventListener("submit", async (e) => {
|
$("#ytptube_popup").addEventListener("submit", async (e) => {
|
||||||
@@ -54,107 +48,109 @@ $("#ytptube_popup").addEventListener("submit", async (e) => {
|
|||||||
showLoading(true)
|
showLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await chrome.runtime.sendMessage({ command: 'send-to-ytptube', url: url, preset: preset })
|
const response = await api.runtime.sendMessage({ command: 'send-to-ytptube', url: url, preset: preset })
|
||||||
|
|
||||||
if (response && response.success) {
|
if (response && response.success) {
|
||||||
showStatusMessage('Request sent successfully!', true)
|
showStatusMessage('Request sent successfully!', true)
|
||||||
} else {
|
} else {
|
||||||
showStatusMessage(response?.message || 'Failed to send request.', false)
|
showStatusMessage(response?.message || 'Failed to send request.', false)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showStatusMessage('Error sending request.', false)
|
showStatusMessage('Error sending request: ' + error.message, false)
|
||||||
console.error('Error:', error)
|
console.error('Error:', error)
|
||||||
} finally {
|
} finally {
|
||||||
showLoading(false)
|
showLoading(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCurrentUrl = async () => (await chrome.tabs.query({ currentWindow: true, active: true }))[0].url
|
const getCurrentUrl = async () => {
|
||||||
|
try {
|
||||||
|
const tabs = await api.tabs.query({ currentWindow: true, active: true });
|
||||||
|
return tabs && tabs[0] ? tabs[0].url : null;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('tabs.query failed:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addEventListener('DOMContentLoaded', async _ => {
|
addEventListener('DOMContentLoaded', async _ => {
|
||||||
let url = await getCurrentUrl()
|
let url = await getCurrentUrl()
|
||||||
|
if (url && !url.startsWith('http')) url = ""
|
||||||
if (url && !url.startsWith('http')) {
|
|
||||||
url = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#user_url').value = url || ''
|
$('#user_url').value = url || ''
|
||||||
|
|
||||||
const defaultPreset = await getOption("preset")
|
const defaultPreset = await getOption("preset")
|
||||||
|
|
||||||
const cache = await getOption('presets', {
|
const cache = await getOption('presets', { presets: [], last_updated: 0 })
|
||||||
presets: [],
|
|
||||||
last_updated: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Date.now() - cache.last_updated > 1000 * 60 * 60) {
|
if (Date.now() - cache.last_updated > 1000 * 60 * 60) {
|
||||||
cache.presets = await getPresets()
|
cache.presets = await getPresets()
|
||||||
cache.last_updated = Date.now()
|
cache.last_updated = Date.now()
|
||||||
await chrome.storage.sync.set({ presets: cache })
|
await api.storage.sync.set({ presets: cache })
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = $('#preset')
|
const s = $('#preset')
|
||||||
|
while (s.firstChild) s.removeChild(s.lastChild);
|
||||||
|
|
||||||
// -- remove existing options
|
// S'assurer qu'il y a toujours au moins "default"
|
||||||
while (s.firstChild) {
|
const presets = cache.presets.length > 0 ? cache.presets : ['default'];
|
||||||
s.removeChild(s.lastChild);
|
presets.forEach(p => {
|
||||||
}
|
|
||||||
|
|
||||||
cache.presets.forEach(p => {
|
|
||||||
let option = document.createElement('option');
|
let option = document.createElement('option');
|
||||||
option.value = p;
|
option.value = p;
|
||||||
option.text = p;
|
option.text = p;
|
||||||
// -- set the default preset
|
if (p === defaultPreset) option.selected = true;
|
||||||
if (p === defaultPreset) {
|
|
||||||
option.selected = true;
|
|
||||||
}
|
|
||||||
s.appendChild(option);
|
s.appendChild(option);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
$('#go-to-options').addEventListener('click', function () {
|
$('#go-to-options').addEventListener('click', function () {
|
||||||
if (chrome.runtime.openOptionsPage) {
|
if (api.runtime.openOptionsPage) {
|
||||||
chrome.runtime.openOptionsPage();
|
api.runtime.openOptionsPage();
|
||||||
} else {
|
} else {
|
||||||
window.open(chrome.runtime.getURL('options.html'));
|
// Sur Firefox Android, openOptionsPage peut être absent
|
||||||
|
const optionsUrl = api.runtime.getURL('options.html');
|
||||||
|
api.tabs.create({ url: optionsUrl }).catch(() => window.open(optionsUrl));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#preset').addEventListener('change', async e => await chrome.storage.sync.set({ preset: e.target.value }))
|
$('#preset').addEventListener('change', async e => await api.storage.sync.set({ preset: e.target.value }))
|
||||||
|
|
||||||
const getPresets = async () => {
|
const getPresets = async () => {
|
||||||
let presets = []
|
let presets = []
|
||||||
|
|
||||||
const instanceUrl = await getOption("instance_url");
|
const instanceUrl = await getOption("instance_url");
|
||||||
if (!instanceUrl) {
|
if (!instanceUrl) {
|
||||||
presets.push('YTPTube instance url not configured.');
|
presets.push('default');
|
||||||
return presets
|
return presets
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = {};
|
let headers = {};
|
||||||
|
|
||||||
const auth_username = await getOption('username');
|
const auth_username = await getOption('username');
|
||||||
const auth_password = await getOption('password');
|
const auth_password = await getOption('password');
|
||||||
if (auth_username && auth_password) {
|
if (auth_username && auth_password) {
|
||||||
headers['Authorization'] = 'Basic ' + btoa(`${auth_username}:${auth_password}`);
|
headers['Authorization'] = 'Basic ' + btoa(`${auth_username}:${auth_password}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(instanceUrl);
|
try {
|
||||||
url.pathname = '/api/presets';
|
const url = new URL(instanceUrl);
|
||||||
url.search = 'filter=name';
|
url.pathname = '/api/presets';
|
||||||
|
url.search = 'filter=name';
|
||||||
|
|
||||||
const req = await fetch(url, { method: 'GET', headers: { ...headers, 'Accept': 'application/json' } });
|
const req = await fetch(url, { method: 'GET', headers: { ...headers, 'Accept': 'application/json' } });
|
||||||
if (200 !== req.status) {
|
if (200 !== req.status) {
|
||||||
presets.push('Error fetching presets from YTPTube.');
|
presets.push('default');
|
||||||
return presets
|
return presets
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await req.json()
|
const data = await req.json()
|
||||||
if (data.items && Array.isArray(data.items)) {
|
if (data.items && Array.isArray(data.items)) {
|
||||||
data.items.forEach(preset => presets.push(preset.name))
|
data.items.forEach(preset => presets.push(preset.name))
|
||||||
} else {
|
} else if (Array.isArray(data)) {
|
||||||
data.forEach(preset => presets.push(preset.name))
|
data.forEach(preset => presets.push(preset.name))
|
||||||
|
} else {
|
||||||
|
presets.push('default');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching presets:', e);
|
||||||
|
presets.push('default');
|
||||||
}
|
}
|
||||||
|
|
||||||
return presets
|
return presets
|
||||||
};
|
};
|
||||||
|
|||||||
Référencer dans un nouveau ticket
Bloquer un utilisateur