The Winter module provides polyfills for modern web APIs to ensure consistent behavior across React Native and web platforms. These polyfills are automatically installed globally when using Expo, enabling you to use standard web APIs like fetch, URL, TextEncoder/Decoder, and FormData in React Native applications with identical behavior to web browsers.
Note: Winter polyfills are automatically available globally - no explicit imports are required. They override or provide missing global APIs in React Native environments.
Complete implementation of the modern fetch API with Request, Response, and Headers support.
/**
* Fetch API for making HTTP requests with Promise-based interface
* @param input - URL string or Request object
* @param init - Optional request configuration
* @returns Promise resolving to Response object
*/
function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
/**
* HTTP Headers implementation with case-insensitive header management
*/
class Headers {
constructor(init?: HeadersInit);
append(name: string, value: string): void;
delete(name: string): void;
get(name: string): string | null;
has(name: string): boolean;
set(name: string, value: string): void;
forEach(callback: (value: string, key: string, parent: Headers) => void): void;
keys(): IterableIterator<string>;
values(): IterableIterator<string>;
entries(): IterableIterator<[string, string]>;
}
/**
* HTTP Request representation with full configuration options
*/
class Request {
constructor(input: RequestInfo | URL, init?: RequestInit);
readonly method: string;
readonly url: string;
readonly headers: Headers;
readonly body: ReadableStream<Uint8Array> | null;
readonly bodyUsed: boolean;
readonly cache: RequestCache;
readonly credentials: RequestCredentials;
readonly destination: RequestDestination;
readonly integrity: string;
readonly keepalive: boolean;
readonly mode: RequestMode;
readonly redirect: RequestRedirect;
readonly referrer: string;
readonly referrerPolicy: ReferrerPolicy;
readonly signal: AbortSignal;
clone(): Request;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
}
/**
* HTTP Response representation with status and data handling
*/
class Response {
constructor(body?: BodyInit | null, init?: ResponseInit);
readonly status: number;
readonly statusText: string;
readonly headers: Headers;
readonly ok: boolean;
readonly redirected: boolean;
readonly type: ResponseType;
readonly url: string;
readonly body: ReadableStream<Uint8Array> | null;
readonly bodyUsed: boolean;
clone(): Response;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
static error(): Response;
static redirect(url: string, status?: number): Response;
}Complete URL parsing and manipulation implementation compatible with the web standard.
/**
* URL constructor and manipulation utilities
*/
class URL {
constructor(url: string, base?: string | URL);
hash: string;
host: string;
hostname: string;
href: string;
origin: string;
password: string;
pathname: string;
port: string;
protocol: string;
search: string;
searchParams: URLSearchParams;
username: string;
toString(): string;
toJSON(): string;
}
/**
* URL search parameters manipulation
*/
class URLSearchParams {
constructor(init?: string | string[][] | Record<string, string> | URLSearchParams);
append(name: string, value: string): void;
delete(name: string): void;
get(name: string): string | null;
getAll(name: string): string[];
has(name: string): boolean;
set(name: string, value: string): void;
sort(): void;
toString(): string;
forEach(callback: (value: string, key: string, parent: URLSearchParams) => void): void;
keys(): IterableIterator<string>;
values(): IterableIterator<string>;
entries(): IterableIterator<[string, string]>;
}Text encoding and decoding utilities for handling different character encodings.
/**
* Text decoding from various character encodings to UTF-16 strings
*/
class TextDecoder {
constructor(label?: string, options?: TextDecoderOptions);
readonly encoding: string;
readonly fatal: boolean;
readonly ignoreBOM: boolean;
decode(input?: BufferSource, options?: TextDecodeOptions): string;
}
/**
* Text encoding from UTF-16 strings to UTF-8 bytes
*/
class TextEncoder {
constructor();
readonly encoding: string;
encode(input?: string): Uint8Array;
encodeInto(source: string, destination: Uint8Array): TextEncoderEncodeIntoResult;
}
interface TextDecoderOptions {
fatal?: boolean;
ignoreBOM?: boolean;
}
interface TextDecodeOptions {
stream?: boolean;
}
interface TextEncoderEncodeIntoResult {
read: number;
written: number;
}Form data handling for multipart/form-data HTTP requests and form processing.
/**
* FormData implementation for handling form submissions and file uploads
*/
class FormData {
constructor();
append(name: string, value: string | Blob, filename?: string): void;
delete(name: string): void;
get(name: string): FormDataEntryValue | null;
getAll(name: string): FormDataEntryValue[];
has(name: string): boolean;
set(name: string, value: string | Blob, filename?: string): void;
forEach(callback: (value: FormDataEntryValue, key: string, parent: FormData) => void): void;
keys(): IterableIterator<string>;
values(): IterableIterator<FormDataEntryValue>;
entries(): IterableIterator<[string, FormDataEntryValue]>;
}
type FormDataEntryValue = string | File;// No imports needed - globally available
// import { fetch, Headers, Request, Response } from 'expo/fetch'; // Not needed
// Basic GET request
async function fetchUserData(userId: string) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const userData = await response.json();
return userData;
} catch (error) {
console.error('Failed to fetch user data:', error);
throw error;
}
}
// POST request with JSON body
async function createUser(userData: any) {
const headers = new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here',
});
const request = new Request('https://api.example.com/users', {
method: 'POST',
headers,
body: JSON.stringify(userData),
});
const response = await fetch(request);
if (response.ok) {
return await response.json();
} else {
const errorText = await response.text();
throw new Error(`Failed to create user: ${errorText}`);
}
}
// File upload with FormData
async function uploadFile(file: File) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'Profile picture');
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData,
});
return await response.json();
}
// Request with timeout and abort controller
async function fetchWithTimeout(url: string, timeoutMs: number = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
signal: controller.signal,
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}// No imports needed - globally available
// URL parsing and manipulation
function buildApiUrl(baseUrl: string, endpoint: string, params: Record<string, string>) {
const url = new URL(endpoint, baseUrl);
// Add query parameters
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
return url.toString();
}
// Example usage
const apiUrl = buildApiUrl(
'https://api.example.com',
'/search',
{ q: 'javascript', limit: '10', page: '1' }
);
// Result: https://api.example.com/search?q=javascript&limit=10&page=1
// URLSearchParams manipulation
function updateUrlParams(currentUrl: string, updates: Record<string, string>) {
const url = new URL(currentUrl);
Object.entries(updates).forEach(([key, value]) => {
if (value) {
url.searchParams.set(key, value);
} else {
url.searchParams.delete(key);
}
});
return url.toString();
}
// Parse query string
function parseQueryString(search: string): Record<string, string> {
const params = new URLSearchParams(search);
const result: Record<string, string> = {};
params.forEach((value, key) => {
result[key] = value;
});
return result;
}// No imports needed - globally available
// Encode text to bytes
function encodeTextToBytes(text: string): Uint8Array {
const encoder = new TextEncoder();
return encoder.encode(text);
}
// Decode bytes to text
function decodeBytesToText(bytes: Uint8Array): string {
const decoder = new TextDecoder('utf-8');
return decoder.decode(bytes);
}
// Handle different encodings
function decodeWithEncoding(bytes: Uint8Array, encoding: string): string {
try {
const decoder = new TextDecoder(encoding, { fatal: true });
return decoder.decode(bytes);
} catch (error) {
// Fallback to UTF-8 if encoding fails
const fallbackDecoder = new TextDecoder('utf-8', { fatal: false });
return fallbackDecoder.decode(bytes);
}
}
// Stream decoding for large data
function streamDecode(chunks: Uint8Array[]): string {
const decoder = new TextDecoder('utf-8');
let result = '';
chunks.forEach((chunk, index) => {
const isLast = index === chunks.length - 1;
result += decoder.decode(chunk, { stream: !isLast });
});
return result;
}// No imports needed - globally available
// Create form data for file upload
function createUploadFormData(file: File, metadata: Record<string, string>): FormData {
const formData = new FormData();
// Add file
formData.append('file', file, file.name);
// Add metadata
Object.entries(metadata).forEach(([key, value]) => {
formData.append(key, value);
});
return formData;
}
// Convert object to FormData
function objectToFormData(obj: Record<string, any>): FormData {
const formData = new FormData();
Object.entries(obj).forEach(([key, value]) => {
if (value instanceof File || value instanceof Blob) {
formData.append(key, value);
} else if (Array.isArray(value)) {
value.forEach(item => formData.append(key, String(item)));
} else if (value !== null && value !== undefined) {
formData.append(key, String(value));
}
});
return formData;
}
// Extract data from FormData
function formDataToObject(formData: FormData): Record<string, any> {
const result: Record<string, any> = {};
formData.forEach((value, key) => {
if (result[key]) {
// Handle multiple values for same key
if (Array.isArray(result[key])) {
result[key].push(value);
} else {
result[key] = [result[key], value];
}
} else {
result[key] = value;
}
});
return result;
}// No imports needed - globally available
// HTTP client with retry logic
class HTTPClient {
private baseUrl: string;
private defaultHeaders: Headers;
constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {
this.baseUrl = baseUrl;
this.defaultHeaders = new Headers(defaultHeaders);
}
async request(
endpoint: string,
options: RequestInit = {},
retries: number = 3
): Promise<Response> {
const url = new URL(endpoint, this.baseUrl);
// Merge headers
const headers = new Headers(this.defaultHeaders);
if (options.headers) {
const optionHeaders = new Headers(options.headers);
optionHeaders.forEach((value, key) => {
headers.set(key, value);
});
}
const request = new Request(url.toString(), {
...options,
headers,
});
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const response = await fetch(request.clone());
if (response.ok || attempt === retries) {
return response;
}
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
} catch (error) {
if (attempt === retries) {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}
async get(endpoint: string, headers?: Record<string, string>) {
return this.request(endpoint, { method: 'GET', headers });
}
async post(endpoint: string, body: any, headers?: Record<string, string>) {
return this.request(endpoint, {
method: 'POST',
body: typeof body === 'string' ? body : JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
...headers,
},
});
}
}
// Usage
const client = new HTTPClient('https://api.example.com', {
'Authorization': 'Bearer token',
'User-Agent': 'MyApp/1.0',
});
const userData = await client.get('/users/123');
const newUser = await client.post('/users', { name: 'John', email: 'john@example.com' });The Winter polyfills ensure identical behavior across React Native and web platforms:
// This code works identically on web and React Native
async function universalHttpRequest(url: string, data: any) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}// Check if polyfills are available
function checkPolyfillSupport() {
return {
fetch: typeof fetch !== 'undefined',
URL: typeof URL !== 'undefined',
URLSearchParams: typeof URLSearchParams !== 'undefined',
TextEncoder: typeof TextEncoder !== 'undefined',
TextDecoder: typeof TextDecoder !== 'undefined',
FormData: typeof FormData !== 'undefined',
};
}
// Conditional usage based on availability
function safeHttpRequest(url: string) {
if (typeof fetch !== 'undefined') {
return fetch(url);
} else {
// Fallback for environments without fetch
throw new Error('Fetch API not available');
}
}The Winter polyfills are optimized for React Native but may have different performance characteristics than native browser implementations:
// For large data processing, consider streaming
async function processLargeResponse(url: string) {
const response = await fetch(url);
const reader = response.body?.getReader();
if (!reader) {
throw new Error('Response body not readable');
}
let result = '';
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
result += decoder.decode(value, { stream: true });
// Process chunks as they arrive
processChunk(result);
}
return result;
}