Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension: added MPD detection if it was fetched from loaded blob script, added Firefox builds #8

Merged
merged 2 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
generate_release_notes: true
files: |
./.output/${{ env.PACKAGE_NAME }}-${{ env.PACKAGE_VERSION }}-chrome.zip
./.output/${{ env.PACKAGE_NAME }}-${{ env.PACKAGE_VERSION }}-firefox.zip

permissions:
contents: write
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,25 @@ npm install azot
npm install -g azot
```

### Browser extension
### Chrome extension

**Developer Mode** needs to be enabled in `chrome://extensions/` page

1. Download archive from [latest release](https://github.com/vitalygashkov/azot/releases/latest)
2. Drag and drop downloaded zip file to `chrome://extensions/` page
2. Go to `chrome://extensions/` page
3. Ensure Developer Mode enabled and then drag and drop downloaded zip file to this page

[Read Google's guide](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked)

### Firefox extension

1. Download archive from [latest release](https://github.com/vitalygashkov/azot/releases/latest)
2. Go to `about:debugging#/runtime/this-firefox` page
3. Click `Load Temporary Add-on` button and choose downloaded zip file

> Temporary add-on is not persistent and will be removed after browser restart

[Read Mozilla's guide](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#installing)

## Usage

Expand Down
97 changes: 96 additions & 1 deletion src/extension/entrypoints/network.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,103 @@ export default defineUnlistedScript(() => {
window.XMLHttpRequest = PatchedXHR;
};

const patchBlobFetch = () => {
window.addEventListener('message', (event) => {
if (
event.data &&
event.data.jsonrpc === '2.0' &&
event.data.method === 'intercepted_response'
) {
const { url, text, headers } = event.data.params;
parsePssh(text, url);
}
});

// Monitor for worker creation
const originalWorker = window.Worker;
window.Worker = function (scriptUrl, options) {
const worker = new originalWorker(scriptUrl, options);

worker.addEventListener('message', (event) => {
if (
event.data &&
event.data.jsonrpc === '2.0' &&
event.data.method === 'intercepted_response'
) {
const { url, text, headers } = event.data.params;
parsePssh(text, url);
}
});

return worker;
};

// Store original blob URL creation
const originalCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function (blob) {
if (
blob instanceof Blob &&
(blob.type.includes('javascript') ||
blob.type.includes('application/x-javascript') ||
blob.type === 'text/javascript' ||
blob.type === '')
) {
// Convert blob to text synchronously
const xhr = new XMLHttpRequest();
const url = originalCreateObjectURL(blob);
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.open('GET', url, false);
xhr.send();
const blobDataBuffer = Uint8Array.from(xhr.response, (c) =>
c.charCodeAt(0),
);
const sourceCode = new TextDecoder().decode(blobDataBuffer);
const injectionCode = `
const originalFetch = fetch;
fetch = async function(...args) {
const response = await originalFetch.apply(this, args);

const size = response.headers.get('content-length');
const MAX_SIZE = 1024 * 1024 * 1; // 1 MB
const isSizeOk = Number(size) < MAX_SIZE;
if (size && !isSizeOk) return response;

const type = response.headers.get('content-type');
const isTypeOk = type?.includes('xml') || type?.includes('dash');
if (!isTypeOk) return response;

const clone = response.clone();
clone.text().then(text => {
const message = {
jsonrpc: '2.0',
method: 'intercepted_response',
params: {
url: response.url,
text,
headers: Object.fromEntries(response.headers.entries())
},
id: Date.now()
};
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
self.postMessage(message);
} else {
window.postMessage(message, '*');
}
});
return response;
};
`;
const modifiedCode = injectionCode + '\n' + sourceCode;
const modifiedBlob = new Blob([modifiedCode], { type: blob.type });
return originalCreateObjectURL.call(URL, modifiedBlob);
}
return originalCreateObjectURL.call(URL, blob);
};
};

patchFetch();
patchXmlHttpRequest();
patchBlobFetch();

console.log('[azot] Fetch interception added');
console.log('[azot] Request interception added');
});