From b53a3a4ec01c4818edde73a989f46919f478e447 Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 12:28:57 +0200 Subject: [PATCH 01/11] Added test suites: Array Buffer, Bytes, constructor-endings, constructor, text --- apps/test-suite/TestModules.js | 1 + apps/test-suite/tests/Blob.ts | 760 ++++++++++++++++++ .../results.bin | 1 + .../classes/classes_dex/classes.dex | Bin 0 -> 45756 bytes .../results.bin | 1 + .../classes/classes_dex/classes.dex | Bin 0 -> 45772 bytes .../main/java/expo/modules/blob/BlobModule.kt | 4 +- packages/expo-blob/build/BlobModule.d.ts | 23 +- packages/expo-blob/build/BlobModule.d.ts.map | 2 +- packages/expo-blob/build/BlobModule.js | 29 +- packages/expo-blob/build/BlobModule.js.map | 2 +- .../expo-blob/build/BlobModule.types.d.ts | 3 +- .../expo-blob/build/BlobModule.types.d.ts.map | 2 +- .../expo-blob/build/BlobModule.types.js.map | 2 +- packages/expo-blob/build/utils.d.ts | 17 + packages/expo-blob/build/utils.d.ts.map | 1 + packages/expo-blob/build/utils.js | 24 + packages/expo-blob/build/utils.js.map | 1 + packages/expo-blob/src/BlobModule.ts | 10 +- 19 files changed, 858 insertions(+), 25 deletions(-) create mode 100644 apps/test-suite/tests/Blob.ts create mode 100644 packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin create mode 100644 packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/transformed/classes/classes_dex/classes.dex create mode 100644 packages/expo-blob/android/build/.transforms/8f8a6c2346678f20ad35dd488b88f140/results.bin create mode 100644 packages/expo-blob/android/build/.transforms/8f8a6c2346678f20ad35dd488b88f140/transformed/classes/classes_dex/classes.dex create mode 100644 packages/expo-blob/build/utils.d.ts create mode 100644 packages/expo-blob/build/utils.d.ts.map create mode 100644 packages/expo-blob/build/utils.js create mode 100644 packages/expo-blob/build/utils.js.map diff --git a/apps/test-suite/TestModules.js b/apps/test-suite/TestModules.js index 2b622665646961..4db10c6088cd3b 100644 --- a/apps/test-suite/TestModules.js +++ b/apps/test-suite/TestModules.js @@ -76,6 +76,7 @@ export function getTestModules() { if (['android', 'ios'].includes(Platform.OS)) { modules.push(require('./tests/FileSystemNext')); + modules.push(require('./tests/Blob')); } if (Platform.OS === 'android') { diff --git a/apps/test-suite/tests/Blob.ts b/apps/test-suite/tests/Blob.ts new file mode 100644 index 00000000000000..69665a7c0b0520 --- /dev/null +++ b/apps/test-suite/tests/Blob.ts @@ -0,0 +1,760 @@ +import {ExpoBlob as Blob} from "expo-blob" +import { Platform } from 'expo-modules-core'; + +export const name = 'Blob'; + +export async function test({ describe, it, expect, jasmine }) { + const test_blob = (fn, expectations) => { + var expected = expectations.expected, + type = expectations.type, + desc = expectations.desc; + + it(desc, async (t) => { + var blob = fn(); + expect(blob instanceof Blob).toBeTruthy(); + expect(blob instanceof File).toBeFalsy(); + expect(blob.type).toEqual(type); + expect(blob.size).toBe(expected.length); + + const text = await blob.text(); + const text1 = blob.syncText(); + expect(text).toEqual(expected); + expect(text).toEqual(text1); + }); + } + const test_blob_binary = async (fn, expectations) => { + var expected = expectations.expected, + type = expectations.type, + desc = expectations.desc; + it(desc, async () => { + var blob = fn(); + expect(blob instanceof Blob).toBeTruthy(); + expect(blob instanceof File).toBeFalsy(); + expect(blob.type).toBe(type); + expect(blob.size).toBe(expected.length); + + const ab = await blob.arrayBuffer(); + expect(ab instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(ab)).toEqual(expected); + }) + + } + describe('Blob', async () => { + describe('Blob creation', () => { + it('Empty blob', () => { + const blob = new Blob([]); + expect(blob).toBeTruthy(); + }); + it('String blob', () => { + const blob = new Blob(["ab", "cd"]); + expect(blob).toBeTruthy(); + }); + it('TypedArray blob', () => { + const blob = new Blob([Int32Array.from([-1, 2]), Uint8Array.from([1, 2, 0])]); + expect(blob).toBeTruthy(); + }); + it('Blob blob', () => { + const blob = new Blob([new Blob([]), new Blob(["a", "b"])]); + expect(blob).toBeTruthy(); + }) + it('Mixed blob', () => { + const blob0 = new Blob([Int32Array.from([-1, 2]), Uint8Array.from([1, 2, 0])]); + const blob1 = new Blob(["ab", "cd"]); + const blob2 = new Blob(["a", new Blob([new Blob(["b"])])]) + const blob = new Blob([blob0, blob1, blob2]); + expect(blob).toBeTruthy(); + }) + it('Blob flatten', () => { + const blob = new Blob(["aa", ["ab", "cd"], "ef"]) + expect(blob).toBeTruthy(); + }) + }); + + describe('Array buffer', () => { + it('simple', async () => { + const input_arr = new TextEncoder().encode("PASS"); + const blob = new Blob([input_arr]); + const array_buffer = await blob.arrayBuffer(); + expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); + }); + it('empty Blob data', async () => { + const input_arr = new TextEncoder().encode(""); + const blob = new Blob([input_arr]); + const array_buffer = await blob.arrayBuffer(); + expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); + }) + it('non-ascii input', async () => { + const input_arr = new TextEncoder().encode("\u08B8\u000a"); + const blob = new Blob([input_arr]); + const array_buffer = await blob.arrayBuffer(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); + }) + it('non-unicode input', async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + const blob = new Blob([typed_arr]); + // const array_buffer = await blob.arrayBuffer(); + const array_buffer = await blob.bytes(); + // expect(blob.syncText()).toEqual("\u0008\u00F1\u0030\u007B\u0097") + // expect(new Uint8Array(array_buffer) == typed_arr).toBeTruthy() + // expect(new Uint8Array(array_buffer)).toEqual(typed_arr); + expect(blob.size).toBe(5) + expect(array_buffer.length).toBe(5) + console.log(array_buffer.byteOffset) + console.log(array_buffer, typed_arr) + expect(array_buffer).toEqual(typed_arr); + }) + it('concurrent reads', async () => { + const input_arr = new TextEncoder().encode("PASS"); + const blob = new Blob([input_arr]); + const array_buffer_results = await Promise.all([blob.arrayBuffer(), + blob.arrayBuffer(), blob.arrayBuffer()]); + for (let array_buffer of array_buffer_results) { + expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); + } + }) + }) + + describe('Bytes', async () => { + it('Simple', async () => { + const input_arr = new TextEncoder().encode("PASS"); + const blob = new Blob([input_arr]); + const uint8array = await blob.bytes(); + expect(uint8array instanceof Uint8Array).toBeTruthy(); + expect(uint8array).toEqual(input_arr); + }); + it('empty Blob data', async () => { + const input_arr = new TextEncoder().encode(""); + const blob = new Blob([input_arr]); + const uint8array = await blob.bytes(); + expect(uint8array instanceof Uint8Array).toBeTruthy(); + expect(uint8array).toEqual(input_arr); + }); + it('non-ascii input', async () => { + const input_arr = new TextEncoder().encode("\u08B8\u000a"); + const blob = new Blob([input_arr]); + const uint8array = await blob.bytes(); + expect(uint8array).toEqual(input_arr); + }); + it('non-unicode input', async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + const blob = new Blob([typed_arr]); + const uint8array = await blob.bytes(); + expect(uint8array).toEqual(typed_arr); + }); + it('concurrent reads', async () => { + const input_arr = new TextEncoder().encode("PASS"); + const blob = new Blob([input_arr]); + const uint8array_results = await Promise.all([blob.bytes(), + blob.bytes(), blob.bytes()]); + for (let uint8array of uint8array_results) { + expect(uint8array instanceof Uint8Array).toBeTruthy(); + expect(uint8array).toEqual(input_arr); + } + }); + }) + + // Windows platforms use CRLF as the native line ending. All others use LF. + const crlf = Platform.OS == 'windows'; + const native_ending = crlf ? '\r\n' : '\n'; + describe('constructor-endings', async () => { + it('valid endings value', () => { + const blob0 = new Blob([], {endings: 'native'}); + const blob1 = new Blob([], {endings: 'transparent'}); + expect(blob0).toBeTruthy() + expect(blob1).toBeTruthy() + }) + it('invalud endings value', () => { + [ + null, + '', + 'invalidEnumValue', + 'Transparent', + 'NATIVE', + 0, + {}, + ].forEach((ending) => { + // @ts-expect-error + expect(() => new Blob([], {endings:ending})).toThrow() + }) + }) + it('Exception propagation from options', () => { + const test_error = {name: 'test string'}; + // @ts-expect-error + expect(() => new Blob([], { get endings() { throw test_error; }})).toThrow('test string') + }) + // TODO weird test, as it could maybe be used lazily and not in the constructor + it('The \'endings\' options property is used', () => { + let got = false; + // @ts-expect-error + new Blob([], { get endings() { got = true; } }); + expect(got).toBeTruthy() + }) + const sampleEndings = [ + {name: 'LF', input: '\n', native: native_ending}, + {name: 'CR', input: '\r', native: native_ending}, + + {name: 'CRLF', input: '\r\n', native: native_ending}, + {name: 'CRCR', input: '\r\r', native: native_ending.repeat(2)}, + {name: 'LFCR', input: '\n\r', native: native_ending.repeat(2)}, + {name: 'LFLF', input: '\n\n', native: native_ending.repeat(2)}, + + {name: 'CRCRLF', input: '\r\r\n', native: native_ending.repeat(2)}, + {name: 'CRLFLF', input: '\r\n\n', native: native_ending.repeat(2)}, + {name: 'CRLFCR', input: '\r\n\r\n', native: native_ending.repeat(2)}, + + {name: 'CRLFCRLF', input: '\r\n\r\n', native: native_ending.repeat(2)}, + {name: 'LFCRLFCR', input: '\n\r\n\r', native: native_ending.repeat(3)}, + + ]; + it('Newlines should not change with endings unspecified', () => { + sampleEndings.forEach(testCase => { + const blob = new Blob([testCase.input]); + expect(blob.syncText()).toBe(testCase.input) + }) + }) + it('Newlines should not change with endings "transparent"', () => { + sampleEndings.forEach(testCase => { + const blob = new Blob([testCase.input], {endings: 'transparent'}); + expect(blob.syncText()).toBe(testCase.input) + }); + }) + it('Newlines should match the platform with endings "native"', () => { + sampleEndings.forEach(testCase => { + const blob = new Blob([testCase.input], {endings: 'native'}); + expect(blob.syncText()).toBe(testCase.native) + }); + }) + it('CR/LF in adjacent input strings', () => { + const blob = new Blob(['\r', '\n'], {endings: 'native'}); + const expected = native_ending.repeat(2); + expect(blob.syncText()).toBe(expected) + }) + }) + + describe('constructor', () => { + it("globalThis should have a Blob property.", () => {expect("Blob" in globalThis).toBeTruthy()}) + it("Blob.length should be 0", () => {expect(Blob.length).toBe(0)}) + it("Blob should be a function", () => {expect(Blob instanceof Function).toBeTruthy()}) + it("Blob constructor with no arguments", () => { + var blob = new Blob() + expect(blob instanceof Blob).toBeTruthy() + expect(String(blob)).toBe('[object Blob]') + expect(blob.size).toBe(0) + expect(blob.type).toBe("") + }) + it("Blob constructor with no arguments, without 'new'", () => { + expect(() => {var blob = Blob();}).toThrow() + }) + it("Blob constructor without brackets", () => { + var blob = new Blob; + expect(blob instanceof Blob).toBeTruthy() + expect(blob.size).toBe(0) + expect(blob.type).toBe("") + }) + it("Blob constructor with undefined as first argument", () => { + var blob = new Blob(undefined) + expect(blob instanceof Blob).toBeTruthy() + expect(String(blob)).toBe('[object Blob]') + expect(blob.size).toBe(0) + expect(blob.type).toBe("") + }) + // TODO Something wrong with null ? Why should Blob() work and Blob(null) not ??? + it('Passing non-objects, Dates and RegExps for blobParts should throw a TypeError.', () => { + var args = [ + null, + true, + false, + 0, + 1, + 1.5, + "FAIL", + new Date(), + // @ts-expect-error + new RegExp(), + {}, + { 0: "FAIL", length: 1 }, + ]; + args.forEach((arg) => { + // @ts-expect-error + expect(() => new Blob(arg)).toThrow() + }); + }) + test_blob(function() { + + return new Blob({ + [Symbol.iterator]: Array.prototype[Symbol.iterator], + }); + }, { + expected: "", + type: "", + desc: "A plain object with @@iterator should be treated as a sequence for the blobParts argument." + }); + it('A plain object with custom @@iterator should be treated as a sequence for the blobParts argument.', () => { + const blob = new Blob({ + [Symbol.iterator]() { + var i = 0; + return {next: () => [ + {done:false, value:'ab'}, + {done:false, value:'cde'}, + {done:true} + ][i++] + }; + } + }); + expect(blob.size).toBe(5) + }) + it('A plain object with @@iterator and a length property should be treated as a sequence for the blobParts argument.', () => { + let blob = new Blob({ + [Symbol.iterator]: Array.prototype[Symbol.iterator], + 0: "PASS", + length: 1 + }); + expect(blob.syncText()).toEqual("PASS") + expect(blob.type).toBe("") + }) + it('A String object should be treated as a sequence for the blobParts argument.', () => { + let blob = new Blob(new String("xyz")); + expect(blob.syncText()).toEqual("xyz") + expect(blob.type).toBe("") + }) + test_blob(function() { + return new Blob(new String("xyz")); + }, { + expected: "xyz", + type: "", + desc: "A String object should be treated as a sequence for the blobParts argument." + }); + test_blob(function() { + return new Blob(new Uint8Array([1, 2, 3])); + }, { + expected: "123", + type: "", + desc: "A Uint8Array object should be treated as a sequence for the blobParts argument." + }); + + var test_error = { + name: "test", + message: "test error", + }; + it("The length getter should be invoked and any exceptions should be propagated.", () => { + expect(() => { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + get length() { throw test_error; } + }; + new Blob(obj) + }).toThrow(test_error) + }) + it("ToUint32 should be applied to the length and any exceptions should be propagated.", () => { + expect(() => { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + length: { + valueOf: null, + toString: function() { throw test_error; } + } + }; + new Blob(obj); + }).toThrow(test_error) + expect(() => { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + length: { valueOf: function() { throw test_error; } } + }; + new Blob(obj); + }).toThrow(test_error) + }) + it("Getters and value conversions should happen in order until an exception is thrown.", () => { + var received = []; + var obj = { + get [Symbol.iterator]() { + received.push("Symbol.iterator"); + return Array.prototype[Symbol.iterator]; + }, + get length() { + received.push("length getter"); + return { + valueOf: function() { + received.push("length valueOf"); + return 3; + } + }; + }, + get 0() { + received.push("0 getter"); + return { + toString: function() { + received.push("0 toString"); + return "a"; + } + }; + }, + get 1() { + received.push("1 getter"); + throw test_error; + }, + get 2() { + received.push("2 getter"); + expect(true).toBe(false); + return "unreachable"; + } + }; + expect(() => new Blob(obj)).toThrow(test_error); + // Somehow we don't call 0 toString but I don't know why not or why would we + expect(received).toEqual([ + "Symbol.iterator", + "length getter", + "length valueOf", + "0 getter", + "0 toString", + "length getter", + "length valueOf", + "1 getter", + ]); + }) + it("ToString should be called on elements of the blobParts array and any exceptions should be propagated.", () => { + expect(() => new Blob([{ toString: function() { throw test_error; } }])).toThrow(test_error) + expect(() => new Blob([{ toString: undefined, valueOf: function() { throw test_error; } }])).toThrow(test_error) + expect(() => new Blob([{ + toString: function() { throw test_error; }, + valueOf: function() { expect(false).toBe(true); } + }])).toThrow(test_error) + // TODO add the proper TypeError type to toThrow + expect(() => new Blob([{toString: null, valueOf: null}])).toThrow() + }) + test_blob(function() { + var arr = [ + { toString: function() { arr.pop(); return "PASS"; } }, + { toString: function() { } } + ]; + return new Blob(arr); + }, { + expected: "PASS", + type: "", + desc: "Changes to the blobParts array should be reflected in the returned Blob (pop)." + }); + test_blob(function() { + var arr = [ + { + toString: function() { + if (arr.length === 3) { + return "A"; + } + arr.unshift({ + toString: function() { + expect(true).toBe(false) + } + }); + return "P"; + } + }, + { + toString: function() { + return "SS"; + } + } + ]; + return new Blob(arr); + }, { + expected: "PASS", + type: "", + desc: "Changes to the blobParts array should be reflected in the returned Blob (unshift)." + }); + test_blob(function() { + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652 + return new Blob([ + null, + undefined, + true, + false, + 0, + 1, + new String("stringobject"), + [], + ['x', 'y'], + {}, + { 0: "FAIL", length: 1 }, + { toString: function() { return "stringA"; } }, + { toString: undefined, valueOf: function() { return "stringB"; } }, + { valueOf: function() { expect(false).toBe(true) } } + ]); + }, { + expected: "nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]", + type: "", + desc: "ToString should be called on elements of the blobParts array." + }); + test_blob(function() { + return new Blob([ + new ArrayBuffer(8) + ]); + }, { + expected: "\0\0\0\0\0\0\0\0", + type: "", + desc: "ArrayBuffer elements of the blobParts array should be supported." + }); + + test_blob(function() { + return new Blob([ + new Uint8Array([0x50, 0x41, 0x53, 0x53]), + new Int8Array([0x50, 0x41, 0x53, 0x53]), + new Uint16Array([0x4150, 0x5353]), + new Int16Array([0x4150, 0x5353]), + new Uint32Array([0x53534150]), + new Int32Array([0x53534150]), + new Float32Array([0xD341500000]) + ]); + }, { + expected: "PASSPASSPASSPASSPASSPASSPASS", + type: "", + desc: "Passing typed arrays as elements of the blobParts array should work." + }); + test_blob(function() { + return new Blob([ + new Float16Array([2.65625, 58.59375]) + ]); + }, { + expected: "PASS", + type: "", + desc: "Passing a Float16Array as element of the blobParts array should work." + }); + test_blob(function() { + return new Blob([ + // 0x535 3415053534150 + // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310 + // 0x13415053534150 * 2**(-52) + // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680 + new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680]) + ]); + }, { + expected: "PASSPASS", + type: "", + desc: "Passing a Float64Array as element of the blobParts array should work." + }); + test_blob(function() { + return new Blob([ + new BigInt64Array([BigInt("0x5353415053534150")]), + new BigUint64Array([BigInt("0x5353415053534150")]) + ]); + }, { + expected: "PASSPASSPASSPASS", + type: "", + desc: "Passing BigInt typed arrays as elements of the blobParts array should work." + }); + + // TODO revisit this one as its implementation is weird in the wpt + it("Passing a FrozenArray as the blobParts array should work (FrozenArray).", async () => { + var channel = new MessageChannel(); + channel.port2.onmessage = this.step_func(function(e) { + var b_ports = new Blob(e.ports); + expect(b_ports.size).toEqual("[object MessagePort]".length) + this.done(); + }); + var channel2 = new MessageChannel(); + channel.port1.postMessage('', [channel2.port1]); + }) + + test_blob(function() { + var blob = new Blob(['foo']); + return new Blob([blob, blob]); + }, { + expected: "foofoo", + type: "", + desc: "Array with two blobs" + }); + test_blob_binary(function() { + var view = new Uint8Array([0, 255, 0]); + return new Blob([view.buffer, view.buffer]); + }, { + expected: [0, 255, 0, 0, 255, 0], + type: "", + desc: "Array with two buffers" + }); + + test_blob_binary(function() { + var view = new Uint8Array([0, 255, 0, 4]); + var blob = new Blob([view, view]); + expect(blob.size).toBe(8); + var view1 = new Uint16Array(view.buffer, 2); + return new Blob([view1, view.buffer, view1]); + }, { + expected: [0, 4, 0, 255, 0, 4, 0, 4], + type: "", + desc: "Array with two bufferviews" + }); + + // TODO revisit this, why can we pass view but not the buffer? + test_blob(function() { + var view = new Uint8Array([0]); + var blob = new Blob(["fo"]); + return new Blob([view.buffer, blob, "foo"]); + }, { + expected: "\0fofoo", + type: "", + desc: "Array with mixed types" + }); + + it('options properties should be accessed in lexicographic order.', async () => { + const accessed = []; + const stringified = []; + + new Blob([], { + get type() { accessed.push('type'); }, + get endings() { accessed.push('endings'); } + }); + new Blob([], { + type: { toString: () => { stringified.push('type'); return ''; } }, + endings: { toString: () => { stringified.push('endings'); return 'transparent'; } } + }); + expect(accessed).toEqual(['endings', 'type']); + expect(stringified).toEqual(['endings', 'type']); + }) + + it('Arguments should be evaluated from left to right.', async () => { + expect( + () => new Blob( + [{ toString: function() { throw test_error } }], + { + get type() { assert_unreached("type getter should not be called."); } + }) + ).toThrow(test_error); + }) + + describe('Passing arguments for options', () => { + [ + null, + undefined, + {}, + { unrecognized: true }, + /regex/, + function() {} + ].forEach((arg : any, idx) => { + test_blob(function() { + return new Blob([], arg); + }, { + expected: "", + type: "", + desc: "Passing " + JSON.stringify(arg) + " (index " + idx + ") for options should use the defaults." + }); + test_blob(function() { + return new Blob(["\na\r\nb\n\rc\r"], arg); + }, { + expected: "\na\r\nb\n\rc\r", + type: "", + desc: "Passing " + JSON.stringify(arg) + " (index " + idx + ") for options should use the defaults (with newlines)." + }); + }); + }) + + describe('Blob constructor should throw with invalid property bag', () => { + [ + 123, + 123.4, + true, + 'abc' + ].forEach((arg : any) => { + it('Passing ' + JSON.stringify(arg) + ' for options should throw', () => { + expect(() => { + new Blob([], arg) + }).toThrow() + }) + }); + }) + + describe('Type test', () => { + var type_tests = [ + // blobParts, type, expected type + [[], '', ''], + [[], 'a', 'a'], + [[], 'A', 'a'], + [[], 'text/html', 'text/html'], + [[], 'TEXT/HTML', 'text/html'], + [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'], + [[], '\u00E5', ''], + [[], '\uD801\uDC7E', ''], // U+1047E + [[], ' image/gif ', ' image/gif '], + [[], '\timage/gif\t', ''], + [[], 'image/gif;\u007f', ''], + [[], '\u0130mage/gif', ''], // uppercase i with dot + [[], '\u0131mage/gif', ''], // lowercase dotless i + [[], 'image/gif\u0000', ''], + // check that type isn't changed based on sniffing + [[0x3C, 0x48, 0x54, 0x4D, 0x4C, 0x3E], 'unknown/unknown', 'unknown/unknown'], // "" + [[0x00, 0xFF], 'text/plain', 'text/plain'], + [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a" + ]; + + type_tests.forEach(function(t: Array) { + it("Blob with type " + JSON.stringify(t[1]), () => { + // TODO Why this construction? It does'nt work yet, but it's not what we test for... + // var arr = new Uint8Array([t[0]]).buffer; + // var b = new Blob([arr], {type:t[1]}); + + var b = new Blob(t[0], {type:t[1]}); + expect(b.type).toEqual(t[2]); + }); + }); + }) + }) + + describe('Text', async () => { + it('simple', async () => { + const blob = new Blob(["PASS"]); + const text = await blob.text(); + expect(text).toBe("PASS"); + }); + it('empty blob data', async () => { + const blob = new Blob(); + const text = await blob.text(); + expect(text).toBe(""); + }); + it('multi-element array in constructor', async () => { + const blob = new Blob(["P", "A", "SS"]); + const text = await blob.text(); + expect(text).toBe("PASS"); + }); + it('non-unicode', async () => { + const non_unicode = "\u0061\u030A"; + const input_arr = new TextEncoder().encode(non_unicode); + const blob = new Blob([input_arr]); + const text = await blob.text(); + expect(text).toBe(non_unicode); + }); + it('different charset param in type option', async () => { + const blob = new Blob(["PASS"], { type: "text/plain;charset=utf-16le" }); + const text = await blob.text(); + expect(text).toBe("PASS"); + }) + it('Sync Text', () => { + const blob = new Blob(["PA", "SS"]); + const text = blob.syncText(); + expect(text).toBe("PASS"); + }) + it('different charset param with non-ascii input', async () => { + const non_unicode = "\u0061\u030A"; + const input_arr = new TextEncoder().encode(non_unicode); + const blob = new Blob([input_arr], { type: "text/plain;charset=utf-16le" }); + const text = await blob.text(); + expect(text).toBe(non_unicode); + }) + it('invalid utf-8 input', async () => { + const input_arr = new Uint8Array([192, 193, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]); + const blob = new Blob([input_arr]); + const text = await blob.text(); + expect(text).toBe("\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd"); + }); + it('Promise.all multiple reads', async () => { + const input_arr = new Uint8Array([192, 193, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]); + const blob = new Blob([input_arr]); + const text_results = await Promise.all([blob.text(), blob.text(), blob.text()]); + text_results.forEach(text => { + expect(text).toBe("\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd"); + }); + }); + }) + }); +} \ No newline at end of file diff --git a/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin b/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin new file mode 100644 index 00000000000000..0d259ddcb52852 --- /dev/null +++ b/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin @@ -0,0 +1 @@ +o/classes diff --git a/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/transformed/classes/classes_dex/classes.dex b/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/transformed/classes/classes_dex/classes.dex new file mode 100644 index 0000000000000000000000000000000000000000..906cd2e1836d6bae41bfd64f9ea55901218437c9 GIT binary patch literal 45756 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 d, options: BlobOptionsBag? -> + Constructor() { blobParts: List?, options: BlobOptionsBag? -> val type = options?.type ?: DEFAULT_TYPE val endings = options?.endings ?: EndingType.TRANSPARENT - Blob(blobParts.internal(endings == EndingType.NATIVE), type) + Blob((blobParts ?: listOf()).internal(endings == EndingType.NATIVE), type) } Property("size") { blob: Blob -> diff --git a/packages/expo-blob/build/BlobModule.d.ts b/packages/expo-blob/build/BlobModule.d.ts index a772a64fc0d806..0f9b14aa49bf37 100644 --- a/packages/expo-blob/build/BlobModule.d.ts +++ b/packages/expo-blob/build/BlobModule.d.ts @@ -1,10 +1,23 @@ -import { Blob } from "./BlobModule.types"; -declare const NativeBlobModule: any; -export declare class ExpoBlob extends NativeBlobModule.Blob implements Blob { - constructor(blobParts?: any, options?: BlobPropertyBag); - slice(start?: number, end?: number, contentType?: string): Blob; +import { NativeModule, SharedObject } from "expo"; +import { Blob, BlobPart } from "./BlobModule.types"; +declare class NativeBlob extends SharedObject { + readonly size: number; + readonly type: string; + constructor(blobParts?: BlobPart[], options?: BlobPropertyBag); + slice(start?: number, end?: number, contentType?: string): ExpoBlob; + bytes(): Promise; text(): Promise; + syncText(): string; +} +declare class ExpoBlobModule extends NativeModule { + Blob: typeof NativeBlob; +} +declare const NativeBlobModule: ExpoBlobModule; +export declare class ExpoBlob extends NativeBlobModule.Blob implements Blob { + constructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag); + slice(start?: number, end?: number, contentType?: string): ExpoBlob; stream(): ReadableStream; + arrayBuffer(): Promise; } export {}; //# sourceMappingURL=BlobModule.d.ts.map \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.d.ts.map b/packages/expo-blob/build/BlobModule.d.ts.map index 1edd9d4f667063..1a8d2dff32d89a 100644 --- a/packages/expo-blob/build/BlobModule.d.ts.map +++ b/packages/expo-blob/build/BlobModule.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C,QAAA,MAAM,gBAAgB,EAAE,GAAqC,CAAC;AAE9D,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACtD,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,eAAe;IAItD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IASzD,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,MAAM,IAAI,cAAc;CAgBxB"} \ No newline at end of file +{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,YAAY,EAAE,MAAM,MAAM,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,OAAO,UAAW,SAAQ,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAC7D,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IACnE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC5B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,QAAQ,IAAI,MAAM;CAClB;AAED,OAAO,OAAO,cAAe,SAAQ,YAAY;IAChD,IAAI,EAAE,OAAO,UAAU,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,gBAAkD,CAAC;AAEzE,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACtD,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe;IAUxE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IAOnE,MAAM,IAAI,cAAc;IAiBlB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAG7C"} \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.js b/packages/expo-blob/build/BlobModule.js index 1d059405272487..53c193782a6a49 100644 --- a/packages/expo-blob/build/BlobModule.js +++ b/packages/expo-blob/build/BlobModule.js @@ -1,22 +1,26 @@ import { requireNativeModule } from "expo"; +import { normalizedContentType } from "./utils"; const NativeBlobModule = requireNativeModule("ExpoBlob"); export class ExpoBlob extends NativeBlobModule.Blob { constructor(blobParts, options) { - super(blobParts, options); + if (!blobParts) { + super([], options); + } + else if (blobParts instanceof Array) { + super(blobParts.flat(Infinity), options); + } + else { + super(Array.from(blobParts).flat(Infinity), options); + } } slice(start, end, contentType) { - const slicedBlob = super.slice(start, end, contentType); - const options = { - type: slicedBlob.type, - endings: slicedBlob.endings, - }; - return new ExpoBlob(slicedBlob, options); - } - async text() { - return Promise.resolve(super.text()); + const normalizedType = contentType ?? normalizedContentType(contentType); + const slicedBlob = super.slice(start, end, normalizedType); + Object.setPrototypeOf(slicedBlob, ExpoBlob.prototype); + return slicedBlob; } stream() { - const text = super.text(); + const text = super.syncText(); const encoder = new TextEncoder(); const uint8 = encoder.encode(text); let offset = 0; @@ -32,5 +36,8 @@ export class ExpoBlob extends NativeBlobModule.Blob { }, }); } + async arrayBuffer() { + return super.bytes().then((bytes) => bytes.buffer); + } } //# sourceMappingURL=BlobModule.js.map \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.js.map b/packages/expo-blob/build/BlobModule.js.map index 7b6a0254eb182f..92a91f40466193 100644 --- a/packages/expo-blob/build/BlobModule.js.map +++ b/packages/expo-blob/build/BlobModule.js.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAG3C,MAAM,gBAAgB,GAAQ,mBAAmB,CAAC,UAAU,CAAC,CAAC;AAE9D,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAe,EAAE,OAAyB;QACrD,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QACxD,MAAM,OAAO,GAAoB;YAChC,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,OAAO,EAAE,UAAU,CAAC,OAAO;SAC3B,CAAC;QACF,OAAO,IAAI,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,IAAI;QACT,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;CACD","sourcesContent":["import { requireNativeModule } from \"expo\";\nimport { Blob } from \"./BlobModule.types\";\n\nconst NativeBlobModule: any = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any, options?: BlobPropertyBag) {\n\t\tsuper(blobParts, options);\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): Blob {\n\t\tconst slicedBlob = super.slice(start, end, contentType);\n\t\tconst options: BlobPropertyBag = {\n\t\t\ttype: slicedBlob.type,\n\t\t\tendings: slicedBlob.endings,\n\t\t};\n\t\treturn new ExpoBlob(slicedBlob, options);\n\t}\n\n\tasync text(): Promise {\n\t\treturn Promise.resolve(super.text());\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.text();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n}\n"]} \ No newline at end of file +{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QACtD,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,WAAW,IAAI,qBAAqB,CAAC,WAAW,CAAC,CAAC;QACzE,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = contentType ?? normalizedContentType(contentType);\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer);\n\t}\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.types.d.ts b/packages/expo-blob/build/BlobModule.types.d.ts index f99f2cf2abec05..67ee05e989afee 100644 --- a/packages/expo-blob/build/BlobModule.types.d.ts +++ b/packages/expo-blob/build/BlobModule.types.d.ts @@ -1,8 +1,9 @@ export declare class Blob { constructor(blobParts?: any, options?: BlobPropertyBag); slice(start?: number, end?: number, contentType?: string): Blob; - text(): Promise; stream(): ReadableStream; + text(): Promise; + arrayBuffer(): Promise; } export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob; //# sourceMappingURL=BlobModule.types.d.ts.map \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.types.d.ts.map b/packages/expo-blob/build/BlobModule.types.d.ts.map index c0809539567694..a1a64eee7d0228 100644 --- a/packages/expo-blob/build/BlobModule.types.d.ts.map +++ b/packages/expo-blob/build/BlobModule.types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.types.d.ts","sourceRoot":"","sources":["../src/BlobModule.types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,IAAI;gBACZ,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,eAAe;IAEtD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAC/D,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,MAAM,IAAI,cAAc;CACxB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,IAAI,CAAC"} \ No newline at end of file +{"version":3,"file":"BlobModule.types.d.ts","sourceRoot":"","sources":["../src/BlobModule.types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,IAAI;gBACZ,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,eAAe;IAEtD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAC/D,MAAM,IAAI,cAAc;IACxB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CACvC;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,IAAI,CAAC"} \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.types.js.map b/packages/expo-blob/build/BlobModule.types.js.map index 8bc4951a8137fc..4aead2898e8892 100644 --- a/packages/expo-blob/build/BlobModule.types.js.map +++ b/packages/expo-blob/build/BlobModule.types.js.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.types.js","sourceRoot":"","sources":["../src/BlobModule.types.ts"],"names":[],"mappings":"","sourcesContent":["export declare class Blob {\n\tconstructor(blobParts?: any, options?: BlobPropertyBag);\n\n\tslice(start?: number, end?: number, contentType?: string): Blob;\n\ttext(): Promise;\n\tstream(): ReadableStream;\n}\n\nexport type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob;\n"]} \ No newline at end of file +{"version":3,"file":"BlobModule.types.js","sourceRoot":"","sources":["../src/BlobModule.types.ts"],"names":[],"mappings":"","sourcesContent":["export declare class Blob {\n\tconstructor(blobParts?: any, options?: BlobPropertyBag);\n\n\tslice(start?: number, end?: number, contentType?: string): Blob;\n\tstream(): ReadableStream;\n\ttext(): Promise;\n\tarrayBuffer(): Promise;\n}\n\nexport type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob;\n"]} \ No newline at end of file diff --git a/packages/expo-blob/build/utils.d.ts b/packages/expo-blob/build/utils.d.ts new file mode 100644 index 00000000000000..a7f581287101ee --- /dev/null +++ b/packages/expo-blob/build/utils.d.ts @@ -0,0 +1,17 @@ +/** + * Normalizes the content type string for a Blob. + * + * Returns the lowercased content type if it is valid, or an empty string otherwise. + * + * A valid content type: + * - Is not null, undefined, or empty + * - Contains only printable ASCII characters (0x20–0x7E) + * - Does not contain forbidden control characters: NUL (\x00), LF (\x0A), or CR (\x0D) + * + * If any of these conditions are not met, returns an empty string to indicate an invalid or unsafe content type. + * + * @param type The content type string to normalize. + * @returns The normalized (lowercased) content type, or an empty string if invalid. + */ +export declare function normalizedContentType(type?: string): string; +//# sourceMappingURL=utils.d.ts.map \ No newline at end of file diff --git a/packages/expo-blob/build/utils.d.ts.map b/packages/expo-blob/build/utils.d.ts.map new file mode 100644 index 00000000000000..b19ab9410a6b2a --- /dev/null +++ b/packages/expo-blob/build/utils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAK3D"} \ No newline at end of file diff --git a/packages/expo-blob/build/utils.js b/packages/expo-blob/build/utils.js new file mode 100644 index 00000000000000..35e1a30b50ac45 --- /dev/null +++ b/packages/expo-blob/build/utils.js @@ -0,0 +1,24 @@ +/** + * Normalizes the content type string for a Blob. + * + * Returns the lowercased content type if it is valid, or an empty string otherwise. + * + * A valid content type: + * - Is not null, undefined, or empty + * - Contains only printable ASCII characters (0x20–0x7E) + * - Does not contain forbidden control characters: NUL (\x00), LF (\x0A), or CR (\x0D) + * + * If any of these conditions are not met, returns an empty string to indicate an invalid or unsafe content type. + * + * @param type The content type string to normalize. + * @returns The normalized (lowercased) content type, or an empty string if invalid. + */ +export function normalizedContentType(type) { + if (!type || type.length === 0) + return ""; + const asciiPrintable = /^[\x20-\x7E]+$/; + if (!asciiPrintable.test(type)) + return ""; + return type.toLowerCase(); +} +//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/packages/expo-blob/build/utils.js.map b/packages/expo-blob/build/utils.js.map new file mode 100644 index 00000000000000..1acb66f43b19ab --- /dev/null +++ b/packages/expo-blob/build/utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAa;IAClD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,gBAAgB,CAAC;IACxC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * Normalizes the content type string for a Blob.\n *\n * Returns the lowercased content type if it is valid, or an empty string otherwise.\n *\n * A valid content type:\n * - Is not null, undefined, or empty\n * - Contains only printable ASCII characters (0x20–0x7E)\n * - Does not contain forbidden control characters: NUL (\\x00), LF (\\x0A), or CR (\\x0D)\n *\n * If any of these conditions are not met, returns an empty string to indicate an invalid or unsafe content type.\n *\n * @param type The content type string to normalize.\n * @returns The normalized (lowercased) content type, or an empty string if invalid.\n */\nexport function normalizedContentType(type?: string): string {\n\tif (!type || type.length === 0) return \"\";\n\tconst asciiPrintable = /^[\\x20-\\x7E]+$/;\n\tif (!asciiPrintable.test(type)) return \"\";\n\treturn type.toLowerCase();\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/src/BlobModule.ts b/packages/expo-blob/src/BlobModule.ts index 0e9a740e87c4c7..cc71776fb5fac9 100644 --- a/packages/expo-blob/src/BlobModule.ts +++ b/packages/expo-blob/src/BlobModule.ts @@ -18,8 +18,14 @@ declare class ExpoBlobModule extends NativeModule { const NativeBlobModule = requireNativeModule("ExpoBlob"); export class ExpoBlob extends NativeBlobModule.Blob implements Blob { - constructor(blobParts?: any[], options?: BlobPropertyBag) { - super(blobParts?.flat(Infinity), options); + constructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) { + if (!blobParts) { + super([], options); + } else if (blobParts instanceof Array) { + super(blobParts.flat(Infinity), options); + } else { + super(Array.from(blobParts).flat(Infinity), options); + } } slice(start?: number, end?: number, contentType?: string): ExpoBlob { From e5e7869004973c924db4b4ae888aa918d497db8d Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 13:03:49 +0200 Subject: [PATCH 02/11] in worker & slice tests --- apps/test-suite/tests/Blob.ts | 280 +++++++++++++++++++++++++++++++++- 1 file changed, 278 insertions(+), 2 deletions(-) diff --git a/apps/test-suite/tests/Blob.ts b/apps/test-suite/tests/Blob.ts index 69665a7c0b0520..95258e502ac378 100644 --- a/apps/test-suite/tests/Blob.ts +++ b/apps/test-suite/tests/Blob.ts @@ -285,7 +285,6 @@ export async function test({ describe, it, expect, jasmine }) { }); }) test_blob(function() { - return new Blob({ [Symbol.iterator]: Array.prototype[Symbol.iterator], }); @@ -693,7 +692,7 @@ export async function test({ describe, it, expect, jasmine }) { // TODO Why this construction? It does'nt work yet, but it's not what we test for... // var arr = new Uint8Array([t[0]]).buffer; // var b = new Blob([arr], {type:t[1]}); - + var b = new Blob(t[0], {type:t[1]}); expect(b.type).toEqual(t[2]); }); @@ -756,5 +755,282 @@ export async function test({ describe, it, expect, jasmine }) { }); }); }) + + describe('Worker', async () => { + it('Create Blob in Worker', async () => { + const data = "TEST"; + const blob = new Blob([data], {type: "text/plain"}); + expect(await blob.text()).toEqual(data); + }); + }) + + describe('Blob slice overflow', async () => { + var text = ''; + + for (var i = 0; i < 2000; ++i) { + text += 'A'; + } + + it("slice start is negative, relativeStart will be max((size + start), 0)", () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(-1, blob.size); + expect(sliceBlob.size).toBe(1); + }); + + it("slice start is greater than blob size, relativeStart will be min(start, size)", () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size + 1, blob.size); + expect(sliceBlob.size).toBe(0); + }); + + it("slice end is negative, relativeEnd will be max((size + end), 0)", () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size - 2, -1); + expect(sliceBlob.size).toBe(1); + }); + + it("slice end is greater than blob size, relativeEnd will be min(end, size)", () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size - 2, blob.size + 999); + expect(sliceBlob.size).toBe(2) + }); + }) + + describe('Blob Slice', async () => { + test_blob(() => { + var blobTemp = new Blob(["PASS"]); + return blobTemp.slice(); + }, { + expected: "PASS", + type: "", + desc: "no-argument Blob slice" + } + ); + + describe("Slices", async () => { + var blob1 = new Blob(["squiggle"]); + var blob2 = new Blob(["steak"], {type: "content/type"}); + + test_blob(() => blob1, + { + expected: "squiggle", + type: "", + desc: "blob1." + }); + + test_blob(() => blob2, + { + expected: "steak", + type: "content/type", + desc: "blob2." + }); + + test_blob(() => { + return new Blob().slice(0,0,null); + }, { + expected: "", + type: "null", + desc: "null type Blob slice" + }); + + test_blob(() => { + return new Blob().slice(0,0,undefined); + }, { + expected: "", + type: "", + desc: "undefined type Blob slice" + }); + + test_blob(() => { + return new Blob().slice(0,0); + }, { + expected: "", + type: "", + desc: "no type Blob slice" + }); + + var arrayBuffer = new ArrayBuffer(16); + var int8View = new Int8Array(arrayBuffer); + for (var i = 0; i < 16; i++) { + int8View[i] = i + 65; + } + + var testData = [ + [ + ["PASSSTRING"], + [{start: -6, contents: "STRING"}, + {start: -12, contents: "PASSSTRING"}, + {start: 4, contents: "STRING"}, + {start: 12, contents: ""}, + {start: 0, end: -6, contents: "PASS"}, + {start: 0, end: -12, contents: ""}, + {start: 0, end: 4, contents: "PASS"}, + {start: 0, end: 12, contents: "PASSSTRING"}, + {start: 7, end: 4, contents: ""}] + ], + + // Test 3 strings + [ + ["foo", "bar", "baz"], + [{start: 0, end: 9, contents: "foobarbaz"}, + {start: 0, end: 3, contents: "foo"}, + {start: 3, end: 9, contents: "barbaz"}, + {start: 6, end: 9, contents: "baz"}, + {start: 6, end: 12, contents: "baz"}, + {start: 0, end: 9, contents: "foobarbaz"}, + {start: 0, end: 11, contents: "foobarbaz"}, + {start: 10, end: 15, contents: ""}] + ], + + // Test string, Blob, string + [ + ["foo", blob1, "baz"], + [{start: 0, end: 3, contents: "foo"}, + {start: 3, end: 11, contents: "squiggle"}, + {start: 2, end: 4, contents: "os"}, + {start: 10, end: 12, contents: "eb"}] + ], + + // Test blob, string, blob + [ + [blob1, "foo", blob1], + [{start: 0, end: 8, contents: "squiggle"}, + {start: 7, end: 9, contents: "ef"}, + {start: 10, end: 12, contents: "os"}, + {start: 1, end: 4, contents: "qui"}, + {start: 12, end: 15, contents: "qui"}, + {start: 40, end: 60, contents: ""}] + ], + + // Test blobs all the way down + [ + [blob2, blob1, blob2], + [{start: 0, end: 5, contents: "steak"}, + {start: 5, end: 13, contents: "squiggle"}, + {start: 13, end: 18, contents: "steak"}, + {start: 1, end: 3, contents: "te"}, + {start: 6, end: 10, contents: "quig"}] + ], + + // Test an ArrayBufferView + [ + [int8View, blob1, "foo"], + [{start: 0, end: 8, contents: "ABCDEFGH"}, + {start: 8, end: 18, contents: "IJKLMNOPsq"}, + {start: 17, end: 20, contents: "qui"}, + {start: 4, end: 12, contents: "EFGHIJKL"}] + ], + + // Test a partial ArrayBufferView + [ + [new Uint8Array(arrayBuffer, 3, 5), blob1, "foo"], + [{start: 0, end: 8, contents: "DEFGHsqu"}, + {start: 8, end: 18, contents: "igglefoo"}, + {start: 4, end: 12, contents: "Hsquiggl"}] + ], + + // Test type coercion of a number + [ + [3, int8View, "foo"], + [{start: 0, end: 8, contents: "3ABCDEFG"}, + {start: 8, end: 18, contents: "HIJKLMNOPf"}, + {start: 17, end: 21, contents: "foo"}, + {start: 4, end: 12, contents: "DEFGHIJK"}] + ], + + [ + [(new Uint8Array([0, 255, 0])).buffer, + new Blob(['abcd']), + 'efgh', + 'ijklmnopqrstuvwxyz'], + [{start: 1, end: 4, contents: "\uFFFD\u0000a"}, + {start: 4, end: 8, contents: "bcde"}, + {start: 8, end: 12, contents: "fghi"}, + {start: 1, end: 12, contents: "\uFFFD\u0000abcdefghi"}] + ] + ]; + + testData.forEach(function(data, i) { + var blobs = data[0]; + var tests = data[1]; + tests.forEach(function(expectations, j) { + describe("Slicing test (" + i + "," + j + ").", () => { + var blob = new Blob(blobs); + + it('blob is an instance of Blob', () => { + expect(blob instanceof Blob).toBeTruthy(); + expect(blob instanceof File).toBeFalsy(); + }) + + test_blob(() => { + return expectations.end === undefined + ? blob.slice(expectations.start) + : blob.slice(expectations.start, expectations.end); + }, { + expected: expectations.contents, + type: "", + desc: "Slicing test: slice (" + i + "," + j + ")." + }); + },); + }); + }); + }); + + describe('Invalid content types', () => { + var invalidTypes = [ + "\xFF", + "te\x09xt/plain", + "te\x00xt/plain", + "te\x1Fxt/plain", + "te\x7Fxt/plain" + ]; + invalidTypes.forEach(function(type) { + test_blob(() => { + var blob = new Blob(["PASS"]); + return blob.slice(0, 4, type); + }, { + expected: "PASS", + type: "", + desc: "Invalid contentType (" + JSON.stringify(type) + ")" + }); + }); + }) + + var validTypes = [ + "te(xt/plain", + "te)xt/plain", + "text/plain", + "te@xt/plain", + "te,xt/plain", + "te;xt/plain", + "te:xt/plain", + "te\\xt/plain", + "te\"xt/plain", + "te/xt/plain", + "te[xt/plain", + "te]xt/plain", + "te?xt/plain", + "te=xt/plain", + "te{xt/plain", + "te}xt/plain", + "te\x20xt/plain", + "TEXT/PLAIN", + "text/plain;charset = UTF-8", + "text/plain;charset=UTF-8" + ]; + describe('valid content types', () => { + validTypes.forEach((type) => { + test_blob(() => { + var blob = new Blob(["PASS"]); + return blob.slice(0, 4, type); + }, { + expected: "PASS", + type: type.toLowerCase(), + desc: "Valid contentType (" + JSON.stringify(type) + ")" + }); + }); + }) + }) }); } \ No newline at end of file From 517def170ef9f64205ffc21c332683d6886c57bf Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 13:34:54 +0200 Subject: [PATCH 03/11] constructor dom windows + stream related tests --- apps/test-suite/tests/Blob.ts | 83 +++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/apps/test-suite/tests/Blob.ts b/apps/test-suite/tests/Blob.ts index 95258e502ac378..e5bf70cf48cb1c 100644 --- a/apps/test-suite/tests/Blob.ts +++ b/apps/test-suite/tests/Blob.ts @@ -3,6 +3,11 @@ import { Platform } from 'expo-modules-core'; export const name = 'Blob'; +var test_error = { + name: "test", + message: "test error", +}; + export async function test({ describe, it, expect, jasmine }) { const test_blob = (fn, expectations) => { var expected = expectations.expected, @@ -336,10 +341,6 @@ export async function test({ describe, it, expect, jasmine }) { desc: "A Uint8Array object should be treated as a sequence for the blobParts argument." }); - var test_error = { - name: "test", - message: "test error", - }; it("The length getter should be invoked and any exceptions should be propagated.", () => { expect(() => { var obj = { @@ -700,6 +701,52 @@ export async function test({ describe, it, expect, jasmine }) { }) }) + describe('constructor dom windows', async () => { + it("Passing platform objects for blobParts should throw a TypeError.", () => { + const document = new Document() + var args = [ + document.createElement("div"), + window, + ]; + args.forEach((arg) => { + expect(() => new Blob(arg)).toThrow(); + }); + }); + + it("A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)", () => { + var element = document.createElement("div"); + element.appendChild(document.createElement("div")); + element.appendChild(document.createElement("p")); + var list = element.children; + Object.defineProperty(list, "length", { + get: function() { throw test_error; } + }); + expect(() => {new Blob(list);}).toThrow(test_error); + }); + + test_blob(function() { + const document = new Document() + var select = document.createElement("select"); + select.appendChild(document.createElement("option")); + return new Blob(select); + }, { + expected: "[object HTMLOptionElement]", + type: "", + desc: "Passing a platform object that supports indexed properties as the blobParts array should work (select)." + }); + + test_blob(function() { + const document = new Document() + var elm = document.createElement("div"); + elm.setAttribute("foo", "bar"); + return new Blob(elm.attributes); + }, { + expected: "[object Attr]", + type: "", + desc: "Passing an platform object that supports indexed properties as the blobParts array should work (attributes)." + }); + }) + describe('Text', async () => { it('simple', async () => { const blob = new Blob(["PASS"]); @@ -1032,5 +1079,33 @@ export async function test({ describe, it, expect, jasmine }) { }); }) }) + + describe('stream', async () => { + it('stream byob crash', async () => { + let a = new Blob(['', '', undefined], { }) + let b = a.stream() + let c = new ReadableStreamBYOBReader(b) + let d = new Int16Array(8) + await c.read(d) + c.releaseLock() + await a.text() + await b.cancel() + }) + + it('stream xhr crash', async () => { + // TODO this constructor doesn't work need to fix it + // const blob = new Blob([1, 2]); + const blob = new Blob([Int32Array.from([1, 2])]); + const readable = blob.stream() + const writable = new WritableStream({}, { + size() { + let xhr = new XMLHttpRequest() + xhr.open("POST", "1", false) + xhr.send() + } + }) + readable.pipeThrough({ readable, writable }) + }) + }) }); } \ No newline at end of file From 7e847a95e54eb7457beeb4c1f868e2b71728f031 Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 13:48:36 +0200 Subject: [PATCH 04/11] stream tests --- apps/test-suite/tests/Blob.ts | 95 ++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/apps/test-suite/tests/Blob.ts b/apps/test-suite/tests/Blob.ts index e5bf70cf48cb1c..0c308b1cf34cb4 100644 --- a/apps/test-suite/tests/Blob.ts +++ b/apps/test-suite/tests/Blob.ts @@ -42,8 +42,42 @@ export async function test({ describe, it, expect, jasmine }) { expect(ab instanceof ArrayBuffer).toBeTruthy(); expect(new Uint8Array(ab)).toEqual(expected); }) - } + + // Helper function that triggers garbage collection while reading a chunk + // if perform_gc is true. + const read_and_gc = async (reader, perform_gc) => { + // Passing Uint8Array for byte streams; non-byte streams will simply ignore it + const read_promise = reader.read(new Uint8Array(64)); + if (perform_gc) { + // TODO Actually perform garbage collection in here + // await garbageCollect(); + } + return read_promise; + } + + // Takes in a ReadableStream and reads from it until it is done, returning + // an array that contains the results of each read operation. If perform_gc + // is true, garbage collection is triggered while reading every chunk. + const read_all_chunks = async (stream, { perform_gc = false, mode } = {}) => { + expect(stream instanceof ReadableStream).toBeTruthy(); + expect('getReader' in stream).toBeTruthy(); + const reader = stream.getReader({ mode }); + + expect('read' in reader).toBeTruthy() + let read_value = await read_and_gc(reader, perform_gc); + + let out = []; + let i = 0; + while (!read_value.done) { + for (let val of read_value.value) { + out[i++] = val; + } + read_value = await read_and_gc(reader, perform_gc); + } + return out; + } + describe('Blob', async () => { describe('Blob creation', () => { it('Empty blob', () => { @@ -1106,6 +1140,65 @@ export async function test({ describe, it, expect, jasmine }) { }) readable.pipeThrough({ readable, writable }) }) + + it("Blob.stream()", async () => { + const blob = new Blob(["PASS"]); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream); + for (let [index, value] of chunks.entries()) { + expect(value).toEqual("PASS".charCodeAt(index)); + } + }) + + it("Blob.stream() empty Blob", async () => { + const blob = new Blob(); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream); + expect(chunks).toEqual([]); + }) + + it("Blob.stream() non-unicode input", async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + const blob = new Blob([typed_arr]); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream); + expect(chunks).toEqual(input_arr); + }) + + it("Blob.stream() garbage collection of blob shouldn't break stream " + + "consumption", async() => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + let blob = new Blob([typed_arr]); + const stream = blob.stream(); + blob = null; + // TODO Actually call garbageCollect() + // await garbageCollect(); + const chunks = await read_all_chunks(stream, { perform_gc: true }); + expect(chunks).toEqual(input_arr); + }) + + it("Blob.stream() garbage collection of stream shouldn't break stream " + + "consumption", async() => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + let blob = new Blob([typed_arr]); + const chunksPromise = read_all_chunks(blob.stream()); + // It somehow matters to do GC here instead of doing `perform_gc: true` + // TODO Actually call garbageCollect() + // await garbageCollect(); + expect(await chunksPromise).toEqual(input_arr); + }) + + it("Reading Blob.stream() with BYOB reader", async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + let blob = new Blob([typed_arr]); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream, { mode: "byob" }); + expect(chunks).toEqual(input_arr); + }) }) }); } \ No newline at end of file From 47da5574e92c7152d221f8b70f38fdfed72f21e6 Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 14:49:05 +0200 Subject: [PATCH 05/11] slice fixes --- .../android/src/main/java/expo/modules/blob/Blob.kt | 3 +++ .../src/main/java/expo/modules/blob/BlobModule.kt | 12 +++++++++--- packages/expo-blob/build/BlobModule.d.ts.map | 2 +- packages/expo-blob/build/BlobModule.js | 5 ++++- packages/expo-blob/build/BlobModule.js.map | 2 +- packages/expo-blob/src/BlobModule.ts | 5 ++++- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt b/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt index fe5452c34bcf3c..5e7be6d72c26cc 100644 --- a/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt +++ b/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt @@ -51,6 +51,9 @@ class Blob() : SharedObject() { } fun slice(start: Int, end: Int, contentType: String): Blob { + if (start >= end) { + return Blob(listOf(), contentType) + } var i: Int = 0 val bps: MutableList = mutableListOf() diff --git a/packages/expo-blob/android/src/main/java/expo/modules/blob/BlobModule.kt b/packages/expo-blob/android/src/main/java/expo/modules/blob/BlobModule.kt index 0b9bdddeb7b314..9fd4d38d3fe9d9 100644 --- a/packages/expo-blob/android/src/main/java/expo/modules/blob/BlobModule.kt +++ b/packages/expo-blob/android/src/main/java/expo/modules/blob/BlobModule.kt @@ -2,6 +2,8 @@ package expo.modules.blob import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition +import kotlin.math.max +import kotlin.math.min class BlobModule : Module() { override fun definition() = ModuleDefinition { @@ -24,12 +26,16 @@ class BlobModule : Module() { Function("slice") { blob: Blob, start: Int?, end: Int?, contentType: String? -> var sliceStart: Int = start ?: 0 - var sliceEnd: Int = end ?: 0 + var sliceEnd: Int = end ?: blob.size if (sliceStart < 0) { - sliceStart = blob.size + sliceStart + sliceStart = max(blob.size + sliceStart, 0) + } else { + sliceStart = min(sliceStart, blob.size) } if (sliceEnd < 0) { - sliceEnd = blob.size + sliceEnd + sliceEnd = max(blob.size + sliceEnd, 0) + } else { + sliceEnd = min(sliceEnd, blob.size) } blob.slice(sliceStart, sliceEnd, contentType ?: "") } diff --git a/packages/expo-blob/build/BlobModule.d.ts.map b/packages/expo-blob/build/BlobModule.d.ts.map index 1a8d2dff32d89a..592bf6c337aded 100644 --- a/packages/expo-blob/build/BlobModule.d.ts.map +++ b/packages/expo-blob/build/BlobModule.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,YAAY,EAAE,MAAM,MAAM,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,OAAO,UAAW,SAAQ,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAC7D,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IACnE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC5B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,QAAQ,IAAI,MAAM;CAClB;AAED,OAAO,OAAO,cAAe,SAAQ,YAAY;IAChD,IAAI,EAAE,OAAO,UAAU,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,gBAAkD,CAAC;AAEzE,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACtD,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe;IAUxE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IAOnE,MAAM,IAAI,cAAc;IAiBlB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAG7C"} \ No newline at end of file +{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,YAAY,EAAE,MAAM,MAAM,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,OAAO,UAAW,SAAQ,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAC7D,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IACnE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC5B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,QAAQ,IAAI,MAAM;CAClB;AAED,OAAO,OAAO,cAAe,SAAQ,YAAY;IAChD,IAAI,EAAE,OAAO,UAAU,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,gBAAkD,CAAC;AAEzE,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACtD,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe;IAaxE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IAOnE,MAAM,IAAI,cAAc;IAiBlB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAG7C"} \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.js b/packages/expo-blob/build/BlobModule.js index 53c193782a6a49..14bc5de3f6e31f 100644 --- a/packages/expo-blob/build/BlobModule.js +++ b/packages/expo-blob/build/BlobModule.js @@ -3,6 +3,9 @@ import { normalizedContentType } from "./utils"; const NativeBlobModule = requireNativeModule("ExpoBlob"); export class ExpoBlob extends NativeBlobModule.Blob { constructor(blobParts, options) { + if (options) { + options.type = normalizedContentType(options.type); + } if (!blobParts) { super([], options); } @@ -14,7 +17,7 @@ export class ExpoBlob extends NativeBlobModule.Blob { } } slice(start, end, contentType) { - const normalizedType = contentType ?? normalizedContentType(contentType); + const normalizedType = contentType ? normalizedContentType(contentType) : ""; const slicedBlob = super.slice(start, end, normalizedType); Object.setPrototypeOf(slicedBlob, ExpoBlob.prototype); return slicedBlob; diff --git a/packages/expo-blob/build/BlobModule.js.map b/packages/expo-blob/build/BlobModule.js.map index 92a91f40466193..e9c476a9bbd848 100644 --- a/packages/expo-blob/build/BlobModule.js.map +++ b/packages/expo-blob/build/BlobModule.js.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QACtD,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,WAAW,IAAI,qBAAqB,CAAC,WAAW,CAAC,CAAC;QACzE,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = contentType ?? normalizedContentType(contentType);\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer);\n\t}\n}\n"]} \ No newline at end of file +{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QACtD,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (options) {\n\t\t\toptions.type = normalizedContentType(options.type)\n\t\t}\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = contentType ? normalizedContentType(contentType) : \"\";\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer);\n\t}\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/src/BlobModule.ts b/packages/expo-blob/src/BlobModule.ts index cc71776fb5fac9..aeb292632b6906 100644 --- a/packages/expo-blob/src/BlobModule.ts +++ b/packages/expo-blob/src/BlobModule.ts @@ -19,6 +19,9 @@ const NativeBlobModule = requireNativeModule("ExpoBlob"); export class ExpoBlob extends NativeBlobModule.Blob implements Blob { constructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) { + if (options) { + options.type = normalizedContentType(options.type) + } if (!blobParts) { super([], options); } else if (blobParts instanceof Array) { @@ -29,7 +32,7 @@ export class ExpoBlob extends NativeBlobModule.Blob implements Blob { } slice(start?: number, end?: number, contentType?: string): ExpoBlob { - const normalizedType = contentType ?? normalizedContentType(contentType); + const normalizedType = contentType ? normalizedContentType(contentType) : ""; const slicedBlob = super.slice(start, end, normalizedType); Object.setPrototypeOf(slicedBlob, ExpoBlob.prototype); return slicedBlob; From e80c2e01d46918ed3eb79a66b90c55d454ab6e18 Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 15:31:00 +0200 Subject: [PATCH 06/11] Array buffer semi-fix, null type fix, byte offset fix --- .../src/main/java/expo/modules/blob/Blob.kt | 2 +- packages/expo-blob/build/BlobModule.d.ts.map | 2 +- packages/expo-blob/build/BlobModule.js | 13 ++++++++++--- packages/expo-blob/build/BlobModule.js.map | 2 +- packages/expo-blob/build/utils.d.ts.map | 2 +- packages/expo-blob/build/utils.js | 2 ++ packages/expo-blob/build/utils.js.map | 2 +- packages/expo-blob/src/BlobModule.ts | 15 ++++++++++++--- packages/expo-blob/src/utils.ts | 1 + 9 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt b/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt index 5e7be6d72c26cc..273fb3500c1f17 100644 --- a/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt +++ b/packages/expo-blob/android/src/main/java/expo/modules/blob/Blob.kt @@ -89,7 +89,7 @@ private fun TypedArray.bytes(): ByteArray { var ba = ByteArray(this.byteLength) for (i in 0.. { + if (v instanceof ArrayBuffer) { + // TODO maybe do this natively not in typescript? + return new Uint8Array(v); + } + return v; + }; if (!blobParts) { super([], options); } else if (blobParts instanceof Array) { - super(blobParts.flat(Infinity), options); + super(blobParts.flat(Infinity).map(inputMapping), options); } else { - super(Array.from(blobParts).flat(Infinity), options); + super(Array.from(blobParts).flat(Infinity).map(inputMapping), options); } } slice(start, end, contentType) { - const normalizedType = contentType ? normalizedContentType(contentType) : ""; + const normalizedType = normalizedContentType(contentType); const slicedBlob = super.slice(start, end, normalizedType); Object.setPrototypeOf(slicedBlob, ExpoBlob.prototype); return slicedBlob; diff --git a/packages/expo-blob/build/BlobModule.js.map b/packages/expo-blob/build/BlobModule.js.map index e9c476a9bbd848..cc9f447dfc0fee 100644 --- a/packages/expo-blob/build/BlobModule.js.map +++ b/packages/expo-blob/build/BlobModule.js.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QACtD,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (options) {\n\t\t\toptions.type = normalizedContentType(options.type)\n\t\t}\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = contentType ? normalizedContentType(contentType) : \"\";\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer);\n\t}\n}\n"]} \ No newline at end of file +{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,CAAO,EAAE,EAAE;YAChC,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;YACzB,CAAC;YACD,OAAO,CAAC,CAAA;QACT,CAAC,CAAA;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACxE,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (options) {\n\t\t\toptions.type = normalizedContentType(options.type)\n\t\t}\n\n\t\tconst inputMapping = (v : any) => {\n\t\t\tif (v instanceof ArrayBuffer) {\n\t\t\t\t// TODO maybe do this natively not in typescript?\n\t\t\t\treturn new Uint8Array(v)\n\t\t\t}\n\t\t\treturn v\n\t\t}\n\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity).map(inputMapping), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity).map(inputMapping), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = normalizedContentType(contentType);\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer);\n\t}\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/build/utils.d.ts.map b/packages/expo-blob/build/utils.d.ts.map index b19ab9410a6b2a..ac0bc57022e1cf 100644 --- a/packages/expo-blob/build/utils.d.ts.map +++ b/packages/expo-blob/build/utils.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAK3D"} \ No newline at end of file +{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAM3D"} \ No newline at end of file diff --git a/packages/expo-blob/build/utils.js b/packages/expo-blob/build/utils.js index 35e1a30b50ac45..aeee6cfcf0facb 100644 --- a/packages/expo-blob/build/utils.js +++ b/packages/expo-blob/build/utils.js @@ -14,6 +14,8 @@ * @returns The normalized (lowercased) content type, or an empty string if invalid. */ export function normalizedContentType(type) { + if (type === null) + return "null"; if (!type || type.length === 0) return ""; const asciiPrintable = /^[\x20-\x7E]+$/; diff --git a/packages/expo-blob/build/utils.js.map b/packages/expo-blob/build/utils.js.map index 1acb66f43b19ab..3ba32545f28e93 100644 --- a/packages/expo-blob/build/utils.js.map +++ b/packages/expo-blob/build/utils.js.map @@ -1 +1 @@ -{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAa;IAClD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,gBAAgB,CAAC;IACxC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * Normalizes the content type string for a Blob.\n *\n * Returns the lowercased content type if it is valid, or an empty string otherwise.\n *\n * A valid content type:\n * - Is not null, undefined, or empty\n * - Contains only printable ASCII characters (0x20–0x7E)\n * - Does not contain forbidden control characters: NUL (\\x00), LF (\\x0A), or CR (\\x0D)\n *\n * If any of these conditions are not met, returns an empty string to indicate an invalid or unsafe content type.\n *\n * @param type The content type string to normalize.\n * @returns The normalized (lowercased) content type, or an empty string if invalid.\n */\nexport function normalizedContentType(type?: string): string {\n\tif (!type || type.length === 0) return \"\";\n\tconst asciiPrintable = /^[\\x20-\\x7E]+$/;\n\tif (!asciiPrintable.test(type)) return \"\";\n\treturn type.toLowerCase();\n}\n"]} \ No newline at end of file +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAa;IAClD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IAChC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,gBAAgB,CAAC;IACxC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * Normalizes the content type string for a Blob.\n *\n * Returns the lowercased content type if it is valid, or an empty string otherwise.\n *\n * A valid content type:\n * - Is not null, undefined, or empty\n * - Contains only printable ASCII characters (0x20–0x7E)\n * - Does not contain forbidden control characters: NUL (\\x00), LF (\\x0A), or CR (\\x0D)\n *\n * If any of these conditions are not met, returns an empty string to indicate an invalid or unsafe content type.\n *\n * @param type The content type string to normalize.\n * @returns The normalized (lowercased) content type, or an empty string if invalid.\n */\nexport function normalizedContentType(type?: string): string {\n\tif (type === null) return \"null\"\n\tif (!type || type.length === 0) return \"\";\n\tconst asciiPrintable = /^[\\x20-\\x7E]+$/;\n\tif (!asciiPrintable.test(type)) return \"\";\n\treturn type.toLowerCase();\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/src/BlobModule.ts b/packages/expo-blob/src/BlobModule.ts index aeb292632b6906..423a8d82c7b6cc 100644 --- a/packages/expo-blob/src/BlobModule.ts +++ b/packages/expo-blob/src/BlobModule.ts @@ -22,17 +22,26 @@ export class ExpoBlob extends NativeBlobModule.Blob implements Blob { if (options) { options.type = normalizedContentType(options.type) } + + const inputMapping = (v : any) => { + if (v instanceof ArrayBuffer) { + // TODO maybe do this natively not in typescript? + return new Uint8Array(v) + } + return v + } + if (!blobParts) { super([], options); } else if (blobParts instanceof Array) { - super(blobParts.flat(Infinity), options); + super(blobParts.flat(Infinity).map(inputMapping), options); } else { - super(Array.from(blobParts).flat(Infinity), options); + super(Array.from(blobParts).flat(Infinity).map(inputMapping), options); } } slice(start?: number, end?: number, contentType?: string): ExpoBlob { - const normalizedType = contentType ? normalizedContentType(contentType) : ""; + const normalizedType = normalizedContentType(contentType); const slicedBlob = super.slice(start, end, normalizedType); Object.setPrototypeOf(slicedBlob, ExpoBlob.prototype); return slicedBlob; diff --git a/packages/expo-blob/src/utils.ts b/packages/expo-blob/src/utils.ts index f36ed8189b3acc..11c745155ee720 100644 --- a/packages/expo-blob/src/utils.ts +++ b/packages/expo-blob/src/utils.ts @@ -14,6 +14,7 @@ * @returns The normalized (lowercased) content type, or an empty string if invalid. */ export function normalizedContentType(type?: string): string { + if (type === null) return "null" if (!type || type.length === 0) return ""; const asciiPrintable = /^[\x20-\x7E]+$/; if (!asciiPrintable.test(type)) return ""; From 9a6d120ad5b5723562760c15f52d14bfe46f6b8e Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 16:22:25 +0200 Subject: [PATCH 07/11] array buffer fix --- packages/expo-blob/build/BlobModule.js | 2 +- packages/expo-blob/build/BlobModule.js.map | 2 +- packages/expo-blob/src/BlobModule.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/expo-blob/build/BlobModule.js b/packages/expo-blob/build/BlobModule.js index 3cabcefc398403..780490540654bf 100644 --- a/packages/expo-blob/build/BlobModule.js +++ b/packages/expo-blob/build/BlobModule.js @@ -47,7 +47,7 @@ export class ExpoBlob extends NativeBlobModule.Blob { }); } async arrayBuffer() { - return super.bytes().then((bytes) => bytes.buffer); + return super.bytes().then((bytes) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)); } } //# sourceMappingURL=BlobModule.js.map \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.js.map b/packages/expo-blob/build/BlobModule.js.map index cc9f447dfc0fee..d3b220a7d35f6b 100644 --- a/packages/expo-blob/build/BlobModule.js.map +++ b/packages/expo-blob/build/BlobModule.js.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,CAAO,EAAE,EAAE;YAChC,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;YACzB,CAAC;YACD,OAAO,CAAC,CAAA;QACT,CAAC,CAAA;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACxE,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (options) {\n\t\t\toptions.type = normalizedContentType(options.type)\n\t\t}\n\n\t\tconst inputMapping = (v : any) => {\n\t\t\tif (v instanceof ArrayBuffer) {\n\t\t\t\t// TODO maybe do this natively not in typescript?\n\t\t\t\treturn new Uint8Array(v)\n\t\t\t}\n\t\t\treturn v\n\t\t}\n\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity).map(inputMapping), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity).map(inputMapping), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = normalizedContentType(contentType);\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer);\n\t}\n}\n"]} \ No newline at end of file +{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,CAAO,EAAE,EAAE;YAChC,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;YACzB,CAAC;YACD,OAAO,CAAC,CAAA;QACT,CAAC,CAAA;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACxE,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7H,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (options) {\n\t\t\toptions.type = normalizedContentType(options.type)\n\t\t}\n\n\t\tconst inputMapping = (v : any) => {\n\t\t\tif (v instanceof ArrayBuffer) {\n\t\t\t\t// TODO maybe do this natively not in typescript?\n\t\t\t\treturn new Uint8Array(v)\n\t\t\t}\n\t\t\treturn v\n\t\t}\n\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity).map(inputMapping), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity).map(inputMapping), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = normalizedContentType(contentType);\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));\n\t}\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/src/BlobModule.ts b/packages/expo-blob/src/BlobModule.ts index 423a8d82c7b6cc..88dadc971f34e2 100644 --- a/packages/expo-blob/src/BlobModule.ts +++ b/packages/expo-blob/src/BlobModule.ts @@ -65,6 +65,6 @@ export class ExpoBlob extends NativeBlobModule.Blob implements Blob { } async arrayBuffer(): Promise { - return super.bytes().then((bytes: Uint8Array) => bytes.buffer); + return super.bytes().then((bytes: Uint8Array) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)); } } From 440dbf63ec7f2fc0378d95f7d138b92cc453feb6 Mon Sep 17 00:00:00 2001 From: HubertBer Date: Mon, 21 Jul 2025 16:54:18 +0200 Subject: [PATCH 08/11] removed android build files --- .../results.bin | 1 - .../transformed/classes/classes_dex/classes.dex | Bin 45756 -> 0 bytes .../results.bin | 1 - .../transformed/classes/classes_dex/classes.dex | Bin 45772 -> 0 bytes 4 files changed, 2 deletions(-) delete mode 100644 packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin delete mode 100644 packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/transformed/classes/classes_dex/classes.dex delete mode 100644 packages/expo-blob/android/build/.transforms/8f8a6c2346678f20ad35dd488b88f140/results.bin delete mode 100644 packages/expo-blob/android/build/.transforms/8f8a6c2346678f20ad35dd488b88f140/transformed/classes/classes_dex/classes.dex diff --git a/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin b/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin deleted file mode 100644 index 0d259ddcb52852..00000000000000 --- a/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/classes diff --git a/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/transformed/classes/classes_dex/classes.dex b/packages/expo-blob/android/build/.transforms/4b06e88cbc20d0207e8f18ecaa322547/transformed/classes/classes_dex/classes.dex deleted file mode 100644 index 906cd2e1836d6bae41bfd64f9ea55901218437c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45756 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 d Date: Mon, 21 Jul 2025 17:10:45 +0200 Subject: [PATCH 09/11] Add link to the original tests on which the test suite is based --- apps/test-suite/tests/Blob.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/test-suite/tests/Blob.ts b/apps/test-suite/tests/Blob.ts index 0c308b1cf34cb4..fcffa2fa69bb5b 100644 --- a/apps/test-suite/tests/Blob.ts +++ b/apps/test-suite/tests/Blob.ts @@ -1,3 +1,5 @@ +// Based on tests in https://github.com/web-platform-tests/wpt/tree/master/FileAPI/blob + import {ExpoBlob as Blob} from "expo-blob" import { Platform } from 'expo-modules-core'; From 9d5297bc8732217033b09cc6352acad7d4342ab1 Mon Sep 17 00:00:00 2001 From: HubertBer Date: Tue, 22 Jul 2025 12:49:05 +0200 Subject: [PATCH 10/11] small fixes --- apps/test-suite/tests/Blob.ts | 2 +- packages/expo-blob/build/BlobModule.d.ts.map | 2 +- packages/expo-blob/build/BlobModule.js | 10 +--------- packages/expo-blob/build/BlobModule.js.map | 2 +- packages/expo-blob/src/BlobModule.ts | 8 +------- 5 files changed, 5 insertions(+), 19 deletions(-) diff --git a/apps/test-suite/tests/Blob.ts b/apps/test-suite/tests/Blob.ts index fcffa2fa69bb5b..2342aa7d6023fb 100644 --- a/apps/test-suite/tests/Blob.ts +++ b/apps/test-suite/tests/Blob.ts @@ -1203,4 +1203,4 @@ export async function test({ describe, it, expect, jasmine }) { }) }) }); -} \ No newline at end of file +} diff --git a/packages/expo-blob/build/BlobModule.d.ts.map b/packages/expo-blob/build/BlobModule.d.ts.map index 5d7652dfbf76c0..dee053f003164e 100644 --- a/packages/expo-blob/build/BlobModule.d.ts.map +++ b/packages/expo-blob/build/BlobModule.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,YAAY,EAAE,MAAM,MAAM,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,OAAO,UAAW,SAAQ,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAC7D,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IACnE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC5B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,QAAQ,IAAI,MAAM;CAClB;AAED,OAAO,OAAO,cAAe,SAAQ,YAAY;IAChD,IAAI,EAAE,OAAO,UAAU,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,gBAAkD,CAAC;AAEzE,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACtD,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe;IAsBxE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IAOnE,MAAM,IAAI,cAAc;IAiBlB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAG7C"} \ No newline at end of file +{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,YAAY,EAAE,MAAM,MAAM,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,OAAO,UAAW,SAAQ,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAC7D,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IACnE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC5B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,QAAQ,IAAI,MAAM;CAClB;AAED,OAAO,OAAO,cAAe,SAAQ,YAAY;IAChD,IAAI,EAAE,OAAO,UAAU,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,gBAAkD,CAAC;AAEzE,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACtD,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe;IAgBxE,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IAOnE,MAAM,IAAI,cAAc;IAiBlB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAG7C"} \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.js b/packages/expo-blob/build/BlobModule.js index 780490540654bf..7ff06504cb20a6 100644 --- a/packages/expo-blob/build/BlobModule.js +++ b/packages/expo-blob/build/BlobModule.js @@ -13,15 +13,7 @@ export class ExpoBlob extends NativeBlobModule.Blob { } return v; }; - if (!blobParts) { - super([], options); - } - else if (blobParts instanceof Array) { - super(blobParts.flat(Infinity).map(inputMapping), options); - } - else { - super(Array.from(blobParts).flat(Infinity).map(inputMapping), options); - } + super(Array.from(blobParts ?? []).flat(Infinity).map(inputMapping), options); } slice(start, end, contentType) { const normalizedType = normalizedContentType(contentType); diff --git a/packages/expo-blob/build/BlobModule.js.map b/packages/expo-blob/build/BlobModule.js.map index d3b220a7d35f6b..41494696e46bbf 100644 --- a/packages/expo-blob/build/BlobModule.js.map +++ b/packages/expo-blob/build/BlobModule.js.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,CAAO,EAAE,EAAE;YAChC,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;YACzB,CAAC;YACD,OAAO,CAAC,CAAA;QACT,CAAC,CAAA;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,SAAS,YAAY,KAAK,EAAE,CAAC;YACvC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;QACxE,CAAC;IACF,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7H,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (options) {\n\t\t\toptions.type = normalizedContentType(options.type)\n\t\t}\n\n\t\tconst inputMapping = (v : any) => {\n\t\t\tif (v instanceof ArrayBuffer) {\n\t\t\t\t// TODO maybe do this natively not in typescript?\n\t\t\t\treturn new Uint8Array(v)\n\t\t\t}\n\t\t\treturn v\n\t\t}\n\n\t\tif (!blobParts) {\n\t\t\tsuper([], options);\n\t\t} else if (blobParts instanceof Array) {\n\t\t\tsuper(blobParts.flat(Infinity).map(inputMapping), options);\n\t\t} else {\n\t\t\tsuper(Array.from(blobParts).flat(Infinity).map(inputMapping), options);\n\t\t}\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = normalizedContentType(contentType);\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));\n\t}\n}\n"]} \ No newline at end of file +{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IAClD,YAAY,SAAiC,EAAE,OAAyB;QACvE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,CAAO,EAAE,EAAE;YAChC,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;YACzB,CAAC;YACD,OAAO,CAAC,CAAA;QACT,CAAC,CAAA;QAED,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACvD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,MAAM;QACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACrC,IAAI,CAAC,UAAU;gBACd,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC3B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7H,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from \"expo\";\nimport { Blob, BlobPart } from \"./BlobModule.types\";\nimport { normalizedContentType } from \"./utils\";\ndeclare class NativeBlob extends SharedObject {\n\treadonly size: number;\n\treadonly type: string;\n\tconstructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob;\n\tbytes(): Promise;\n\ttext(): Promise;\n\tsyncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n\tBlob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule(\"ExpoBlob\");\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n\tconstructor(blobParts?: any[] | Iterable, options?: BlobPropertyBag) {\n\t\tif (options) {\n\t\t\toptions.type = normalizedContentType(options.type)\n\t\t}\n\n\t\tconst inputMapping = (v : any) => {\n\t\t\tif (v instanceof ArrayBuffer) {\n\t\t\t\t// TODO maybe do this natively not in typescript?\n\t\t\t\treturn new Uint8Array(v)\n\t\t\t}\n\t\t\treturn v\n\t\t}\n\n\t\tsuper(Array.from(blobParts ?? []).flat(Infinity).map(inputMapping), options);\n\t}\n\n\tslice(start?: number, end?: number, contentType?: string): ExpoBlob {\n\t\tconst normalizedType = normalizedContentType(contentType);\n\t\tconst slicedBlob = super.slice(start, end, normalizedType);\n\t\tObject.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n\t\treturn slicedBlob;\n\t}\n\n\tstream(): ReadableStream {\n\t\tconst text = super.syncText();\n\t\tconst encoder = new TextEncoder();\n\t\tconst uint8 = encoder.encode(text);\n\t\tlet offset = 0;\n\t\treturn new ReadableStream({\n\t\t\tpull(controller) {\n\t\t\t\tif (offset < uint8.length) {\n\t\t\t\t\tcontroller.enqueue(uint8.subarray(offset));\n\t\t\t\t\toffset = uint8.length;\n\t\t\t\t} else {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));\n\t}\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/src/BlobModule.ts b/packages/expo-blob/src/BlobModule.ts index 88dadc971f34e2..062dd3373a48b1 100644 --- a/packages/expo-blob/src/BlobModule.ts +++ b/packages/expo-blob/src/BlobModule.ts @@ -31,13 +31,7 @@ export class ExpoBlob extends NativeBlobModule.Blob implements Blob { return v } - if (!blobParts) { - super([], options); - } else if (blobParts instanceof Array) { - super(blobParts.flat(Infinity).map(inputMapping), options); - } else { - super(Array.from(blobParts).flat(Infinity).map(inputMapping), options); - } + super(Array.from(blobParts ?? []).flat(Infinity).map(inputMapping), options); } slice(start?: number, end?: number, contentType?: string): ExpoBlob { From 16071286df48d985298ed049fc73591e4e428cc0 Mon Sep 17 00:00:00 2001 From: HubertBer Date: Tue, 22 Jul 2025 13:20:19 +0200 Subject: [PATCH 11/11] prettier formating --- apps/test-suite/tests/Blob.ts | 2529 ++++++++++-------- packages/expo-blob/build/BlobModule.d.ts.map | 2 +- packages/expo-blob/build/BlobModule.js | 4 +- packages/expo-blob/build/BlobModule.js.map | 2 +- packages/expo-blob/build/utils.js | 6 +- packages/expo-blob/build/utils.js.map | 2 +- packages/expo-blob/src/BlobModule.ts | 10 +- packages/expo-blob/src/utils.ts | 10 +- 8 files changed, 1382 insertions(+), 1183 deletions(-) diff --git a/apps/test-suite/tests/Blob.ts b/apps/test-suite/tests/Blob.ts index 2342aa7d6023fb..6f70c16b456d97 100644 --- a/apps/test-suite/tests/Blob.ts +++ b/apps/test-suite/tests/Blob.ts @@ -1,1206 +1,1399 @@ // Based on tests in https://github.com/web-platform-tests/wpt/tree/master/FileAPI/blob -import {ExpoBlob as Blob} from "expo-blob" +import { ExpoBlob as Blob } from 'expo-blob'; import { Platform } from 'expo-modules-core'; export const name = 'Blob'; var test_error = { - name: "test", - message: "test error", + name: 'test', + message: 'test error', }; export async function test({ describe, it, expect, jasmine }) { - const test_blob = (fn, expectations) => { - var expected = expectations.expected, - type = expectations.type, - desc = expectations.desc; - - it(desc, async (t) => { - var blob = fn(); - expect(blob instanceof Blob).toBeTruthy(); - expect(blob instanceof File).toBeFalsy(); - expect(blob.type).toEqual(type); - expect(blob.size).toBe(expected.length); - - const text = await blob.text(); - const text1 = blob.syncText(); - expect(text).toEqual(expected); - expect(text).toEqual(text1); - }); + const test_blob = (fn, expectations) => { + var expected = expectations.expected, + type = expectations.type, + desc = expectations.desc; + + it(desc, async (t) => { + var blob = fn(); + expect(blob instanceof Blob).toBeTruthy(); + expect(blob instanceof File).toBeFalsy(); + expect(blob.type).toEqual(type); + expect(blob.size).toBe(expected.length); + + const text = await blob.text(); + const text1 = blob.syncText(); + expect(text).toEqual(expected); + expect(text).toEqual(text1); + }); + }; + const test_blob_binary = async (fn, expectations) => { + var expected = expectations.expected, + type = expectations.type, + desc = expectations.desc; + it(desc, async () => { + var blob = fn(); + expect(blob instanceof Blob).toBeTruthy(); + expect(blob instanceof File).toBeFalsy(); + expect(blob.type).toBe(type); + expect(blob.size).toBe(expected.length); + + const ab = await blob.arrayBuffer(); + expect(ab instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(ab)).toEqual(expected); + }); + }; + + // Helper function that triggers garbage collection while reading a chunk + // if perform_gc is true. + const read_and_gc = async (reader, perform_gc) => { + // Passing Uint8Array for byte streams; non-byte streams will simply ignore it + const read_promise = reader.read(new Uint8Array(64)); + if (perform_gc) { + // TODO Actually perform garbage collection in here + // await garbageCollect(); } - const test_blob_binary = async (fn, expectations) => { - var expected = expectations.expected, - type = expectations.type, - desc = expectations.desc; - it(desc, async () => { - var blob = fn(); - expect(blob instanceof Blob).toBeTruthy(); - expect(blob instanceof File).toBeFalsy(); - expect(blob.type).toBe(type); - expect(blob.size).toBe(expected.length); - - const ab = await blob.arrayBuffer(); - expect(ab instanceof ArrayBuffer).toBeTruthy(); - expect(new Uint8Array(ab)).toEqual(expected); - }) + return read_promise; + }; + + // Takes in a ReadableStream and reads from it until it is done, returning + // an array that contains the results of each read operation. If perform_gc + // is true, garbage collection is triggered while reading every chunk. + const read_all_chunks = async (stream, { perform_gc = false, mode } = {}) => { + expect(stream instanceof ReadableStream).toBeTruthy(); + expect('getReader' in stream).toBeTruthy(); + const reader = stream.getReader({ mode }); + + expect('read' in reader).toBeTruthy(); + let read_value = await read_and_gc(reader, perform_gc); + + let out = []; + let i = 0; + while (!read_value.done) { + for (let val of read_value.value) { + out[i++] = val; + } + read_value = await read_and_gc(reader, perform_gc); } + return out; + }; + + describe('Blob', async () => { + describe('Blob creation', () => { + it('Empty blob', () => { + const blob = new Blob([]); + expect(blob).toBeTruthy(); + }); + it('String blob', () => { + const blob = new Blob(['ab', 'cd']); + expect(blob).toBeTruthy(); + }); + it('TypedArray blob', () => { + const blob = new Blob([Int32Array.from([-1, 2]), Uint8Array.from([1, 2, 0])]); + expect(blob).toBeTruthy(); + }); + it('Blob blob', () => { + const blob = new Blob([new Blob([]), new Blob(['a', 'b'])]); + expect(blob).toBeTruthy(); + }); + it('Mixed blob', () => { + const blob0 = new Blob([Int32Array.from([-1, 2]), Uint8Array.from([1, 2, 0])]); + const blob1 = new Blob(['ab', 'cd']); + const blob2 = new Blob(['a', new Blob([new Blob(['b'])])]); + const blob = new Blob([blob0, blob1, blob2]); + expect(blob).toBeTruthy(); + }); + it('Blob flatten', () => { + const blob = new Blob(['aa', ['ab', 'cd'], 'ef']); + expect(blob).toBeTruthy(); + }); + }); - // Helper function that triggers garbage collection while reading a chunk - // if perform_gc is true. - const read_and_gc = async (reader, perform_gc) => { - // Passing Uint8Array for byte streams; non-byte streams will simply ignore it - const read_promise = reader.read(new Uint8Array(64)); - if (perform_gc) { - // TODO Actually perform garbage collection in here - // await garbageCollect(); + describe('Array buffer', () => { + it('simple', async () => { + const input_arr = new TextEncoder().encode('PASS'); + const blob = new Blob([input_arr]); + const array_buffer = await blob.arrayBuffer(); + expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); + }); + it('empty Blob data', async () => { + const input_arr = new TextEncoder().encode(''); + const blob = new Blob([input_arr]); + const array_buffer = await blob.arrayBuffer(); + expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); + }); + it('non-ascii input', async () => { + const input_arr = new TextEncoder().encode('\u08B8\u000a'); + const blob = new Blob([input_arr]); + const array_buffer = await blob.arrayBuffer(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); + }); + it('non-unicode input', async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + const blob = new Blob([typed_arr]); + // const array_buffer = await blob.arrayBuffer(); + const array_buffer = await blob.bytes(); + // expect(blob.syncText()).toEqual("\u0008\u00F1\u0030\u007B\u0097") + // expect(new Uint8Array(array_buffer) == typed_arr).toBeTruthy() + // expect(new Uint8Array(array_buffer)).toEqual(typed_arr); + expect(blob.size).toBe(5); + expect(array_buffer.length).toBe(5); + console.log(array_buffer.byteOffset); + console.log(array_buffer, typed_arr); + expect(array_buffer).toEqual(typed_arr); + }); + it('concurrent reads', async () => { + const input_arr = new TextEncoder().encode('PASS'); + const blob = new Blob([input_arr]); + const array_buffer_results = await Promise.all([ + blob.arrayBuffer(), + blob.arrayBuffer(), + blob.arrayBuffer(), + ]); + for (let array_buffer of array_buffer_results) { + expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); + expect(new Uint8Array(array_buffer)).toEqual(input_arr); } - return read_promise; - } + }); + }); - // Takes in a ReadableStream and reads from it until it is done, returning - // an array that contains the results of each read operation. If perform_gc - // is true, garbage collection is triggered while reading every chunk. - const read_all_chunks = async (stream, { perform_gc = false, mode } = {}) => { - expect(stream instanceof ReadableStream).toBeTruthy(); - expect('getReader' in stream).toBeTruthy(); - const reader = stream.getReader({ mode }); - - expect('read' in reader).toBeTruthy() - let read_value = await read_and_gc(reader, perform_gc); - - let out = []; - let i = 0; - while (!read_value.done) { - for (let val of read_value.value) { - out[i++] = val; - } - read_value = await read_and_gc(reader, perform_gc); + describe('Bytes', async () => { + it('Simple', async () => { + const input_arr = new TextEncoder().encode('PASS'); + const blob = new Blob([input_arr]); + const uint8array = await blob.bytes(); + expect(uint8array instanceof Uint8Array).toBeTruthy(); + expect(uint8array).toEqual(input_arr); + }); + it('empty Blob data', async () => { + const input_arr = new TextEncoder().encode(''); + const blob = new Blob([input_arr]); + const uint8array = await blob.bytes(); + expect(uint8array instanceof Uint8Array).toBeTruthy(); + expect(uint8array).toEqual(input_arr); + }); + it('non-ascii input', async () => { + const input_arr = new TextEncoder().encode('\u08B8\u000a'); + const blob = new Blob([input_arr]); + const uint8array = await blob.bytes(); + expect(uint8array).toEqual(input_arr); + }); + it('non-unicode input', async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + const blob = new Blob([typed_arr]); + const uint8array = await blob.bytes(); + expect(uint8array).toEqual(typed_arr); + }); + it('concurrent reads', async () => { + const input_arr = new TextEncoder().encode('PASS'); + const blob = new Blob([input_arr]); + const uint8array_results = await Promise.all([blob.bytes(), blob.bytes(), blob.bytes()]); + for (let uint8array of uint8array_results) { + expect(uint8array instanceof Uint8Array).toBeTruthy(); + expect(uint8array).toEqual(input_arr); } - return out; - } + }); + }); - describe('Blob', async () => { - describe('Blob creation', () => { - it('Empty blob', () => { - const blob = new Blob([]); - expect(blob).toBeTruthy(); - }); - it('String blob', () => { - const blob = new Blob(["ab", "cd"]); - expect(blob).toBeTruthy(); - }); - it('TypedArray blob', () => { - const blob = new Blob([Int32Array.from([-1, 2]), Uint8Array.from([1, 2, 0])]); - expect(blob).toBeTruthy(); - }); - it('Blob blob', () => { - const blob = new Blob([new Blob([]), new Blob(["a", "b"])]); - expect(blob).toBeTruthy(); - }) - it('Mixed blob', () => { - const blob0 = new Blob([Int32Array.from([-1, 2]), Uint8Array.from([1, 2, 0])]); - const blob1 = new Blob(["ab", "cd"]); - const blob2 = new Blob(["a", new Blob([new Blob(["b"])])]) - const blob = new Blob([blob0, blob1, blob2]); - expect(blob).toBeTruthy(); - }) - it('Blob flatten', () => { - const blob = new Blob(["aa", ["ab", "cd"], "ef"]) - expect(blob).toBeTruthy(); + // Windows platforms use CRLF as the native line ending. All others use LF. + const crlf = Platform.OS == 'windows'; + const native_ending = crlf ? '\r\n' : '\n'; + describe('constructor-endings', async () => { + it('valid endings value', () => { + const blob0 = new Blob([], { endings: 'native' }); + const blob1 = new Blob([], { endings: 'transparent' }); + expect(blob0).toBeTruthy(); + expect(blob1).toBeTruthy(); + }); + it('invalud endings value', () => { + [null, '', 'invalidEnumValue', 'Transparent', 'NATIVE', 0, {}].forEach((ending) => { + // @ts-expect-error + expect(() => new Blob([], { endings: ending })).toThrow(); + }); + }); + it('Exception propagation from options', () => { + const test_error = { name: 'test string' }; + // @ts-expect-error + expect( + () => + new Blob([], { + get endings() { + throw test_error; + }, }) + ).toThrow('test string'); + }); + // TODO weird test, as it could maybe be used lazily and not in the constructor + it("The 'endings' options property is used", () => { + let got = false; + // @ts-expect-error + new Blob([], { + get endings() { + got = true; + }, + }); + expect(got).toBeTruthy(); + }); + const sampleEndings = [ + { name: 'LF', input: '\n', native: native_ending }, + { name: 'CR', input: '\r', native: native_ending }, + + { name: 'CRLF', input: '\r\n', native: native_ending }, + { name: 'CRCR', input: '\r\r', native: native_ending.repeat(2) }, + { name: 'LFCR', input: '\n\r', native: native_ending.repeat(2) }, + { name: 'LFLF', input: '\n\n', native: native_ending.repeat(2) }, + + { name: 'CRCRLF', input: '\r\r\n', native: native_ending.repeat(2) }, + { name: 'CRLFLF', input: '\r\n\n', native: native_ending.repeat(2) }, + { name: 'CRLFCR', input: '\r\n\r\n', native: native_ending.repeat(2) }, + + { name: 'CRLFCRLF', input: '\r\n\r\n', native: native_ending.repeat(2) }, + { name: 'LFCRLFCR', input: '\n\r\n\r', native: native_ending.repeat(3) }, + ]; + it('Newlines should not change with endings unspecified', () => { + sampleEndings.forEach((testCase) => { + const blob = new Blob([testCase.input]); + expect(blob.syncText()).toBe(testCase.input); }); + }); + it('Newlines should not change with endings "transparent"', () => { + sampleEndings.forEach((testCase) => { + const blob = new Blob([testCase.input], { endings: 'transparent' }); + expect(blob.syncText()).toBe(testCase.input); + }); + }); + it('Newlines should match the platform with endings "native"', () => { + sampleEndings.forEach((testCase) => { + const blob = new Blob([testCase.input], { endings: 'native' }); + expect(blob.syncText()).toBe(testCase.native); + }); + }); + it('CR/LF in adjacent input strings', () => { + const blob = new Blob(['\r', '\n'], { endings: 'native' }); + const expected = native_ending.repeat(2); + expect(blob.syncText()).toBe(expected); + }); + }); - describe('Array buffer', () => { - it('simple', async () => { - const input_arr = new TextEncoder().encode("PASS"); - const blob = new Blob([input_arr]); - const array_buffer = await blob.arrayBuffer(); - expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); - expect(new Uint8Array(array_buffer)).toEqual(input_arr); - }); - it('empty Blob data', async () => { - const input_arr = new TextEncoder().encode(""); - const blob = new Blob([input_arr]); - const array_buffer = await blob.arrayBuffer(); - expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); - expect(new Uint8Array(array_buffer)).toEqual(input_arr); - }) - it('non-ascii input', async () => { - const input_arr = new TextEncoder().encode("\u08B8\u000a"); - const blob = new Blob([input_arr]); - const array_buffer = await blob.arrayBuffer(); - expect(new Uint8Array(array_buffer)).toEqual(input_arr); - }) - it('non-unicode input', async () => { - const input_arr = [8, 241, 48, 123, 151]; - const typed_arr = new Uint8Array(input_arr); - const blob = new Blob([typed_arr]); - // const array_buffer = await blob.arrayBuffer(); - const array_buffer = await blob.bytes(); - // expect(blob.syncText()).toEqual("\u0008\u00F1\u0030\u007B\u0097") - // expect(new Uint8Array(array_buffer) == typed_arr).toBeTruthy() - // expect(new Uint8Array(array_buffer)).toEqual(typed_arr); - expect(blob.size).toBe(5) - expect(array_buffer.length).toBe(5) - console.log(array_buffer.byteOffset) - console.log(array_buffer, typed_arr) - expect(array_buffer).toEqual(typed_arr); - }) - it('concurrent reads', async () => { - const input_arr = new TextEncoder().encode("PASS"); - const blob = new Blob([input_arr]); - const array_buffer_results = await Promise.all([blob.arrayBuffer(), - blob.arrayBuffer(), blob.arrayBuffer()]); - for (let array_buffer of array_buffer_results) { - expect(array_buffer instanceof ArrayBuffer).toBeTruthy(); - expect(new Uint8Array(array_buffer)).toEqual(input_arr); - } - }) - }) - - describe('Bytes', async () => { - it('Simple', async () => { - const input_arr = new TextEncoder().encode("PASS"); - const blob = new Blob([input_arr]); - const uint8array = await blob.bytes(); - expect(uint8array instanceof Uint8Array).toBeTruthy(); - expect(uint8array).toEqual(input_arr); - }); - it('empty Blob data', async () => { - const input_arr = new TextEncoder().encode(""); - const blob = new Blob([input_arr]); - const uint8array = await blob.bytes(); - expect(uint8array instanceof Uint8Array).toBeTruthy(); - expect(uint8array).toEqual(input_arr); - }); - it('non-ascii input', async () => { - const input_arr = new TextEncoder().encode("\u08B8\u000a"); - const blob = new Blob([input_arr]); - const uint8array = await blob.bytes(); - expect(uint8array).toEqual(input_arr); - }); - it('non-unicode input', async () => { - const input_arr = [8, 241, 48, 123, 151]; - const typed_arr = new Uint8Array(input_arr); - const blob = new Blob([typed_arr]); - const uint8array = await blob.bytes(); - expect(uint8array).toEqual(typed_arr); - }); - it('concurrent reads', async () => { - const input_arr = new TextEncoder().encode("PASS"); - const blob = new Blob([input_arr]); - const uint8array_results = await Promise.all([blob.bytes(), - blob.bytes(), blob.bytes()]); - for (let uint8array of uint8array_results) { - expect(uint8array instanceof Uint8Array).toBeTruthy(); - expect(uint8array).toEqual(input_arr); + describe('constructor', () => { + it('globalThis should have a Blob property.', () => { + expect('Blob' in globalThis).toBeTruthy(); + }); + it('Blob.length should be 0', () => { + expect(Blob.length).toBe(0); + }); + it('Blob should be a function', () => { + expect(Blob instanceof Function).toBeTruthy(); + }); + it('Blob constructor with no arguments', () => { + var blob = new Blob(); + expect(blob instanceof Blob).toBeTruthy(); + expect(String(blob)).toBe('[object Blob]'); + expect(blob.size).toBe(0); + expect(blob.type).toBe(''); + }); + it("Blob constructor with no arguments, without 'new'", () => { + expect(() => { + var blob = Blob(); + }).toThrow(); + }); + it('Blob constructor without brackets', () => { + var blob = new Blob(); + expect(blob instanceof Blob).toBeTruthy(); + expect(blob.size).toBe(0); + expect(blob.type).toBe(''); + }); + it('Blob constructor with undefined as first argument', () => { + var blob = new Blob(undefined); + expect(blob instanceof Blob).toBeTruthy(); + expect(String(blob)).toBe('[object Blob]'); + expect(blob.size).toBe(0); + expect(blob.type).toBe(''); + }); + // TODO Something wrong with null ? Why should Blob() work and Blob(null) not ??? + it('Passing non-objects, Dates and RegExps for blobParts should throw a TypeError.', () => { + var args = [ + null, + true, + false, + 0, + 1, + 1.5, + 'FAIL', + new Date(), + // @ts-expect-error + new RegExp(), + {}, + { 0: 'FAIL', length: 1 }, + ]; + args.forEach((arg) => { + // @ts-expect-error + expect(() => new Blob(arg)).toThrow(); + }); + }); + test_blob( + function () { + return new Blob({ + [Symbol.iterator]: Array.prototype[Symbol.iterator], + }); + }, + { + expected: '', + type: '', + desc: 'A plain object with @@iterator should be treated as a sequence for the blobParts argument.', + } + ); + it('A plain object with custom @@iterator should be treated as a sequence for the blobParts argument.', () => { + const blob = new Blob({ + [Symbol.iterator]() { + var i = 0; + return { + next: () => + [{ done: false, value: 'ab' }, { done: false, value: 'cde' }, { done: true }][i++], + }; + }, + }); + expect(blob.size).toBe(5); + }); + it('A plain object with @@iterator and a length property should be treated as a sequence for the blobParts argument.', () => { + let blob = new Blob({ + [Symbol.iterator]: Array.prototype[Symbol.iterator], + 0: 'PASS', + length: 1, + }); + expect(blob.syncText()).toEqual('PASS'); + expect(blob.type).toBe(''); + }); + it('A String object should be treated as a sequence for the blobParts argument.', () => { + let blob = new Blob(new String('xyz')); + expect(blob.syncText()).toEqual('xyz'); + expect(blob.type).toBe(''); + }); + test_blob( + function () { + return new Blob(new String('xyz')); + }, + { + expected: 'xyz', + type: '', + desc: 'A String object should be treated as a sequence for the blobParts argument.', + } + ); + test_blob( + function () { + return new Blob(new Uint8Array([1, 2, 3])); + }, + { + expected: '123', + type: '', + desc: 'A Uint8Array object should be treated as a sequence for the blobParts argument.', + } + ); + + it('The length getter should be invoked and any exceptions should be propagated.', () => { + expect(() => { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + get length() { + throw test_error; + }, + }; + new Blob(obj); + }).toThrow(test_error); + }); + it('ToUint32 should be applied to the length and any exceptions should be propagated.', () => { + expect(() => { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + length: { + valueOf: null, + toString: function () { + throw test_error; + }, + }, + }; + new Blob(obj); + }).toThrow(test_error); + expect(() => { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + length: { + valueOf: function () { + throw test_error; + }, + }, + }; + new Blob(obj); + }).toThrow(test_error); + }); + it('Getters and value conversions should happen in order until an exception is thrown.', () => { + var received = []; + var obj = { + get [Symbol.iterator]() { + received.push('Symbol.iterator'); + return Array.prototype[Symbol.iterator]; + }, + get length() { + received.push('length getter'); + return { + valueOf: function () { + received.push('length valueOf'); + return 3; + }, + }; + }, + get 0() { + received.push('0 getter'); + return { + toString: function () { + received.push('0 toString'); + return 'a'; + }, + }; + }, + get 1() { + received.push('1 getter'); + throw test_error; + }, + get 2() { + received.push('2 getter'); + expect(true).toBe(false); + return 'unreachable'; + }, + }; + expect(() => new Blob(obj)).toThrow(test_error); + // Somehow we don't call 0 toString but I don't know why not or why would we + expect(received).toEqual([ + 'Symbol.iterator', + 'length getter', + 'length valueOf', + '0 getter', + '0 toString', + 'length getter', + 'length valueOf', + '1 getter', + ]); + }); + it('ToString should be called on elements of the blobParts array and any exceptions should be propagated.', () => { + expect( + () => + new Blob([ + { + toString: function () { + throw test_error; + }, + }, + ]) + ).toThrow(test_error); + expect( + () => + new Blob([ + { + toString: undefined, + valueOf: function () { + throw test_error; + }, + }, + ]) + ).toThrow(test_error); + expect( + () => + new Blob([ + { + toString: function () { + throw test_error; + }, + valueOf: function () { + expect(false).toBe(true); + }, + }, + ]) + ).toThrow(test_error); + // TODO add the proper TypeError type to toThrow + expect(() => new Blob([{ toString: null, valueOf: null }])).toThrow(); + }); + test_blob( + function () { + var arr = [ + { + toString: function () { + arr.pop(); + return 'PASS'; + }, + }, + { toString: function () {} }, + ]; + return new Blob(arr); + }, + { + expected: 'PASS', + type: '', + desc: 'Changes to the blobParts array should be reflected in the returned Blob (pop).', + } + ); + test_blob( + function () { + var arr = [ + { + toString: function () { + if (arr.length === 3) { + return 'A'; } - }); - }) - - // Windows platforms use CRLF as the native line ending. All others use LF. - const crlf = Platform.OS == 'windows'; - const native_ending = crlf ? '\r\n' : '\n'; - describe('constructor-endings', async () => { - it('valid endings value', () => { - const blob0 = new Blob([], {endings: 'native'}); - const blob1 = new Blob([], {endings: 'transparent'}); - expect(blob0).toBeTruthy() - expect(blob1).toBeTruthy() - }) - it('invalud endings value', () => { - [ - null, - '', - 'invalidEnumValue', - 'Transparent', - 'NATIVE', - 0, - {}, - ].forEach((ending) => { - // @ts-expect-error - expect(() => new Blob([], {endings:ending})).toThrow() - }) - }) - it('Exception propagation from options', () => { - const test_error = {name: 'test string'}; - // @ts-expect-error - expect(() => new Blob([], { get endings() { throw test_error; }})).toThrow('test string') - }) - // TODO weird test, as it could maybe be used lazily and not in the constructor - it('The \'endings\' options property is used', () => { - let got = false; - // @ts-expect-error - new Blob([], { get endings() { got = true; } }); - expect(got).toBeTruthy() - }) - const sampleEndings = [ - {name: 'LF', input: '\n', native: native_ending}, - {name: 'CR', input: '\r', native: native_ending}, - - {name: 'CRLF', input: '\r\n', native: native_ending}, - {name: 'CRCR', input: '\r\r', native: native_ending.repeat(2)}, - {name: 'LFCR', input: '\n\r', native: native_ending.repeat(2)}, - {name: 'LFLF', input: '\n\n', native: native_ending.repeat(2)}, - - {name: 'CRCRLF', input: '\r\r\n', native: native_ending.repeat(2)}, - {name: 'CRLFLF', input: '\r\n\n', native: native_ending.repeat(2)}, - {name: 'CRLFCR', input: '\r\n\r\n', native: native_ending.repeat(2)}, - - {name: 'CRLFCRLF', input: '\r\n\r\n', native: native_ending.repeat(2)}, - {name: 'LFCRLFCR', input: '\n\r\n\r', native: native_ending.repeat(3)}, - - ]; - it('Newlines should not change with endings unspecified', () => { - sampleEndings.forEach(testCase => { - const blob = new Blob([testCase.input]); - expect(blob.syncText()).toBe(testCase.input) - }) - }) - it('Newlines should not change with endings "transparent"', () => { - sampleEndings.forEach(testCase => { - const blob = new Blob([testCase.input], {endings: 'transparent'}); - expect(blob.syncText()).toBe(testCase.input) - }); - }) - it('Newlines should match the platform with endings "native"', () => { - sampleEndings.forEach(testCase => { - const blob = new Blob([testCase.input], {endings: 'native'}); - expect(blob.syncText()).toBe(testCase.native) - }); - }) - it('CR/LF in adjacent input strings', () => { - const blob = new Blob(['\r', '\n'], {endings: 'native'}); - const expected = native_ending.repeat(2); - expect(blob.syncText()).toBe(expected) - }) - }) - - describe('constructor', () => { - it("globalThis should have a Blob property.", () => {expect("Blob" in globalThis).toBeTruthy()}) - it("Blob.length should be 0", () => {expect(Blob.length).toBe(0)}) - it("Blob should be a function", () => {expect(Blob instanceof Function).toBeTruthy()}) - it("Blob constructor with no arguments", () => { - var blob = new Blob() - expect(blob instanceof Blob).toBeTruthy() - expect(String(blob)).toBe('[object Blob]') - expect(blob.size).toBe(0) - expect(blob.type).toBe("") - }) - it("Blob constructor with no arguments, without 'new'", () => { - expect(() => {var blob = Blob();}).toThrow() - }) - it("Blob constructor without brackets", () => { - var blob = new Blob; - expect(blob instanceof Blob).toBeTruthy() - expect(blob.size).toBe(0) - expect(blob.type).toBe("") - }) - it("Blob constructor with undefined as first argument", () => { - var blob = new Blob(undefined) - expect(blob instanceof Blob).toBeTruthy() - expect(String(blob)).toBe('[object Blob]') - expect(blob.size).toBe(0) - expect(blob.type).toBe("") - }) - // TODO Something wrong with null ? Why should Blob() work and Blob(null) not ??? - it('Passing non-objects, Dates and RegExps for blobParts should throw a TypeError.', () => { - var args = [ - null, - true, - false, - 0, - 1, - 1.5, - "FAIL", - new Date(), - // @ts-expect-error - new RegExp(), - {}, - { 0: "FAIL", length: 1 }, - ]; - args.forEach((arg) => { - // @ts-expect-error - expect(() => new Blob(arg)).toThrow() - }); - }) - test_blob(function() { - return new Blob({ - [Symbol.iterator]: Array.prototype[Symbol.iterator], + arr.unshift({ + toString: function () { + expect(true).toBe(false); + }, }); - }, { - expected: "", - type: "", - desc: "A plain object with @@iterator should be treated as a sequence for the blobParts argument." - }); - it('A plain object with custom @@iterator should be treated as a sequence for the blobParts argument.', () => { - const blob = new Blob({ - [Symbol.iterator]() { - var i = 0; - return {next: () => [ - {done:false, value:'ab'}, - {done:false, value:'cde'}, - {done:true} - ][i++] - }; - } - }); - expect(blob.size).toBe(5) - }) - it('A plain object with @@iterator and a length property should be treated as a sequence for the blobParts argument.', () => { - let blob = new Blob({ - [Symbol.iterator]: Array.prototype[Symbol.iterator], - 0: "PASS", - length: 1 - }); - expect(blob.syncText()).toEqual("PASS") - expect(blob.type).toBe("") - }) - it('A String object should be treated as a sequence for the blobParts argument.', () => { - let blob = new Blob(new String("xyz")); - expect(blob.syncText()).toEqual("xyz") - expect(blob.type).toBe("") - }) - test_blob(function() { - return new Blob(new String("xyz")); - }, { - expected: "xyz", - type: "", - desc: "A String object should be treated as a sequence for the blobParts argument." - }); - test_blob(function() { - return new Blob(new Uint8Array([1, 2, 3])); - }, { - expected: "123", - type: "", - desc: "A Uint8Array object should be treated as a sequence for the blobParts argument." - }); - - it("The length getter should be invoked and any exceptions should be propagated.", () => { - expect(() => { - var obj = { - [Symbol.iterator]: Array.prototype[Symbol.iterator], - get length() { throw test_error; } - }; - new Blob(obj) - }).toThrow(test_error) - }) - it("ToUint32 should be applied to the length and any exceptions should be propagated.", () => { - expect(() => { - var obj = { - [Symbol.iterator]: Array.prototype[Symbol.iterator], - length: { - valueOf: null, - toString: function() { throw test_error; } - } - }; - new Blob(obj); - }).toThrow(test_error) - expect(() => { - var obj = { - [Symbol.iterator]: Array.prototype[Symbol.iterator], - length: { valueOf: function() { throw test_error; } } - }; - new Blob(obj); - }).toThrow(test_error) - }) - it("Getters and value conversions should happen in order until an exception is thrown.", () => { - var received = []; - var obj = { - get [Symbol.iterator]() { - received.push("Symbol.iterator"); - return Array.prototype[Symbol.iterator]; - }, - get length() { - received.push("length getter"); - return { - valueOf: function() { - received.push("length valueOf"); - return 3; - } - }; - }, - get 0() { - received.push("0 getter"); - return { - toString: function() { - received.push("0 toString"); - return "a"; - } - }; - }, - get 1() { - received.push("1 getter"); - throw test_error; - }, - get 2() { - received.push("2 getter"); - expect(true).toBe(false); - return "unreachable"; - } - }; - expect(() => new Blob(obj)).toThrow(test_error); - // Somehow we don't call 0 toString but I don't know why not or why would we - expect(received).toEqual([ - "Symbol.iterator", - "length getter", - "length valueOf", - "0 getter", - "0 toString", - "length getter", - "length valueOf", - "1 getter", - ]); - }) - it("ToString should be called on elements of the blobParts array and any exceptions should be propagated.", () => { - expect(() => new Blob([{ toString: function() { throw test_error; } }])).toThrow(test_error) - expect(() => new Blob([{ toString: undefined, valueOf: function() { throw test_error; } }])).toThrow(test_error) - expect(() => new Blob([{ - toString: function() { throw test_error; }, - valueOf: function() { expect(false).toBe(true); } - }])).toThrow(test_error) - // TODO add the proper TypeError type to toThrow - expect(() => new Blob([{toString: null, valueOf: null}])).toThrow() - }) - test_blob(function() { - var arr = [ - { toString: function() { arr.pop(); return "PASS"; } }, - { toString: function() { } } - ]; - return new Blob(arr); - }, { - expected: "PASS", - type: "", - desc: "Changes to the blobParts array should be reflected in the returned Blob (pop)." - }); - test_blob(function() { - var arr = [ - { - toString: function() { - if (arr.length === 3) { - return "A"; - } - arr.unshift({ - toString: function() { - expect(true).toBe(false) - } - }); - return "P"; - } - }, - { - toString: function() { - return "SS"; - } - } - ]; - return new Blob(arr); - }, { - expected: "PASS", - type: "", - desc: "Changes to the blobParts array should be reflected in the returned Blob (unshift)." - }); - test_blob(function() { - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652 - return new Blob([ - null, - undefined, - true, - false, - 0, - 1, - new String("stringobject"), - [], - ['x', 'y'], - {}, - { 0: "FAIL", length: 1 }, - { toString: function() { return "stringA"; } }, - { toString: undefined, valueOf: function() { return "stringB"; } }, - { valueOf: function() { expect(false).toBe(true) } } - ]); - }, { - expected: "nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]", - type: "", - desc: "ToString should be called on elements of the blobParts array." - }); - test_blob(function() { - return new Blob([ - new ArrayBuffer(8) - ]); - }, { - expected: "\0\0\0\0\0\0\0\0", - type: "", - desc: "ArrayBuffer elements of the blobParts array should be supported." - }); - - test_blob(function() { - return new Blob([ - new Uint8Array([0x50, 0x41, 0x53, 0x53]), - new Int8Array([0x50, 0x41, 0x53, 0x53]), - new Uint16Array([0x4150, 0x5353]), - new Int16Array([0x4150, 0x5353]), - new Uint32Array([0x53534150]), - new Int32Array([0x53534150]), - new Float32Array([0xD341500000]) - ]); - }, { - expected: "PASSPASSPASSPASSPASSPASSPASS", - type: "", - desc: "Passing typed arrays as elements of the blobParts array should work." - }); - test_blob(function() { - return new Blob([ - new Float16Array([2.65625, 58.59375]) - ]); - }, { - expected: "PASS", - type: "", - desc: "Passing a Float16Array as element of the blobParts array should work." - }); - test_blob(function() { - return new Blob([ - // 0x535 3415053534150 - // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310 - // 0x13415053534150 * 2**(-52) - // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680 - new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680]) - ]); - }, { - expected: "PASSPASS", - type: "", - desc: "Passing a Float64Array as element of the blobParts array should work." - }); - test_blob(function() { - return new Blob([ - new BigInt64Array([BigInt("0x5353415053534150")]), - new BigUint64Array([BigInt("0x5353415053534150")]) - ]); - }, { - expected: "PASSPASSPASSPASS", - type: "", - desc: "Passing BigInt typed arrays as elements of the blobParts array should work." - }); - - // TODO revisit this one as its implementation is weird in the wpt - it("Passing a FrozenArray as the blobParts array should work (FrozenArray).", async () => { - var channel = new MessageChannel(); - channel.port2.onmessage = this.step_func(function(e) { - var b_ports = new Blob(e.ports); - expect(b_ports.size).toEqual("[object MessagePort]".length) - this.done(); - }); - var channel2 = new MessageChannel(); - channel.port1.postMessage('', [channel2.port1]); - }) - - test_blob(function() { - var blob = new Blob(['foo']); - return new Blob([blob, blob]); - }, { - expected: "foofoo", - type: "", - desc: "Array with two blobs" - }); - test_blob_binary(function() { - var view = new Uint8Array([0, 255, 0]); - return new Blob([view.buffer, view.buffer]); - }, { - expected: [0, 255, 0, 0, 255, 0], - type: "", - desc: "Array with two buffers" - }); - - test_blob_binary(function() { - var view = new Uint8Array([0, 255, 0, 4]); - var blob = new Blob([view, view]); - expect(blob.size).toBe(8); - var view1 = new Uint16Array(view.buffer, 2); - return new Blob([view1, view.buffer, view1]); - }, { - expected: [0, 4, 0, 255, 0, 4, 0, 4], - type: "", - desc: "Array with two bufferviews" - }); - - // TODO revisit this, why can we pass view but not the buffer? - test_blob(function() { - var view = new Uint8Array([0]); - var blob = new Blob(["fo"]); - return new Blob([view.buffer, blob, "foo"]); - }, { - expected: "\0fofoo", - type: "", - desc: "Array with mixed types" - }); - - it('options properties should be accessed in lexicographic order.', async () => { - const accessed = []; - const stringified = []; - - new Blob([], { - get type() { accessed.push('type'); }, - get endings() { accessed.push('endings'); } - }); - new Blob([], { - type: { toString: () => { stringified.push('type'); return ''; } }, - endings: { toString: () => { stringified.push('endings'); return 'transparent'; } } - }); - expect(accessed).toEqual(['endings', 'type']); - expect(stringified).toEqual(['endings', 'type']); - }) - - it('Arguments should be evaluated from left to right.', async () => { - expect( - () => new Blob( - [{ toString: function() { throw test_error } }], - { - get type() { assert_unreached("type getter should not be called."); } - }) - ).toThrow(test_error); - }) - - describe('Passing arguments for options', () => { - [ - null, - undefined, - {}, - { unrecognized: true }, - /regex/, - function() {} - ].forEach((arg : any, idx) => { - test_blob(function() { - return new Blob([], arg); - }, { - expected: "", - type: "", - desc: "Passing " + JSON.stringify(arg) + " (index " + idx + ") for options should use the defaults." - }); - test_blob(function() { - return new Blob(["\na\r\nb\n\rc\r"], arg); - }, { - expected: "\na\r\nb\n\rc\r", - type: "", - desc: "Passing " + JSON.stringify(arg) + " (index " + idx + ") for options should use the defaults (with newlines)." - }); - }); - }) - - describe('Blob constructor should throw with invalid property bag', () => { - [ - 123, - 123.4, - true, - 'abc' - ].forEach((arg : any) => { - it('Passing ' + JSON.stringify(arg) + ' for options should throw', () => { - expect(() => { - new Blob([], arg) - }).toThrow() - }) - }); - }) - - describe('Type test', () => { - var type_tests = [ - // blobParts, type, expected type - [[], '', ''], - [[], 'a', 'a'], - [[], 'A', 'a'], - [[], 'text/html', 'text/html'], - [[], 'TEXT/HTML', 'text/html'], - [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'], - [[], '\u00E5', ''], - [[], '\uD801\uDC7E', ''], // U+1047E - [[], ' image/gif ', ' image/gif '], - [[], '\timage/gif\t', ''], - [[], 'image/gif;\u007f', ''], - [[], '\u0130mage/gif', ''], // uppercase i with dot - [[], '\u0131mage/gif', ''], // lowercase dotless i - [[], 'image/gif\u0000', ''], - // check that type isn't changed based on sniffing - [[0x3C, 0x48, 0x54, 0x4D, 0x4C, 0x3E], 'unknown/unknown', 'unknown/unknown'], // "" - [[0x00, 0xFF], 'text/plain', 'text/plain'], - [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a" - ]; - - type_tests.forEach(function(t: Array) { - it("Blob with type " + JSON.stringify(t[1]), () => { - // TODO Why this construction? It does'nt work yet, but it's not what we test for... - // var arr = new Uint8Array([t[0]]).buffer; - // var b = new Blob([arr], {type:t[1]}); - - var b = new Blob(t[0], {type:t[1]}); - expect(b.type).toEqual(t[2]); - }); - }); - }) - }) - - describe('constructor dom windows', async () => { - it("Passing platform objects for blobParts should throw a TypeError.", () => { - const document = new Document() - var args = [ - document.createElement("div"), - window, - ]; - args.forEach((arg) => { - expect(() => new Blob(arg)).toThrow(); - }); - }); - - it("A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)", () => { - var element = document.createElement("div"); - element.appendChild(document.createElement("div")); - element.appendChild(document.createElement("p")); - var list = element.children; - Object.defineProperty(list, "length", { - get: function() { throw test_error; } - }); - expect(() => {new Blob(list);}).toThrow(test_error); - }); - - test_blob(function() { - const document = new Document() - var select = document.createElement("select"); - select.appendChild(document.createElement("option")); - return new Blob(select); - }, { - expected: "[object HTMLOptionElement]", - type: "", - desc: "Passing a platform object that supports indexed properties as the blobParts array should work (select)." - }); - - test_blob(function() { - const document = new Document() - var elm = document.createElement("div"); - elm.setAttribute("foo", "bar"); - return new Blob(elm.attributes); - }, { - expected: "[object Attr]", - type: "", - desc: "Passing an platform object that supports indexed properties as the blobParts array should work (attributes)." - }); - }) - - describe('Text', async () => { - it('simple', async () => { - const blob = new Blob(["PASS"]); - const text = await blob.text(); - expect(text).toBe("PASS"); - }); - it('empty blob data', async () => { - const blob = new Blob(); - const text = await blob.text(); - expect(text).toBe(""); - }); - it('multi-element array in constructor', async () => { - const blob = new Blob(["P", "A", "SS"]); - const text = await blob.text(); - expect(text).toBe("PASS"); - }); - it('non-unicode', async () => { - const non_unicode = "\u0061\u030A"; - const input_arr = new TextEncoder().encode(non_unicode); - const blob = new Blob([input_arr]); - const text = await blob.text(); - expect(text).toBe(non_unicode); - }); - it('different charset param in type option', async () => { - const blob = new Blob(["PASS"], { type: "text/plain;charset=utf-16le" }); - const text = await blob.text(); - expect(text).toBe("PASS"); - }) - it('Sync Text', () => { - const blob = new Blob(["PA", "SS"]); - const text = blob.syncText(); - expect(text).toBe("PASS"); - }) - it('different charset param with non-ascii input', async () => { - const non_unicode = "\u0061\u030A"; - const input_arr = new TextEncoder().encode(non_unicode); - const blob = new Blob([input_arr], { type: "text/plain;charset=utf-16le" }); - const text = await blob.text(); - expect(text).toBe(non_unicode); - }) - it('invalid utf-8 input', async () => { - const input_arr = new Uint8Array([192, 193, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]); - const blob = new Blob([input_arr]); - const text = await blob.text(); - expect(text).toBe("\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd"); - }); - it('Promise.all multiple reads', async () => { - const input_arr = new Uint8Array([192, 193, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]); - const blob = new Blob([input_arr]); - const text_results = await Promise.all([blob.text(), blob.text(), blob.text()]); - text_results.forEach(text => { - expect(text).toBe("\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd"); - }); - }); - }) + return 'P'; + }, + }, + { + toString: function () { + return 'SS'; + }, + }, + ]; + return new Blob(arr); + }, + { + expected: 'PASS', + type: '', + desc: 'Changes to the blobParts array should be reflected in the returned Blob (unshift).', + } + ); + test_blob( + function () { + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652 + return new Blob([ + null, + undefined, + true, + false, + 0, + 1, + new String('stringobject'), + [], + ['x', 'y'], + {}, + { 0: 'FAIL', length: 1 }, + { + toString: function () { + return 'stringA'; + }, + }, + { + toString: undefined, + valueOf: function () { + return 'stringB'; + }, + }, + { + valueOf: function () { + expect(false).toBe(true); + }, + }, + ]); + }, + { + expected: + 'nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]', + type: '', + desc: 'ToString should be called on elements of the blobParts array.', + } + ); + test_blob( + function () { + return new Blob([new ArrayBuffer(8)]); + }, + { + expected: '\0\0\0\0\0\0\0\0', + type: '', + desc: 'ArrayBuffer elements of the blobParts array should be supported.', + } + ); + + test_blob( + function () { + return new Blob([ + new Uint8Array([0x50, 0x41, 0x53, 0x53]), + new Int8Array([0x50, 0x41, 0x53, 0x53]), + new Uint16Array([0x4150, 0x5353]), + new Int16Array([0x4150, 0x5353]), + new Uint32Array([0x53534150]), + new Int32Array([0x53534150]), + new Float32Array([0xd341500000]), + ]); + }, + { + expected: 'PASSPASSPASSPASSPASSPASSPASS', + type: '', + desc: 'Passing typed arrays as elements of the blobParts array should work.', + } + ); + test_blob( + function () { + return new Blob([new Float16Array([2.65625, 58.59375])]); + }, + { + expected: 'PASS', + type: '', + desc: 'Passing a Float16Array as element of the blobParts array should work.', + } + ); + test_blob( + function () { + return new Blob([ + // 0x535 3415053534150 + // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310 + // 0x13415053534150 * 2**(-52) + // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680 + new Float64Array([ + 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680, + ]), + ]); + }, + { + expected: 'PASSPASS', + type: '', + desc: 'Passing a Float64Array as element of the blobParts array should work.', + } + ); + test_blob( + function () { + return new Blob([ + new BigInt64Array([BigInt('0x5353415053534150')]), + new BigUint64Array([BigInt('0x5353415053534150')]), + ]); + }, + { + expected: 'PASSPASSPASSPASS', + type: '', + desc: 'Passing BigInt typed arrays as elements of the blobParts array should work.', + } + ); + + // TODO revisit this one as its implementation is weird in the wpt + it('Passing a FrozenArray as the blobParts array should work (FrozenArray).', async () => { + var channel = new MessageChannel(); + channel.port2.onmessage = this.step_func(function (e) { + var b_ports = new Blob(e.ports); + expect(b_ports.size).toEqual('[object MessagePort]'.length); + this.done(); + }); + var channel2 = new MessageChannel(); + channel.port1.postMessage('', [channel2.port1]); + }); + + test_blob( + function () { + var blob = new Blob(['foo']); + return new Blob([blob, blob]); + }, + { + expected: 'foofoo', + type: '', + desc: 'Array with two blobs', + } + ); + test_blob_binary( + function () { + var view = new Uint8Array([0, 255, 0]); + return new Blob([view.buffer, view.buffer]); + }, + { + expected: [0, 255, 0, 0, 255, 0], + type: '', + desc: 'Array with two buffers', + } + ); + + test_blob_binary( + function () { + var view = new Uint8Array([0, 255, 0, 4]); + var blob = new Blob([view, view]); + expect(blob.size).toBe(8); + var view1 = new Uint16Array(view.buffer, 2); + return new Blob([view1, view.buffer, view1]); + }, + { + expected: [0, 4, 0, 255, 0, 4, 0, 4], + type: '', + desc: 'Array with two bufferviews', + } + ); + + // TODO revisit this, why can we pass view but not the buffer? + test_blob( + function () { + var view = new Uint8Array([0]); + var blob = new Blob(['fo']); + return new Blob([view.buffer, blob, 'foo']); + }, + { + expected: '\0fofoo', + type: '', + desc: 'Array with mixed types', + } + ); + + it('options properties should be accessed in lexicographic order.', async () => { + const accessed = []; + const stringified = []; + + new Blob([], { + get type() { + accessed.push('type'); + }, + get endings() { + accessed.push('endings'); + }, + }); + new Blob([], { + type: { + toString: () => { + stringified.push('type'); + return ''; + }, + }, + endings: { + toString: () => { + stringified.push('endings'); + return 'transparent'; + }, + }, + }); + expect(accessed).toEqual(['endings', 'type']); + expect(stringified).toEqual(['endings', 'type']); + }); + + it('Arguments should be evaluated from left to right.', async () => { + expect( + () => + new Blob( + [ + { + toString: function () { + throw test_error; + }, + }, + ], + { + get type() { + assert_unreached('type getter should not be called.'); + }, + } + ) + ).toThrow(test_error); + }); + + describe('Passing arguments for options', () => { + [null, undefined, {}, { unrecognized: true }, /regex/, function () {}].forEach( + (arg: any, idx) => { + test_blob( + function () { + return new Blob([], arg); + }, + { + expected: '', + type: '', + desc: + 'Passing ' + + JSON.stringify(arg) + + ' (index ' + + idx + + ') for options should use the defaults.', + } + ); + test_blob( + function () { + return new Blob(['\na\r\nb\n\rc\r'], arg); + }, + { + expected: '\na\r\nb\n\rc\r', + type: '', + desc: + 'Passing ' + + JSON.stringify(arg) + + ' (index ' + + idx + + ') for options should use the defaults (with newlines).', + } + ); + } + ); + }); + + describe('Blob constructor should throw with invalid property bag', () => { + [123, 123.4, true, 'abc'].forEach((arg: any) => { + it('Passing ' + JSON.stringify(arg) + ' for options should throw', () => { + expect(() => { + new Blob([], arg); + }).toThrow(); + }); + }); + }); + + describe('Type test', () => { + var type_tests = [ + // blobParts, type, expected type + [[], '', ''], + [[], 'a', 'a'], + [[], 'A', 'a'], + [[], 'text/html', 'text/html'], + [[], 'TEXT/HTML', 'text/html'], + [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'], + [[], '\u00E5', ''], + [[], '\uD801\uDC7E', ''], // U+1047E + [[], ' image/gif ', ' image/gif '], + [[], '\timage/gif\t', ''], + [[], 'image/gif;\u007f', ''], + [[], '\u0130mage/gif', ''], // uppercase i with dot + [[], '\u0131mage/gif', ''], // lowercase dotless i + [[], 'image/gif\u0000', ''], + // check that type isn't changed based on sniffing + [[0x3c, 0x48, 0x54, 0x4d, 0x4c, 0x3e], 'unknown/unknown', 'unknown/unknown'], // "" + [[0x00, 0xff], 'text/plain', 'text/plain'], + [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a" + ]; + + type_tests.forEach(function (t: Array) { + it('Blob with type ' + JSON.stringify(t[1]), () => { + // TODO Why this construction? It does'nt work yet, but it's not what we test for... + // var arr = new Uint8Array([t[0]]).buffer; + // var b = new Blob([arr], {type:t[1]}); + + var b = new Blob(t[0], { type: t[1] }); + expect(b.type).toEqual(t[2]); + }); + }); + }); + }); - describe('Worker', async () => { - it('Create Blob in Worker', async () => { - const data = "TEST"; - const blob = new Blob([data], {type: "text/plain"}); - expect(await blob.text()).toEqual(data); - }); - }) + describe('constructor dom windows', async () => { + it('Passing platform objects for blobParts should throw a TypeError.', () => { + const document = new Document(); + var args = [document.createElement('div'), window]; + args.forEach((arg) => { + expect(() => new Blob(arg)).toThrow(); + }); + }); + + it("A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)", () => { + var element = document.createElement('div'); + element.appendChild(document.createElement('div')); + element.appendChild(document.createElement('p')); + var list = element.children; + Object.defineProperty(list, 'length', { + get: function () { + throw test_error; + }, + }); + expect(() => { + new Blob(list); + }).toThrow(test_error); + }); + + test_blob( + function () { + const document = new Document(); + var select = document.createElement('select'); + select.appendChild(document.createElement('option')); + return new Blob(select); + }, + { + expected: '[object HTMLOptionElement]', + type: '', + desc: 'Passing a platform object that supports indexed properties as the blobParts array should work (select).', + } + ); + + test_blob( + function () { + const document = new Document(); + var elm = document.createElement('div'); + elm.setAttribute('foo', 'bar'); + return new Blob(elm.attributes); + }, + { + expected: '[object Attr]', + type: '', + desc: 'Passing an platform object that supports indexed properties as the blobParts array should work (attributes).', + } + ); + }); - describe('Blob slice overflow', async () => { - var text = ''; + describe('Text', async () => { + it('simple', async () => { + const blob = new Blob(['PASS']); + const text = await blob.text(); + expect(text).toBe('PASS'); + }); + it('empty blob data', async () => { + const blob = new Blob(); + const text = await blob.text(); + expect(text).toBe(''); + }); + it('multi-element array in constructor', async () => { + const blob = new Blob(['P', 'A', 'SS']); + const text = await blob.text(); + expect(text).toBe('PASS'); + }); + it('non-unicode', async () => { + const non_unicode = '\u0061\u030A'; + const input_arr = new TextEncoder().encode(non_unicode); + const blob = new Blob([input_arr]); + const text = await blob.text(); + expect(text).toBe(non_unicode); + }); + it('different charset param in type option', async () => { + const blob = new Blob(['PASS'], { type: 'text/plain;charset=utf-16le' }); + const text = await blob.text(); + expect(text).toBe('PASS'); + }); + it('Sync Text', () => { + const blob = new Blob(['PA', 'SS']); + const text = blob.syncText(); + expect(text).toBe('PASS'); + }); + it('different charset param with non-ascii input', async () => { + const non_unicode = '\u0061\u030A'; + const input_arr = new TextEncoder().encode(non_unicode); + const blob = new Blob([input_arr], { type: 'text/plain;charset=utf-16le' }); + const text = await blob.text(); + expect(text).toBe(non_unicode); + }); + it('invalid utf-8 input', async () => { + const input_arr = new Uint8Array([ + 192, 193, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + ]); + const blob = new Blob([input_arr]); + const text = await blob.text(); + expect(text).toBe( + '\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd' + ); + }); + it('Promise.all multiple reads', async () => { + const input_arr = new Uint8Array([ + 192, 193, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + ]); + const blob = new Blob([input_arr]); + const text_results = await Promise.all([blob.text(), blob.text(), blob.text()]); + text_results.forEach((text) => { + expect(text).toBe( + '\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd' + ); + }); + }); + }); - for (var i = 0; i < 2000; ++i) { - text += 'A'; - } + describe('Worker', async () => { + it('Create Blob in Worker', async () => { + const data = 'TEST'; + const blob = new Blob([data], { type: 'text/plain' }); + expect(await blob.text()).toEqual(data); + }); + }); - it("slice start is negative, relativeStart will be max((size + start), 0)", () => { - var blob = new Blob([text]); - var sliceBlob = blob.slice(-1, blob.size); - expect(sliceBlob.size).toBe(1); - }); + describe('Blob slice overflow', async () => { + var text = ''; + + for (var i = 0; i < 2000; ++i) { + text += 'A'; + } + + it('slice start is negative, relativeStart will be max((size + start), 0)', () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(-1, blob.size); + expect(sliceBlob.size).toBe(1); + }); + + it('slice start is greater than blob size, relativeStart will be min(start, size)', () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size + 1, blob.size); + expect(sliceBlob.size).toBe(0); + }); + + it('slice end is negative, relativeEnd will be max((size + end), 0)', () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size - 2, -1); + expect(sliceBlob.size).toBe(1); + }); + + it('slice end is greater than blob size, relativeEnd will be min(end, size)', () => { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size - 2, blob.size + 999); + expect(sliceBlob.size).toBe(2); + }); + }); - it("slice start is greater than blob size, relativeStart will be min(start, size)", () => { - var blob = new Blob([text]); - var sliceBlob = blob.slice(blob.size + 1, blob.size); - expect(sliceBlob.size).toBe(0); - }); + describe('Blob Slice', async () => { + test_blob( + () => { + var blobTemp = new Blob(['PASS']); + return blobTemp.slice(); + }, + { + expected: 'PASS', + type: '', + desc: 'no-argument Blob slice', + } + ); - it("slice end is negative, relativeEnd will be max((size + end), 0)", () => { - var blob = new Blob([text]); - var sliceBlob = blob.slice(blob.size - 2, -1); - expect(sliceBlob.size).toBe(1); - }); + describe('Slices', async () => { + var blob1 = new Blob(['squiggle']); + var blob2 = new Blob(['steak'], { type: 'content/type' }); - it("slice end is greater than blob size, relativeEnd will be min(end, size)", () => { - var blob = new Blob([text]); - var sliceBlob = blob.slice(blob.size - 2, blob.size + 999); - expect(sliceBlob.size).toBe(2) - }); - }) - - describe('Blob Slice', async () => { - test_blob(() => { - var blobTemp = new Blob(["PASS"]); - return blobTemp.slice(); - }, { - expected: "PASS", - type: "", - desc: "no-argument Blob slice" - } - ); + test_blob(() => blob1, { + expected: 'squiggle', + type: '', + desc: 'blob1.', + }); - describe("Slices", async () => { - var blob1 = new Blob(["squiggle"]); - var blob2 = new Blob(["steak"], {type: "content/type"}); + test_blob(() => blob2, { + expected: 'steak', + type: 'content/type', + desc: 'blob2.', + }); - test_blob(() => blob1, - { - expected: "squiggle", - type: "", - desc: "blob1." - }); + test_blob( + () => { + return new Blob().slice(0, 0, null); + }, + { + expected: '', + type: 'null', + desc: 'null type Blob slice', + } + ); + + test_blob( + () => { + return new Blob().slice(0, 0, undefined); + }, + { + expected: '', + type: '', + desc: 'undefined type Blob slice', + } + ); + + test_blob( + () => { + return new Blob().slice(0, 0); + }, + { + expected: '', + type: '', + desc: 'no type Blob slice', + } + ); + + var arrayBuffer = new ArrayBuffer(16); + var int8View = new Int8Array(arrayBuffer); + for (var i = 0; i < 16; i++) { + int8View[i] = i + 65; + } - test_blob(() => blob2, + var testData = [ + [ + ['PASSSTRING'], + [ + { start: -6, contents: 'STRING' }, + { start: -12, contents: 'PASSSTRING' }, + { start: 4, contents: 'STRING' }, + { start: 12, contents: '' }, + { start: 0, end: -6, contents: 'PASS' }, + { start: 0, end: -12, contents: '' }, + { start: 0, end: 4, contents: 'PASS' }, + { start: 0, end: 12, contents: 'PASSSTRING' }, + { start: 7, end: 4, contents: '' }, + ], + ], + + // Test 3 strings + [ + ['foo', 'bar', 'baz'], + [ + { start: 0, end: 9, contents: 'foobarbaz' }, + { start: 0, end: 3, contents: 'foo' }, + { start: 3, end: 9, contents: 'barbaz' }, + { start: 6, end: 9, contents: 'baz' }, + { start: 6, end: 12, contents: 'baz' }, + { start: 0, end: 9, contents: 'foobarbaz' }, + { start: 0, end: 11, contents: 'foobarbaz' }, + { start: 10, end: 15, contents: '' }, + ], + ], + + // Test string, Blob, string + [ + ['foo', blob1, 'baz'], + [ + { start: 0, end: 3, contents: 'foo' }, + { start: 3, end: 11, contents: 'squiggle' }, + { start: 2, end: 4, contents: 'os' }, + { start: 10, end: 12, contents: 'eb' }, + ], + ], + + // Test blob, string, blob + [ + [blob1, 'foo', blob1], + [ + { start: 0, end: 8, contents: 'squiggle' }, + { start: 7, end: 9, contents: 'ef' }, + { start: 10, end: 12, contents: 'os' }, + { start: 1, end: 4, contents: 'qui' }, + { start: 12, end: 15, contents: 'qui' }, + { start: 40, end: 60, contents: '' }, + ], + ], + + // Test blobs all the way down + [ + [blob2, blob1, blob2], + [ + { start: 0, end: 5, contents: 'steak' }, + { start: 5, end: 13, contents: 'squiggle' }, + { start: 13, end: 18, contents: 'steak' }, + { start: 1, end: 3, contents: 'te' }, + { start: 6, end: 10, contents: 'quig' }, + ], + ], + + // Test an ArrayBufferView + [ + [int8View, blob1, 'foo'], + [ + { start: 0, end: 8, contents: 'ABCDEFGH' }, + { start: 8, end: 18, contents: 'IJKLMNOPsq' }, + { start: 17, end: 20, contents: 'qui' }, + { start: 4, end: 12, contents: 'EFGHIJKL' }, + ], + ], + + // Test a partial ArrayBufferView + [ + [new Uint8Array(arrayBuffer, 3, 5), blob1, 'foo'], + [ + { start: 0, end: 8, contents: 'DEFGHsqu' }, + { start: 8, end: 18, contents: 'igglefoo' }, + { start: 4, end: 12, contents: 'Hsquiggl' }, + ], + ], + + // Test type coercion of a number + [ + [3, int8View, 'foo'], + [ + { start: 0, end: 8, contents: '3ABCDEFG' }, + { start: 8, end: 18, contents: 'HIJKLMNOPf' }, + { start: 17, end: 21, contents: 'foo' }, + { start: 4, end: 12, contents: 'DEFGHIJK' }, + ], + ], + + [ + [new Uint8Array([0, 255, 0]).buffer, new Blob(['abcd']), 'efgh', 'ijklmnopqrstuvwxyz'], + [ + { start: 1, end: 4, contents: '\uFFFD\u0000a' }, + { start: 4, end: 8, contents: 'bcde' }, + { start: 8, end: 12, contents: 'fghi' }, + { start: 1, end: 12, contents: '\uFFFD\u0000abcdefghi' }, + ], + ], + ]; + + testData.forEach(function (data, i) { + var blobs = data[0]; + var tests = data[1]; + tests.forEach(function (expectations, j) { + describe('Slicing test (' + i + ',' + j + ').', () => { + var blob = new Blob(blobs); + + it('blob is an instance of Blob', () => { + expect(blob instanceof Blob).toBeTruthy(); + expect(blob instanceof File).toBeFalsy(); + }); + + test_blob( + () => { + return expectations.end === undefined + ? blob.slice(expectations.start) + : blob.slice(expectations.start, expectations.end); + }, { - expected: "steak", - type: "content/type", - desc: "blob2." - }); - - test_blob(() => { - return new Blob().slice(0,0,null); - }, { - expected: "", - type: "null", - desc: "null type Blob slice" - }); - - test_blob(() => { - return new Blob().slice(0,0,undefined); - }, { - expected: "", - type: "", - desc: "undefined type Blob slice" - }); - - test_blob(() => { - return new Blob().slice(0,0); - }, { - expected: "", - type: "", - desc: "no type Blob slice" - }); - - var arrayBuffer = new ArrayBuffer(16); - var int8View = new Int8Array(arrayBuffer); - for (var i = 0; i < 16; i++) { - int8View[i] = i + 65; + expected: expectations.contents, + type: '', + desc: 'Slicing test: slice (' + i + ',' + j + ').', } - - var testData = [ - [ - ["PASSSTRING"], - [{start: -6, contents: "STRING"}, - {start: -12, contents: "PASSSTRING"}, - {start: 4, contents: "STRING"}, - {start: 12, contents: ""}, - {start: 0, end: -6, contents: "PASS"}, - {start: 0, end: -12, contents: ""}, - {start: 0, end: 4, contents: "PASS"}, - {start: 0, end: 12, contents: "PASSSTRING"}, - {start: 7, end: 4, contents: ""}] - ], - - // Test 3 strings - [ - ["foo", "bar", "baz"], - [{start: 0, end: 9, contents: "foobarbaz"}, - {start: 0, end: 3, contents: "foo"}, - {start: 3, end: 9, contents: "barbaz"}, - {start: 6, end: 9, contents: "baz"}, - {start: 6, end: 12, contents: "baz"}, - {start: 0, end: 9, contents: "foobarbaz"}, - {start: 0, end: 11, contents: "foobarbaz"}, - {start: 10, end: 15, contents: ""}] - ], - - // Test string, Blob, string - [ - ["foo", blob1, "baz"], - [{start: 0, end: 3, contents: "foo"}, - {start: 3, end: 11, contents: "squiggle"}, - {start: 2, end: 4, contents: "os"}, - {start: 10, end: 12, contents: "eb"}] - ], - - // Test blob, string, blob - [ - [blob1, "foo", blob1], - [{start: 0, end: 8, contents: "squiggle"}, - {start: 7, end: 9, contents: "ef"}, - {start: 10, end: 12, contents: "os"}, - {start: 1, end: 4, contents: "qui"}, - {start: 12, end: 15, contents: "qui"}, - {start: 40, end: 60, contents: ""}] - ], - - // Test blobs all the way down - [ - [blob2, blob1, blob2], - [{start: 0, end: 5, contents: "steak"}, - {start: 5, end: 13, contents: "squiggle"}, - {start: 13, end: 18, contents: "steak"}, - {start: 1, end: 3, contents: "te"}, - {start: 6, end: 10, contents: "quig"}] - ], - - // Test an ArrayBufferView - [ - [int8View, blob1, "foo"], - [{start: 0, end: 8, contents: "ABCDEFGH"}, - {start: 8, end: 18, contents: "IJKLMNOPsq"}, - {start: 17, end: 20, contents: "qui"}, - {start: 4, end: 12, contents: "EFGHIJKL"}] - ], - - // Test a partial ArrayBufferView - [ - [new Uint8Array(arrayBuffer, 3, 5), blob1, "foo"], - [{start: 0, end: 8, contents: "DEFGHsqu"}, - {start: 8, end: 18, contents: "igglefoo"}, - {start: 4, end: 12, contents: "Hsquiggl"}] - ], - - // Test type coercion of a number - [ - [3, int8View, "foo"], - [{start: 0, end: 8, contents: "3ABCDEFG"}, - {start: 8, end: 18, contents: "HIJKLMNOPf"}, - {start: 17, end: 21, contents: "foo"}, - {start: 4, end: 12, contents: "DEFGHIJK"}] - ], - - [ - [(new Uint8Array([0, 255, 0])).buffer, - new Blob(['abcd']), - 'efgh', - 'ijklmnopqrstuvwxyz'], - [{start: 1, end: 4, contents: "\uFFFD\u0000a"}, - {start: 4, end: 8, contents: "bcde"}, - {start: 8, end: 12, contents: "fghi"}, - {start: 1, end: 12, contents: "\uFFFD\u0000abcdefghi"}] - ] - ]; - - testData.forEach(function(data, i) { - var blobs = data[0]; - var tests = data[1]; - tests.forEach(function(expectations, j) { - describe("Slicing test (" + i + "," + j + ").", () => { - var blob = new Blob(blobs); - - it('blob is an instance of Blob', () => { - expect(blob instanceof Blob).toBeTruthy(); - expect(blob instanceof File).toBeFalsy(); - }) - - test_blob(() => { - return expectations.end === undefined - ? blob.slice(expectations.start) - : blob.slice(expectations.start, expectations.end); - }, { - expected: expectations.contents, - type: "", - desc: "Slicing test: slice (" + i + "," + j + ")." - }); - },); - }); - }); + ); }); - - describe('Invalid content types', () => { - var invalidTypes = [ - "\xFF", - "te\x09xt/plain", - "te\x00xt/plain", - "te\x1Fxt/plain", - "te\x7Fxt/plain" - ]; - invalidTypes.forEach(function(type) { - test_blob(() => { - var blob = new Blob(["PASS"]); - return blob.slice(0, 4, type); - }, { - expected: "PASS", - type: "", - desc: "Invalid contentType (" + JSON.stringify(type) + ")" - }); - }); - }) - - var validTypes = [ - "te(xt/plain", - "te)xt/plain", - "text/plain", - "te@xt/plain", - "te,xt/plain", - "te;xt/plain", - "te:xt/plain", - "te\\xt/plain", - "te\"xt/plain", - "te/xt/plain", - "te[xt/plain", - "te]xt/plain", - "te?xt/plain", - "te=xt/plain", - "te{xt/plain", - "te}xt/plain", - "te\x20xt/plain", - "TEXT/PLAIN", - "text/plain;charset = UTF-8", - "text/plain;charset=UTF-8" - ]; - describe('valid content types', () => { - validTypes.forEach((type) => { - test_blob(() => { - var blob = new Blob(["PASS"]); - return blob.slice(0, 4, type); - }, { - expected: "PASS", - type: type.toLowerCase(), - desc: "Valid contentType (" + JSON.stringify(type) + ")" - }); - }); - }) - }) - - describe('stream', async () => { - it('stream byob crash', async () => { - let a = new Blob(['', '', undefined], { }) - let b = a.stream() - let c = new ReadableStreamBYOBReader(b) - let d = new Int16Array(8) - await c.read(d) - c.releaseLock() - await a.text() - await b.cancel() - }) - - it('stream xhr crash', async () => { - // TODO this constructor doesn't work need to fix it - // const blob = new Blob([1, 2]); - const blob = new Blob([Int32Array.from([1, 2])]); - const readable = blob.stream() - const writable = new WritableStream({}, { - size() { - let xhr = new XMLHttpRequest() - xhr.open("POST", "1", false) - xhr.send() - } - }) - readable.pipeThrough({ readable, writable }) - }) - - it("Blob.stream()", async () => { - const blob = new Blob(["PASS"]); - const stream = blob.stream(); - const chunks = await read_all_chunks(stream); - for (let [index, value] of chunks.entries()) { - expect(value).toEqual("PASS".charCodeAt(index)); - } - }) - - it("Blob.stream() empty Blob", async () => { - const blob = new Blob(); - const stream = blob.stream(); - const chunks = await read_all_chunks(stream); - expect(chunks).toEqual([]); - }) - - it("Blob.stream() non-unicode input", async () => { - const input_arr = [8, 241, 48, 123, 151]; - const typed_arr = new Uint8Array(input_arr); - const blob = new Blob([typed_arr]); - const stream = blob.stream(); - const chunks = await read_all_chunks(stream); - expect(chunks).toEqual(input_arr); - }) - - it("Blob.stream() garbage collection of blob shouldn't break stream " + - "consumption", async() => { - const input_arr = [8, 241, 48, 123, 151]; - const typed_arr = new Uint8Array(input_arr); - let blob = new Blob([typed_arr]); - const stream = blob.stream(); - blob = null; - // TODO Actually call garbageCollect() - // await garbageCollect(); - const chunks = await read_all_chunks(stream, { perform_gc: true }); - expect(chunks).toEqual(input_arr); - }) - - it("Blob.stream() garbage collection of stream shouldn't break stream " + - "consumption", async() => { - const input_arr = [8, 241, 48, 123, 151]; - const typed_arr = new Uint8Array(input_arr); - let blob = new Blob([typed_arr]); - const chunksPromise = read_all_chunks(blob.stream()); - // It somehow matters to do GC here instead of doing `perform_gc: true` - // TODO Actually call garbageCollect() - // await garbageCollect(); - expect(await chunksPromise).toEqual(input_arr); - }) + }); + }); + }); + + describe('Invalid content types', () => { + var invalidTypes = [ + '\xFF', + 'te\x09xt/plain', + 'te\x00xt/plain', + 'te\x1Fxt/plain', + 'te\x7Fxt/plain', + ]; + invalidTypes.forEach(function (type) { + test_blob( + () => { + var blob = new Blob(['PASS']); + return blob.slice(0, 4, type); + }, + { + expected: 'PASS', + type: '', + desc: 'Invalid contentType (' + JSON.stringify(type) + ')', + } + ); + }); + }); + + var validTypes = [ + 'te(xt/plain', + 'te)xt/plain', + 'text/plain', + 'te@xt/plain', + 'te,xt/plain', + 'te;xt/plain', + 'te:xt/plain', + 'te\\xt/plain', + 'te"xt/plain', + 'te/xt/plain', + 'te[xt/plain', + 'te]xt/plain', + 'te?xt/plain', + 'te=xt/plain', + 'te{xt/plain', + 'te}xt/plain', + 'te\x20xt/plain', + 'TEXT/PLAIN', + 'text/plain;charset = UTF-8', + 'text/plain;charset=UTF-8', + ]; + describe('valid content types', () => { + validTypes.forEach((type) => { + test_blob( + () => { + var blob = new Blob(['PASS']); + return blob.slice(0, 4, type); + }, + { + expected: 'PASS', + type: type.toLowerCase(), + desc: 'Valid contentType (' + JSON.stringify(type) + ')', + } + ); + }); + }); + }); - it("Reading Blob.stream() with BYOB reader", async () => { - const input_arr = [8, 241, 48, 123, 151]; - const typed_arr = new Uint8Array(input_arr); - let blob = new Blob([typed_arr]); - const stream = blob.stream(); - const chunks = await read_all_chunks(stream, { mode: "byob" }); - expect(chunks).toEqual(input_arr); - }) - }) + describe('stream', async () => { + it('stream byob crash', async () => { + let a = new Blob(['', '', undefined], {}); + let b = a.stream(); + let c = new ReadableStreamBYOBReader(b); + let d = new Int16Array(8); + await c.read(d); + c.releaseLock(); + await a.text(); + await b.cancel(); + }); + + it('stream xhr crash', async () => { + // TODO this constructor doesn't work need to fix it + // const blob = new Blob([1, 2]); + const blob = new Blob([Int32Array.from([1, 2])]); + const readable = blob.stream(); + const writable = new WritableStream( + {}, + { + size() { + let xhr = new XMLHttpRequest(); + xhr.open('POST', '1', false); + xhr.send(); + }, + } + ); + readable.pipeThrough({ readable, writable }); + }); + + it('Blob.stream()', async () => { + const blob = new Blob(['PASS']); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream); + for (let [index, value] of chunks.entries()) { + expect(value).toEqual('PASS'.charCodeAt(index)); + } + }); + + it('Blob.stream() empty Blob', async () => { + const blob = new Blob(); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream); + expect(chunks).toEqual([]); + }); + + it('Blob.stream() non-unicode input', async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + const blob = new Blob([typed_arr]); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream); + expect(chunks).toEqual(input_arr); + }); + + it( + "Blob.stream() garbage collection of blob shouldn't break stream " + 'consumption', + async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + let blob = new Blob([typed_arr]); + const stream = blob.stream(); + blob = null; + // TODO Actually call garbageCollect() + // await garbageCollect(); + const chunks = await read_all_chunks(stream, { perform_gc: true }); + expect(chunks).toEqual(input_arr); + } + ); + + it( + "Blob.stream() garbage collection of stream shouldn't break stream " + 'consumption', + async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + let blob = new Blob([typed_arr]); + const chunksPromise = read_all_chunks(blob.stream()); + // It somehow matters to do GC here instead of doing `perform_gc: true` + // TODO Actually call garbageCollect() + // await garbageCollect(); + expect(await chunksPromise).toEqual(input_arr); + } + ); + + it('Reading Blob.stream() with BYOB reader', async () => { + const input_arr = [8, 241, 48, 123, 151]; + const typed_arr = new Uint8Array(input_arr); + let blob = new Blob([typed_arr]); + const stream = blob.stream(); + const chunks = await read_all_chunks(stream, { mode: 'byob' }); + expect(chunks).toEqual(input_arr); + }); }); + }); } diff --git a/packages/expo-blob/build/BlobModule.d.ts.map b/packages/expo-blob/build/BlobModule.d.ts.map index 375511d23fc819..d7aff6fd96a7be 100644 --- a/packages/expo-blob/build/BlobModule.d.ts.map +++ b/packages/expo-blob/build/BlobModule.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,YAAY,EAAE,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,OAAO,UAAW,SAAQ,YAAY;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAC7D,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IACnE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC5B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,QAAQ,IAAI,MAAM;CACnB;AAED,OAAO,OAAO,cAAe,SAAQ,YAAY;IAC/C,IAAI,EAAE,OAAO,UAAU,CAAC;CACzB;AAED,QAAA,MAAM,gBAAgB,gBAAkD,CAAC;AAEzE,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACrD,SAAS,CAAC,EAAE,GAAG,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAIxD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IAOnE,MAAM,IAAI,cAAc;IAiBnB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAG7C"} \ No newline at end of file +{"version":3,"file":"BlobModule.d.ts","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,YAAY,EAAE,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,OAAO,UAAW,SAAQ,YAAY;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAC7D,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IACnE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC5B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IACvB,QAAQ,IAAI,MAAM;CACnB;AAED,OAAO,OAAO,cAAe,SAAQ,YAAY;IAC/C,IAAI,EAAE,OAAO,UAAU,CAAC;CACzB;AAED,QAAA,MAAM,gBAAgB,gBAAkD,CAAC;AAEzE,qBAAa,QAAS,SAAQ,gBAAgB,CAAC,IAAK,YAAW,IAAI;gBACrD,SAAS,CAAC,EAAE,GAAG,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe;IAIxD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ;IAOnE,MAAM,IAAI,cAAc;IAiBlB,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAO9C"} \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.js b/packages/expo-blob/build/BlobModule.js index 659ecfbfd1595d..4db0346ed7e974 100644 --- a/packages/expo-blob/build/BlobModule.js +++ b/packages/expo-blob/build/BlobModule.js @@ -29,7 +29,9 @@ export class ExpoBlob extends NativeBlobModule.Blob { }); } async arrayBuffer() { - return super.bytes().then((bytes) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)); + return super + .bytes() + .then((bytes) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)); } } //# sourceMappingURL=BlobModule.js.map \ No newline at end of file diff --git a/packages/expo-blob/build/BlobModule.js.map b/packages/expo-blob/build/BlobModule.js.map index d9388a03d506a4..b8b7dc9765c508 100644 --- a/packages/expo-blob/build/BlobModule.js.map +++ b/packages/expo-blob/build/BlobModule.js.map @@ -1 +1 @@ -{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAGvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IACjD,YAAY,SAAiB,EAAE,OAAyB;QACtD,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACtD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM;QACJ,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACpC,IAAI,CAAC,UAAU;gBACb,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC1B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACxB,CAAC;qBAAM,CAAC;oBACN,UAAU,CAAC,KAAK,EAAE,CAAC;gBACrB,CAAC;YACH,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAEF,KAAK,CAAC,WAAW;QAChB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7H,CAAC;CACD","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from 'expo';\n\nimport { Blob, BlobPart } from './BlobModule.types';\nimport { normalizedContentType } from './utils';\ndeclare class NativeBlob extends SharedObject {\n readonly size: number;\n readonly type: string;\n constructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n slice(start?: number, end?: number, contentType?: string): ExpoBlob;\n bytes(): Promise;\n text(): Promise;\n syncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n Blob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule('ExpoBlob');\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n constructor(blobParts?: any[], options?: BlobPropertyBag) {\n super(blobParts?.flat(Infinity) ?? [], options);\n }\n\n slice(start?: number, end?: number, contentType?: string): ExpoBlob {\n const normalizedType = normalizedContentType(contentType);\n const slicedBlob = super.slice(start, end, normalizedType);\n Object.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n return slicedBlob;\n }\n\n stream(): ReadableStream {\n const text = super.syncText();\n const encoder = new TextEncoder();\n const uint8 = encoder.encode(text);\n let offset = 0;\n return new ReadableStream({\n pull(controller) {\n if (offset < uint8.length) {\n controller.enqueue(uint8.subarray(offset));\n offset = uint8.length;\n } else {\n controller.close();\n }\n },\n });\n }\n\n\tasync arrayBuffer(): Promise {\n\t\treturn super.bytes().then((bytes: Uint8Array) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));\n\t}\n}\n"]} \ No newline at end of file +{"version":3,"file":"BlobModule.js","sourceRoot":"","sources":["../src/BlobModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAgB,MAAM,MAAM,CAAC;AAGvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAehD,MAAM,gBAAgB,GAAG,mBAAmB,CAAiB,UAAU,CAAC,CAAC;AAEzE,MAAM,OAAO,QAAS,SAAQ,gBAAgB,CAAC,IAAI;IACjD,YAAY,SAAiB,EAAE,OAAyB;QACtD,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,GAAY,EAAE,WAAoB;QACtD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM;QACJ,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,cAAc,CAAa;YACpC,IAAI,CAAC,UAAU;gBACb,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBAC1B,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC3C,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;gBACxB,CAAC;qBAAM,CAAC;oBACN,UAAU,CAAC,KAAK,EAAE,CAAC;gBACrB,CAAC;YACH,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,WAAW;QACf,OAAO,KAAK;aACT,KAAK,EAAE;aACP,IAAI,CAAC,CAAC,KAAiB,EAAE,EAAE,CAC1B,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,CAC1E,CAAC;IACN,CAAC;CACF","sourcesContent":["import { NativeModule, requireNativeModule, SharedObject } from 'expo';\n\nimport { Blob, BlobPart } from './BlobModule.types';\nimport { normalizedContentType } from './utils';\ndeclare class NativeBlob extends SharedObject {\n readonly size: number;\n readonly type: string;\n constructor(blobParts?: BlobPart[], options?: BlobPropertyBag);\n slice(start?: number, end?: number, contentType?: string): ExpoBlob;\n bytes(): Promise;\n text(): Promise;\n syncText(): string;\n}\n\ndeclare class ExpoBlobModule extends NativeModule {\n Blob: typeof NativeBlob;\n}\n\nconst NativeBlobModule = requireNativeModule('ExpoBlob');\n\nexport class ExpoBlob extends NativeBlobModule.Blob implements Blob {\n constructor(blobParts?: any[], options?: BlobPropertyBag) {\n super(blobParts?.flat(Infinity) ?? [], options);\n }\n\n slice(start?: number, end?: number, contentType?: string): ExpoBlob {\n const normalizedType = normalizedContentType(contentType);\n const slicedBlob = super.slice(start, end, normalizedType);\n Object.setPrototypeOf(slicedBlob, ExpoBlob.prototype);\n return slicedBlob;\n }\n\n stream(): ReadableStream {\n const text = super.syncText();\n const encoder = new TextEncoder();\n const uint8 = encoder.encode(text);\n let offset = 0;\n return new ReadableStream({\n pull(controller) {\n if (offset < uint8.length) {\n controller.enqueue(uint8.subarray(offset));\n offset = uint8.length;\n } else {\n controller.close();\n }\n },\n });\n }\n\n async arrayBuffer(): Promise {\n return super\n .bytes()\n .then((bytes: Uint8Array) =>\n bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)\n );\n }\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/build/utils.js b/packages/expo-blob/build/utils.js index aeee6cfcf0facb..b389eb8edd1b3a 100644 --- a/packages/expo-blob/build/utils.js +++ b/packages/expo-blob/build/utils.js @@ -15,12 +15,12 @@ */ export function normalizedContentType(type) { if (type === null) - return "null"; + return 'null'; if (!type || type.length === 0) - return ""; + return ''; const asciiPrintable = /^[\x20-\x7E]+$/; if (!asciiPrintable.test(type)) - return ""; + return ''; return type.toLowerCase(); } //# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/packages/expo-blob/build/utils.js.map b/packages/expo-blob/build/utils.js.map index 3ba32545f28e93..d8d5cac863252e 100644 --- a/packages/expo-blob/build/utils.js.map +++ b/packages/expo-blob/build/utils.js.map @@ -1 +1 @@ -{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAa;IAClD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IAChC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,gBAAgB,CAAC;IACxC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * Normalizes the content type string for a Blob.\n *\n * Returns the lowercased content type if it is valid, or an empty string otherwise.\n *\n * A valid content type:\n * - Is not null, undefined, or empty\n * - Contains only printable ASCII characters (0x20–0x7E)\n * - Does not contain forbidden control characters: NUL (\\x00), LF (\\x0A), or CR (\\x0D)\n *\n * If any of these conditions are not met, returns an empty string to indicate an invalid or unsafe content type.\n *\n * @param type The content type string to normalize.\n * @returns The normalized (lowercased) content type, or an empty string if invalid.\n */\nexport function normalizedContentType(type?: string): string {\n\tif (type === null) return \"null\"\n\tif (!type || type.length === 0) return \"\";\n\tconst asciiPrintable = /^[\\x20-\\x7E]+$/;\n\tif (!asciiPrintable.test(type)) return \"\";\n\treturn type.toLowerCase();\n}\n"]} \ No newline at end of file +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAa;IACjD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IACjC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,gBAAgB,CAAC;IACxC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * Normalizes the content type string for a Blob.\n *\n * Returns the lowercased content type if it is valid, or an empty string otherwise.\n *\n * A valid content type:\n * - Is not null, undefined, or empty\n * - Contains only printable ASCII characters (0x20–0x7E)\n * - Does not contain forbidden control characters: NUL (\\x00), LF (\\x0A), or CR (\\x0D)\n *\n * If any of these conditions are not met, returns an empty string to indicate an invalid or unsafe content type.\n *\n * @param type The content type string to normalize.\n * @returns The normalized (lowercased) content type, or an empty string if invalid.\n */\nexport function normalizedContentType(type?: string): string {\n if (type === null) return 'null';\n if (!type || type.length === 0) return '';\n const asciiPrintable = /^[\\x20-\\x7E]+$/;\n if (!asciiPrintable.test(type)) return '';\n return type.toLowerCase();\n}\n"]} \ No newline at end of file diff --git a/packages/expo-blob/src/BlobModule.ts b/packages/expo-blob/src/BlobModule.ts index 29b5d4a7e2fa62..582df6093d549a 100644 --- a/packages/expo-blob/src/BlobModule.ts +++ b/packages/expo-blob/src/BlobModule.ts @@ -47,7 +47,11 @@ export class ExpoBlob extends NativeBlobModule.Blob implements Blob { }); } - async arrayBuffer(): Promise { - return super.bytes().then((bytes: Uint8Array) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)); - } + async arrayBuffer(): Promise { + return super + .bytes() + .then((bytes: Uint8Array) => + bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) + ); + } } diff --git a/packages/expo-blob/src/utils.ts b/packages/expo-blob/src/utils.ts index 11c745155ee720..f69a31a9978c3e 100644 --- a/packages/expo-blob/src/utils.ts +++ b/packages/expo-blob/src/utils.ts @@ -14,9 +14,9 @@ * @returns The normalized (lowercased) content type, or an empty string if invalid. */ export function normalizedContentType(type?: string): string { - if (type === null) return "null" - if (!type || type.length === 0) return ""; - const asciiPrintable = /^[\x20-\x7E]+$/; - if (!asciiPrintable.test(type)) return ""; - return type.toLowerCase(); + if (type === null) return 'null'; + if (!type || type.length === 0) return ''; + const asciiPrintable = /^[\x20-\x7E]+$/; + if (!asciiPrintable.test(type)) return ''; + return type.toLowerCase(); }