mirror of
https://github.com/GetOpenScript/OpenScript.git
synced 2026-09-18 09:45:43 +00:00
Fix OpenScript.fetch JSON IPC serialization for text and binary
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"minimum_chrome_version": "138",
|
"minimum_chrome_version": "138",
|
||||||
"name": "OpenScript",
|
"name": "OpenScript",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"description": "A lightweight user script manager for modern browsers",
|
"description": "A lightweight user script manager for modern browsers",
|
||||||
"action": {
|
"action": {
|
||||||
"default_popup": "src/popup.html",
|
"default_popup": "src/popup.html",
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "openscript",
|
"name": "openscript",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "openscript",
|
"name": "openscript",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lucide": "^0.475.0"
|
"lucide": "^0.475.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openscript",
|
"name": "openscript",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
|
const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
|
||||||
|
|
||||||
|
const toBase64 = buffer => {
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
let bin = '';
|
||||||
|
for (let i = 0; i < bytes.byteLength; i += 8192) {
|
||||||
|
bin += String.fromCharCode(...bytes.subarray(i, Math.min(i + 8192, bytes.byteLength)));
|
||||||
|
}
|
||||||
|
return btoa(bin);
|
||||||
|
};
|
||||||
|
|
||||||
export const runScriptFetch = async (url, options = {}) => {
|
export const runScriptFetch = async (url, options = {}) => {
|
||||||
const res = await fetch(url, options);
|
const res = await fetch(url, options);
|
||||||
const { status, statusText } = res;
|
const { status, statusText } = res;
|
||||||
const headers = [...res.headers.entries()];
|
const headers = [...res.headers.entries()];
|
||||||
const body = NULL_BODY_STATUSES.has(status) ? null : await res.arrayBuffer();
|
if (NULL_BODY_STATUSES.has(status)) {
|
||||||
|
return { status, statusText, headers, url: res.url, body: null };
|
||||||
|
}
|
||||||
|
if (['arraybuffer', 'blob'].includes(options?.responseType)) {
|
||||||
|
const buffer = await res.arrayBuffer();
|
||||||
|
return { status, statusText, headers, url: res.url, base64: toBase64(buffer) };
|
||||||
|
}
|
||||||
|
const body = await res.text();
|
||||||
return { status, statusText, headers, url: res.url, body };
|
return { status, statusText, headers, url: res.url, body };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,7 +42,17 @@ ${code}
|
|||||||
type: '${FETCH_MESSAGE}', url: url.toString(), options: { ...rest, headers, body },
|
type: '${FETCH_MESSAGE}', url: url.toString(), options: { ...rest, headers, body },
|
||||||
});
|
});
|
||||||
if (!response?.ok) throw new TypeError(response?.error || 'OpenScript fetch failed');
|
if (!response?.ok) throw new TypeError(response?.error || 'OpenScript fetch failed');
|
||||||
const resBody = [101, 204, 205, 304].includes(response.status) ? null : response.body;
|
let resBody = null;
|
||||||
|
if (![101, 204, 205, 304].includes(response.status)) {
|
||||||
|
if (response.base64 !== undefined) {
|
||||||
|
const bin = atob(response.base64);
|
||||||
|
const bytes = new Uint8Array(bin.length);
|
||||||
|
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
||||||
|
resBody = bytes.buffer;
|
||||||
|
} else {
|
||||||
|
resBody = response.body ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
const res = new Response(resBody, {
|
const res = new Response(resBody, {
|
||||||
status: response.status, statusText: response.statusText, headers: response.headers,
|
status: response.status, statusText: response.statusText, headers: response.headers,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export const VERSION = '1.0.4';
|
export const VERSION = '1.0.5';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
|||||||
import { runScriptFetch } from '../src/utils/fetch.js';
|
import { runScriptFetch } from '../src/utils/fetch.js';
|
||||||
import { wrapScriptCode } from '../src/utils/userScripts.js';
|
import { wrapScriptCode } from '../src/utils/userScripts.js';
|
||||||
|
|
||||||
test('runScriptFetch performs background fetch and serializes response', async () => {
|
test('runScriptFetch performs background fetch and serializes response text', async () => {
|
||||||
const originalFetch = globalThis.fetch;
|
const originalFetch = globalThis.fetch;
|
||||||
globalThis.fetch = async (url, options) => new Response(JSON.stringify({ hello: 'world' }), {
|
globalThis.fetch = async (url, options) => new Response(JSON.stringify({ hello: 'world' }), {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -16,9 +16,26 @@ test('runScriptFetch performs background fetch and serializes response', async (
|
|||||||
assert.equal(result.status, 200);
|
assert.equal(result.status, 200);
|
||||||
assert.equal(result.statusText, 'OK');
|
assert.equal(result.statusText, 'OK');
|
||||||
assert.ok(result.headers.some(([k, v]) => k === 'content-type' && v === 'application/json'));
|
assert.ok(result.headers.some(([k, v]) => k === 'content-type' && v === 'application/json'));
|
||||||
assert.ok(result.body instanceof ArrayBuffer);
|
assert.equal(typeof result.body, 'string');
|
||||||
const decoded = JSON.parse(new TextDecoder().decode(result.body));
|
assert.deepEqual(JSON.parse(result.body), { hello: 'world' });
|
||||||
assert.deepEqual(decoded, { hello: 'world' });
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runScriptFetch handles binary arraybuffer responses', async () => {
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
const binaryData = new Uint8Array([1, 2, 3, 4, 255]);
|
||||||
|
globalThis.fetch = async () => new Response(binaryData.buffer, {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'content-type': 'application/octet-stream' },
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runScriptFetch('https://api.example.com/binary', { responseType: 'arraybuffer' });
|
||||||
|
assert.equal(result.status, 200);
|
||||||
|
assert.ok(result.base64);
|
||||||
|
assert.equal(typeof result.base64, 'string');
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
}
|
}
|
||||||
@@ -48,14 +65,14 @@ test('runScriptFetch forwards errors on network failure', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('OpenScript.fetch runtime wrapper reconstructs a native Response', async () => {
|
test('OpenScript.fetch runtime wrapper reconstructs a native Response for JSON text', async () => {
|
||||||
const originalChrome = globalThis.chrome;
|
const originalChrome = globalThis.chrome;
|
||||||
const mockPayload = {
|
const mockPayload = {
|
||||||
status: 200,
|
status: 200,
|
||||||
statusText: 'OK',
|
statusText: 'OK',
|
||||||
headers: [['content-type', 'application/json'], ['x-powered-by', 'openscript']],
|
headers: [['content-type', 'application/json'], ['x-powered-by', 'openscript']],
|
||||||
url: 'https://api.example.com/redirected',
|
url: 'https://api.example.com/redirected',
|
||||||
body: new TextEncoder().encode(JSON.stringify({ success: true })).buffer,
|
body: JSON.stringify({ success: true }),
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.chrome = {
|
globalThis.chrome = {
|
||||||
@@ -108,6 +125,52 @@ test('OpenScript.fetch runtime wrapper reconstructs a native Response', async ()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('OpenScript.fetch runtime wrapper decodes base64 binary responses', async () => {
|
||||||
|
const originalChrome = globalThis.chrome;
|
||||||
|
const mockPayload = {
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
headers: [['content-type', 'application/octet-stream']],
|
||||||
|
url: 'https://api.example.com/image.bin',
|
||||||
|
base64: 'AQID/w==',
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.chrome = {
|
||||||
|
runtime: {
|
||||||
|
sendMessage: async () => ({ ok: true, ...mockPayload }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let resolveDone;
|
||||||
|
const donePromise = new Promise(resolve => { resolveDone = resolve; });
|
||||||
|
globalThis.__resolve_done = resolveDone;
|
||||||
|
|
||||||
|
const scriptCode = `
|
||||||
|
const res = await OpenScript.fetch('https://api.example.com/image.bin', { responseType: 'arraybuffer' });
|
||||||
|
const buf = await res.arrayBuffer();
|
||||||
|
globalThis.__resolve_done({
|
||||||
|
ok: res.ok,
|
||||||
|
byteLength: buf.byteLength,
|
||||||
|
bytes: [...new Uint8Array(buf)],
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const wrapped = wrapScriptCode(scriptCode);
|
||||||
|
const fn = new Function(wrapped);
|
||||||
|
fn();
|
||||||
|
const result = await donePromise;
|
||||||
|
assert.deepEqual(result, {
|
||||||
|
ok: true,
|
||||||
|
byteLength: 4,
|
||||||
|
bytes: [1, 2, 3, 255],
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
delete globalThis.__resolve_done;
|
||||||
|
globalThis.chrome = originalChrome;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('OpenScript.fetch throws TypeError when request fails', async () => {
|
test('OpenScript.fetch throws TypeError when request fails', async () => {
|
||||||
const originalChrome = globalThis.chrome;
|
const originalChrome = globalThis.chrome;
|
||||||
globalThis.chrome = {
|
globalThis.chrome = {
|
||||||
|
|||||||
Reference in New Issue
Block a user