UNPKG

react-native-fast-ws

Version:

A modern WebSocket implementation for React Native, built with Nitro

171 lines (148 loc) 4.72 kB
import { ReadableStream } from 'web-streams-polyfill' /** * https://w3c.github.io/FileAPI/#dfn-BlobPropertyBag */ interface BlobPropertyBag { type?: string } /** * https://w3c.github.io/FileAPI/#typedefdef-blobpart */ type BufferSource = ArrayBuffer | ArrayBufferView type BlobPart = BufferSource | Blob | string function calculateSize(parts: Array<BlobPart>): number { return parts.reduce((acc, part) => { if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) { return acc + part.byteLength } if (part instanceof Blob) { return acc + part.size } if (typeof part === 'string') { return acc + new TextEncoder().encode(part).length } return acc }, 0) } /** * https://w3c.github.io/FileAPI/#blob-section */ export class Blob { private parts: Array<BlobPart> /** * https://w3c.github.io/FileAPI/#attributes-blob */ readonly size: number readonly type: string constructor(parts: Array<BlobPart> = [], options: BlobPropertyBag = {}) { this.parts = parts this.size = calculateSize(parts) this.type = options?.type?.toLowerCase() || '' } private async getData(): Promise<Uint8Array> { const view = new Uint8Array(this.size) let offset = 0 for (const part of this.parts) { switch (true) { case part instanceof ArrayBuffer: view.set(new Uint8Array(part), offset) offset += part.byteLength break case ArrayBuffer.isView(part): view.set(new Uint8Array(part.buffer, part.byteOffset, part.byteLength), offset) offset += part.byteLength break case part instanceof Blob: { const partBuffer = await part.getData() view.set(new Uint8Array(partBuffer), offset) offset += partBuffer.byteLength break } case typeof part === 'string': { const encoded = new TextEncoder().encode(part) view.set(encoded, offset) offset += encoded.length break } default: throw new Error('Invalid blob part') } } return view } /** * https://w3c.github.io/FileAPI/#slice-method-algo */ slice(start: number = 0, end: number = this.size, contentType: string = ''): Blob { const relativeStart = start < 0 ? Math.max(this.size + start, 0) : Math.min(start, this.size) const relativeEnd = end < 0 ? Math.max(this.size + end, 0) : Math.min(end, this.size) const span = Math.max(relativeEnd - relativeStart, 0) const parts: BlobPart[] = [] let blobSize = 0 for (const part of this.parts) { if (blobSize >= relativeEnd) break let partSize: number if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) { partSize = part.byteLength } else if (part instanceof Blob) { partSize = part.size } else if (typeof part === 'string') { partSize = new TextEncoder().encode(part).length } else { throw new Error('Invalid blob part') } if (blobSize + partSize > relativeStart) { const partStart = Math.max(0, relativeStart - blobSize) const partEnd = Math.min(partSize, partStart + span) if (part instanceof ArrayBuffer) { parts.push(part.slice(partStart, partEnd)) } else if (ArrayBuffer.isView(part)) { parts.push(part.buffer.slice(part.byteOffset + partStart, part.byteOffset + partEnd)) } else if (part instanceof Blob) { parts.push(part.slice(partStart, partEnd)) } else if (typeof part === 'string') { const encoded = new TextEncoder().encode(part) parts.push(encoded.slice(partStart, partEnd)) } blobSize += partEnd - partStart } else { blobSize += partSize } } return new Blob(parts, { type: contentType }) } /** * https://w3c.github.io/FileAPI/#stream-method-algo */ stream(): ReadableStream<Uint8Array> { return new ReadableStream({ start: async (controller) => { controller.enqueue(await this.getData()) controller.close() }, }) } /** * https://w3c.github.io/FileAPI/#arraybuffer-method-algo */ async arrayBuffer(): Promise<ArrayBuffer> { const view = await this.getData() return view.buffer } /** * https://w3c.github.io/FileAPI/#bytes-method-algo */ async bytes(): Promise<Uint8Array> { return await this.getData() } /** * https://w3c.github.io/FileAPI/#text-method-algo */ async text(): Promise<string> { const view = await this.getData() return new TextDecoder().decode(view) } get [Symbol.toStringTag](): string { return 'Blob' } }