Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 74af18f8aa | |||
| f07e9f9c3a | |||
| fecc87cd66 | |||
| 964be309cf | |||
| 1fb42334ae | |||
| 49bc022909 |
22
README.md
22
README.md
@@ -33,7 +33,7 @@ WTFPL licensed.
|
|||||||
1. Open `about:debugging#/runtime/this-firefox`
|
1. Open `about:debugging#/runtime/this-firefox`
|
||||||
2. Click **Load Temporary Add-on...**
|
2. Click **Load Temporary Add-on...**
|
||||||
3. Select this extension's `manifest.json`
|
3. Select this extension's `manifest.json`
|
||||||
4. Visit `https://splice.com/sounds/search/samples`
|
4. Visit any URL under `https://splice.com/sounds` (for example `https://splice.com/sounds/packs/`)
|
||||||
|
|
||||||
## Build Firefox Package
|
## Build Firefox Package
|
||||||
|
|
||||||
@@ -52,3 +52,23 @@ WTFPL licensed.
|
|||||||
4. Select `dist/splirate-firefox-v<version>.xpi`
|
4. Select `dist/splirate-firefox-v<version>.xpi`
|
||||||
|
|
||||||
Note: on standard Firefox, unsigned extensions are temporary via `about:debugging` and are removed on restart.
|
Note: on standard Firefox, unsigned extensions are temporary via `about:debugging` and are removed on restart.
|
||||||
|
|
||||||
|
## Signed Build Download
|
||||||
|
|
||||||
|
- Direct signed XPI: [splirate-1.0.2.xpi](https://git.zetaphor.com/zetaphor/splirate/releases/download/signed/splirate-1.0.2.xpi)
|
||||||
|
- Releases page: [Splirate Releases](https://git.zetaphor.com/zetaphor/splirate/releases)
|
||||||
|
|
||||||
|
## Persistent Install (Private / Unlisted Signing)
|
||||||
|
|
||||||
|
Use Mozilla's unlisted signing to get a persistent signed XPI without public listing.
|
||||||
|
|
||||||
|
1. Create AMO API credentials:
|
||||||
|
- [Firefox Add-on Developer Hub](https://addons.mozilla.org/developers/)
|
||||||
|
2. Export credentials in your shell:
|
||||||
|
- `export AMO_JWT_ISSUER="your-issuer"`
|
||||||
|
- `export AMO_JWT_SECRET="your-secret"`
|
||||||
|
3. Run signing:
|
||||||
|
- `npm run sign`
|
||||||
|
4. Install the signed `.xpi` from:
|
||||||
|
- `dist/signed/`
|
||||||
|
- Firefox: `about:addons` → gear icon → **Install Add-on From File...**
|
||||||
@@ -5,7 +5,7 @@ const FULL_URL_BY_SAMPLE = new Map();
|
|||||||
const HISTORY_KEY = "capturedS3History";
|
const HISTORY_KEY = "capturedS3History";
|
||||||
const MAX_HISTORY_ITEMS = 500;
|
const MAX_HISTORY_ITEMS = 500;
|
||||||
const TARGET_HOST = "splice.com";
|
const TARGET_HOST = "splice.com";
|
||||||
const TARGET_PATH = "/sounds/search/samples";
|
const TARGET_PATH_PREFIX = "/sounds";
|
||||||
const AUDIO_SAMPLE_PATH_RE = /\/audio_samples\//i;
|
const AUDIO_SAMPLE_PATH_RE = /\/audio_samples\//i;
|
||||||
const WAVEFORM_PATH_RE = /\.wv\.json$/i;
|
const WAVEFORM_PATH_RE = /\.wv\.json$/i;
|
||||||
const SCRAMBLED_PATH_RE = /-scrambled\//i;
|
const SCRAMBLED_PATH_RE = /-scrambled\//i;
|
||||||
@@ -18,7 +18,9 @@ function isDomainAllowed(pageUrl) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(pageUrl);
|
const parsed = new URL(pageUrl);
|
||||||
return parsed.protocol === "https:" && parsed.hostname === TARGET_HOST && parsed.pathname === TARGET_PATH;
|
const path = parsed.pathname || "";
|
||||||
|
const inSoundsPath = path === TARGET_PATH_PREFIX || path.startsWith(TARGET_PATH_PREFIX + "/");
|
||||||
|
return parsed.protocol === "https:" && parsed.hostname === TARGET_HOST && inSoundsPath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -409,7 +411,7 @@ function deriveFilename(entry, mode) {
|
|||||||
}
|
}
|
||||||
return `${safeStem}.wav`;
|
return `${safeStem}.wav`;
|
||||||
}
|
}
|
||||||
return `${safeStem}_decoded.mp3`;
|
return `${safeStem}.mp3`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadDirectUrl(url, entry) {
|
async function downloadDirectUrl(url, entry) {
|
||||||
@@ -647,6 +649,12 @@ function serializeTabEntries(state) {
|
|||||||
.map(serializeEntry);
|
.map(serializeEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function clearAllCapturedState() {
|
||||||
|
TAB_CAPTURED.clear();
|
||||||
|
FULL_URL_BY_SAMPLE.clear();
|
||||||
|
await browser.storage.local.remove(HISTORY_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((message, sender) => {
|
browser.runtime.onMessage.addListener((message, sender) => {
|
||||||
if (!message || typeof message.type !== "string") {
|
if (!message || typeof message.type !== "string") {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -724,6 +732,10 @@ browser.runtime.onMessage.addListener((message, sender) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.type === "CLEAR_CAPTURED_STATE") {
|
||||||
|
return clearAllCapturedState().then(() => ({ ok: true }));
|
||||||
|
}
|
||||||
|
|
||||||
if (message.type === "DOWNLOAD_ENTRY") {
|
if (message.type === "DOWNLOAD_ENTRY") {
|
||||||
return resolveAndDownloadEntry(message.entry)
|
return resolveAndDownloadEntry(message.entry)
|
||||||
.then(async (result) => {
|
.then(async (result) => {
|
||||||
@@ -864,3 +876,7 @@ browser.webNavigation.onCommitted.addListener((details) => {
|
|||||||
}
|
}
|
||||||
maybeClearTabState(details.tabId);
|
maybeClearTabState(details.tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
browser.runtime.onStartup.addListener(() => {
|
||||||
|
void clearAllCapturedState();
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const TARGET_SELECTOR = ".playbar-playback-info-bar.playback-row-section.svelte-11gjrk3";
|
const TARGET_SELECTOR = ".playbar-playback-info-bar.playback-row-section.svelte-11gjrk3";
|
||||||
const INJECTED_CONTAINER_ID = "s3-audio-capture-controls";
|
const INJECTED_CONTAINER_ID = "s3-audio-capture-controls";
|
||||||
const TARGET_HOST = "splice.com";
|
const TARGET_HOST = "splice.com";
|
||||||
const TARGET_PATH = "/sounds/search/samples";
|
const TARGET_PATH_PREFIX = "/sounds";
|
||||||
const PROBE_EVENT_SOURCE = "S3_CAPTURE_PROBE";
|
const PROBE_EVENT_SOURCE = "S3_CAPTURE_PROBE";
|
||||||
let knownUrls = new Set();
|
let knownUrls = new Set();
|
||||||
let knownPerformanceUrls = new Set();
|
let knownPerformanceUrls = new Set();
|
||||||
@@ -21,13 +21,20 @@ let currentPlaybackCoverImageUrl = "";
|
|||||||
let lastPlaybackMetaSignature = "";
|
let lastPlaybackMetaSignature = "";
|
||||||
let lastPlaybackMetaApplied = false;
|
let lastPlaybackMetaApplied = false;
|
||||||
|
|
||||||
|
function isUserCancelledError(result) {
|
||||||
|
const error = result && typeof result.error === "string" ? result.error.toLowerCase() : "";
|
||||||
|
return error.includes("canceled by the user") || error.includes("cancelled by the user");
|
||||||
|
}
|
||||||
|
|
||||||
function isDomainAllowed(url) {
|
function isDomainAllowed(url) {
|
||||||
if (typeof url !== "string") {
|
if (typeof url !== "string") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url);
|
||||||
return parsed.protocol === "https:" && parsed.hostname === TARGET_HOST && parsed.pathname === TARGET_PATH;
|
const path = parsed.pathname || "";
|
||||||
|
const inSoundsPath = path === TARGET_PATH_PREFIX || path.startsWith(TARGET_PATH_PREFIX + "/");
|
||||||
|
return parsed.protocol === "https:" && parsed.hostname === TARGET_HOST && inSoundsPath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -595,6 +602,9 @@ function injectControls() {
|
|||||||
const currentButton = createButtonMetaItem("download", "Download current audio", () => {
|
const currentButton = createButtonMetaItem("download", "Download current audio", () => {
|
||||||
browser.runtime.sendMessage({ type: "DOWNLOAD_LATEST" }).then((result) => {
|
browser.runtime.sendMessage({ type: "DOWNLOAD_LATEST" }).then((result) => {
|
||||||
if (!result || !result.ok) {
|
if (!result || !result.ok) {
|
||||||
|
if (isUserCancelledError(result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
window.alert((result && result.error) || "No URL available for download.");
|
window.alert((result && result.error) || "No URL available for download.");
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
|||||||
25
history.css
25
history.css
@@ -11,6 +11,14 @@ body {
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
@@ -27,6 +35,23 @@ p {
|
|||||||
color: #8ea292;
|
color: #8ea292;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#clearHistoryBtn {
|
||||||
|
border: 1px solid #3f2a2a;
|
||||||
|
background: #1c1111;
|
||||||
|
color: #dcb9b9;
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clearHistoryBtn:hover {
|
||||||
|
border-color: #6b4444;
|
||||||
|
color: #f3d7d7;
|
||||||
|
background: #241515;
|
||||||
|
}
|
||||||
|
|
||||||
#historyList {
|
#historyList {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<main class="history-page">
|
<main class="history-page">
|
||||||
<header>
|
<header>
|
||||||
<h1><img class="brand-logo" src="logo.png" alt="Splirate"></h1>
|
<h1><img class="brand-logo" src="logo.png" alt="Splirate"></h1>
|
||||||
|
<button id="clearHistoryBtn" type="button">Clear</button>
|
||||||
</header>
|
</header>
|
||||||
<p id="status">Loading history...</p>
|
<p id="status">Loading history...</p>
|
||||||
<ul id="historyList"></ul>
|
<ul id="historyList"></ul>
|
||||||
|
|||||||
25
history.js
25
history.js
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
const statusEl = document.getElementById("status");
|
const statusEl = document.getElementById("status");
|
||||||
const listEl = document.getElementById("historyList");
|
const listEl = document.getElementById("historyList");
|
||||||
|
const clearHistoryBtn = document.getElementById("clearHistoryBtn");
|
||||||
|
|
||||||
|
function isUserCancelledError(result) {
|
||||||
|
const error = result && typeof result.error === "string" ? result.error.toLowerCase() : "";
|
||||||
|
return error.includes("canceled by the user") || error.includes("cancelled by the user");
|
||||||
|
}
|
||||||
|
|
||||||
function formatMeta(entry) {
|
function formatMeta(entry) {
|
||||||
const packName = entry && entry.packName ? entry.packName : "n/a";
|
const packName = entry && entry.packName ? entry.packName : "n/a";
|
||||||
@@ -104,6 +110,9 @@ function buildItem(entry) {
|
|||||||
entry
|
entry
|
||||||
});
|
});
|
||||||
if (!result || !result.ok) {
|
if (!result || !result.ok) {
|
||||||
|
if (isUserCancelledError(result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
window.alert((result && result.error) || "Download failed.");
|
window.alert((result && result.error) || "Download failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -140,3 +149,19 @@ async function loadHistory() {
|
|||||||
loadHistory().catch(() => {
|
loadHistory().catch(() => {
|
||||||
statusEl.textContent = "Unexpected error while loading history.";
|
statusEl.textContent = "Unexpected error while loading history.";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (clearHistoryBtn) {
|
||||||
|
clearHistoryBtn.addEventListener("click", async () => {
|
||||||
|
const confirmed = window.confirm("Clear all captured samples and history?");
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await browser.runtime.sendMessage({ type: "CLEAR_CAPTURED_STATE" });
|
||||||
|
if (!result || !result.ok) {
|
||||||
|
statusEl.textContent = "Unable to clear captured samples.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await loadHistory();
|
||||||
|
statusEl.textContent = "Captured samples cleared.";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
BIN
icons/icon-128.png
Normal file
BIN
icons/icon-128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
icons/icon-48.png
Normal file
BIN
icons/icon-48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
icons/icon-96.png
Normal file
BIN
icons/icon-96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Splirate",
|
"name": "Splirate",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"description": "Splirate captures Splice sample links and downloads playable audio.",
|
"description": "Splirate captures Splice sample links and downloads playable audio.",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "logo-single.png",
|
"48": "icons/icon-48.png",
|
||||||
"96": "logo-single.png",
|
"96": "icons/icon-96.png",
|
||||||
"128": "logo-single.png"
|
"128": "icons/icon-128.png"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"tabs",
|
"tabs",
|
||||||
"storage",
|
"storage",
|
||||||
"downloads",
|
"downloads",
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"https://splice.com/sounds/search/samples*",
|
"https://splice.com/sounds*",
|
||||||
"*://*.amazonaws.com/*",
|
"*://*.amazonaws.com/*",
|
||||||
"*://*.amazonaws.com.cn/*",
|
"*://*.amazonaws.com.cn/*",
|
||||||
"webNavigation"
|
"webNavigation"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": [
|
"matches": [
|
||||||
"https://splice.com/sounds/search/samples*"
|
"https://splice.com/sounds*"
|
||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"content-script.js"
|
"content-script.js"
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_title": "Splirate",
|
"default_title": "Splirate",
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
"48": "logo-single.png",
|
"48": "icons/icon-48.png",
|
||||||
"96": "logo-single.png"
|
"96": "icons/icon-96.png"
|
||||||
},
|
},
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
},
|
},
|
||||||
@@ -48,10 +48,15 @@
|
|||||||
"history.css",
|
"history.css",
|
||||||
"page-probe.js"
|
"page-probe.js"
|
||||||
],
|
],
|
||||||
"applications": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "splirate@splice.local",
|
"id": "splirate@splice.local",
|
||||||
"strict_min_version": "109.0"
|
"strict_min_version": "109.0",
|
||||||
|
"data_collection_permissions": {
|
||||||
|
"required": [
|
||||||
|
"none"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "splirate",
|
"name": "splirate",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Splirate Firefox extension",
|
"description": "Splirate Firefox extension",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bash scripts/build-firefox.sh",
|
"build": "bash scripts/build-firefox.sh",
|
||||||
"build:firefox": "bash scripts/build-firefox.sh",
|
"build:firefox": "bash scripts/build-firefox.sh",
|
||||||
"build:xpi": "bash scripts/build-firefox.sh"
|
"build:xpi": "bash scripts/build-firefox.sh",
|
||||||
|
"sign": "bash scripts/sign-firefox.sh",
|
||||||
|
"sign:firefox": "bash scripts/sign-firefox.sh"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
popup.css
23
popup.css
@@ -20,6 +20,12 @@ body {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.popup-header h1 {
|
.popup-header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
@@ -43,12 +49,29 @@ body {
|
|||||||
transition: all 120ms ease;
|
transition: all 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#clearBtn {
|
||||||
|
border: 1px solid #3f2a2a;
|
||||||
|
background: #1c1111;
|
||||||
|
color: #dcb9b9;
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
#openHistoryBtn:hover {
|
#openHistoryBtn:hover {
|
||||||
border-color: #3c5342;
|
border-color: #3c5342;
|
||||||
color: #e3f3e7;
|
color: #e3f3e7;
|
||||||
background: #151d18;
|
background: #151d18;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#clearBtn:hover {
|
||||||
|
border-color: #6b4444;
|
||||||
|
color: #f3d7d7;
|
||||||
|
background: #241515;
|
||||||
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
margin: 8px 0 12px;
|
margin: 8px 0 12px;
|
||||||
color: #829589;
|
color: #829589;
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
<main class="popup">
|
<main class="popup">
|
||||||
<header class="popup-header">
|
<header class="popup-header">
|
||||||
<h1><img class="brand-logo" src="logo.png" alt="Splirate"></h1>
|
<h1><img class="brand-logo" src="logo.png" alt="Splirate"></h1>
|
||||||
<button id="openHistoryBtn" type="button">Open Full History</button>
|
<div class="header-actions">
|
||||||
|
<button id="clearBtn" type="button">Clear</button>
|
||||||
|
<button id="openHistoryBtn" type="button">History</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
<p id="status" class="status">Loading captured URLs...</p>
|
<p id="status" class="status">Loading captured URLs...</p>
|
||||||
|
|||||||
25
popup.js
25
popup.js
@@ -3,11 +3,17 @@
|
|||||||
const statusEl = document.getElementById("status");
|
const statusEl = document.getElementById("status");
|
||||||
const listEl = document.getElementById("urlList");
|
const listEl = document.getElementById("urlList");
|
||||||
const openHistoryBtn = document.getElementById("openHistoryBtn");
|
const openHistoryBtn = document.getElementById("openHistoryBtn");
|
||||||
|
const clearBtn = document.getElementById("clearBtn");
|
||||||
|
|
||||||
function setStatus(message) {
|
function setStatus(message) {
|
||||||
statusEl.textContent = message;
|
statusEl.textContent = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUserCancelledError(result) {
|
||||||
|
const error = result && typeof result.error === "string" ? result.error.toLowerCase() : "";
|
||||||
|
return error.includes("canceled by the user") || error.includes("cancelled by the user");
|
||||||
|
}
|
||||||
|
|
||||||
function entryLabel(entry) {
|
function entryLabel(entry) {
|
||||||
return entry && entry.fullUrl ? "full candidate" : "preview fallback";
|
return entry && entry.fullUrl ? "full candidate" : "preview fallback";
|
||||||
}
|
}
|
||||||
@@ -114,6 +120,9 @@ function buildRow(entry) {
|
|||||||
download.addEventListener("click", async () => {
|
download.addEventListener("click", async () => {
|
||||||
const result = await browser.runtime.sendMessage({ type: "DOWNLOAD_ENTRY", entry });
|
const result = await browser.runtime.sendMessage({ type: "DOWNLOAD_ENTRY", entry });
|
||||||
if (!result || !result.ok) {
|
if (!result || !result.ok) {
|
||||||
|
if (isUserCancelledError(result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
window.alert((result && result.error) || "Download failed.");
|
window.alert((result && result.error) || "Download failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -153,6 +162,22 @@ openHistoryBtn.addEventListener("click", async () => {
|
|||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (clearBtn) {
|
||||||
|
clearBtn.addEventListener("click", async () => {
|
||||||
|
const confirmed = window.confirm("Clear all captured samples and history?");
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await browser.runtime.sendMessage({ type: "CLEAR_CAPTURED_STATE" });
|
||||||
|
if (!result || !result.ok) {
|
||||||
|
setStatus("Unable to clear captured samples.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await loadCapturedUrls();
|
||||||
|
setStatus("Captured samples cleared.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
loadCapturedUrls().catch(() => {
|
loadCapturedUrls().catch(() => {
|
||||||
setStatus("Unexpected error while loading URLs.");
|
setStatus("Unexpected error while loading URLs.");
|
||||||
});
|
});
|
||||||
|
|||||||
25
scripts/sign-firefox.sh
Executable file
25
scripts/sign-firefox.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
DIST_DIR="${ROOT_DIR}/dist/signed"
|
||||||
|
|
||||||
|
if [[ -z "${AMO_JWT_ISSUER:-}" || -z "${AMO_JWT_SECRET:-}" ]]; then
|
||||||
|
echo "Missing AMO credentials." >&2
|
||||||
|
echo "Set AMO_JWT_ISSUER and AMO_JWT_SECRET before running this script." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${DIST_DIR}"
|
||||||
|
|
||||||
|
cd "${ROOT_DIR}"
|
||||||
|
|
||||||
|
# Signs as an unlisted add-on (private distribution).
|
||||||
|
npx web-ext sign \
|
||||||
|
--source-dir "${ROOT_DIR}" \
|
||||||
|
--artifacts-dir "${DIST_DIR}" \
|
||||||
|
--channel unlisted \
|
||||||
|
--ignore-files "dist/*" "scripts/*" ".git/*" "*.har" "*.log" "*.tmp"
|
||||||
|
|
||||||
|
echo "Signed XPI artifacts are in:"
|
||||||
|
echo "${DIST_DIR}"
|
||||||
Reference in New Issue
Block a user