first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

158
node_modules/react-native/Libraries/Blob/Blob.js generated vendored Normal file
View File

@@ -0,0 +1,158 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {BlobData, BlobOptions} from './BlobTypes';
/**
* Opaque JS representation of some binary data in native.
*
* The API is modeled after the W3C Blob API, with one caveat
* regarding explicit deallocation. Refer to the `close()`
* method for further details.
*
* Example usage in a React component:
*
* class WebSocketImage extends React.Component {
* state = {blob: null};
* componentDidMount() {
* let ws = this.ws = new WebSocket(...);
* ws.binaryType = 'blob';
* ws.onmessage = (event) => {
* if (this.state.blob) {
* this.state.blob.close();
* }
* this.setState({blob: event.data});
* };
* }
* componentUnmount() {
* if (this.state.blob) {
* this.state.blob.close();
* }
* this.ws.close();
* }
* render() {
* if (!this.state.blob) {
* return <View />;
* }
* return <Image source={{uri: URL.createObjectURL(this.state.blob)}} />;
* }
* }
*
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob
*/
class Blob {
_data: ?BlobData;
/**
* Constructor for JS consumers.
* Currently we only support creating Blobs from other Blobs.
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
*/
constructor(parts: Array<Blob | string> = [], options?: BlobOptions) {
const BlobManager = require('./BlobManager').default;
this.data = BlobManager.createFromParts(parts, options).data;
}
/*
* This method is used to create a new Blob object containing
* the data in the specified range of bytes of the source Blob.
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice
*/
// $FlowFixMe[unsafe-getters-setters]
set data(data: ?BlobData) {
this._data = data;
}
// $FlowFixMe[unsafe-getters-setters]
get data(): BlobData {
if (!this._data) {
throw new Error('Blob has been closed and is no longer available');
}
return this._data;
}
slice(start?: number, end?: number, contentType?: string = ''): Blob {
const BlobManager = require('./BlobManager').default;
let {offset, size} = this.data;
if (typeof start === 'number') {
if (start > size) {
// $FlowFixMe[reassign-const]
start = size;
}
offset += start;
size -= start;
if (typeof end === 'number') {
if (end < 0) {
// $FlowFixMe[reassign-const]
end = this.size + end;
}
if (end > this.size) {
// $FlowFixMe[reassign-const]
end = this.size;
}
size = end - start;
}
}
return BlobManager.createFromOptions({
blobId: this.data.blobId,
offset,
size,
type: contentType,
/* Since `blob.slice()` creates a new view onto the same binary
* data as the original blob, we should re-use the same collector
* object so that the underlying resource gets deallocated when
* the last view into the data is released, not the first.
*/
__collector: this.data.__collector,
});
}
/**
* This method is in the standard, but not actually implemented by
* any browsers at this point. It's important for how Blobs work in
* React Native, however, since we cannot de-allocate resources automatically,
* so consumers need to explicitly de-allocate them.
*
* Note that the semantics around Blobs created via `blob.slice()`
* and `new Blob([blob])` are different. `blob.slice()` creates a
* new *view* onto the same binary data, so calling `close()` on any
* of those views is enough to deallocate the data, whereas
* `new Blob([blob, ...])` actually copies the data in memory.
*/
close() {
const BlobManager = require('./BlobManager').default;
BlobManager.release(this.data.blobId);
this.data = null;
}
/**
* Size of the data contained in the Blob object, in bytes.
*/
// $FlowFixMe[unsafe-getters-setters]
get size(): number {
return this.data.size;
}
/*
* String indicating the MIME type of the data contained in the Blob.
* If the type is unknown, this string is empty.
*/
// $FlowFixMe[unsafe-getters-setters]
get type(): string {
return this.data.type || '';
}
}
export default Blob;

185
node_modules/react-native/Libraries/Blob/BlobManager.js generated vendored Normal file
View File

@@ -0,0 +1,185 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import typeof BlobT from './Blob';
import type {BlobCollector, BlobData, BlobOptions} from './BlobTypes';
import NativeBlobModule from './NativeBlobModule';
import invariant from 'invariant';
const Blob: BlobT = require('./Blob').default;
const BlobRegistry = require('./BlobRegistry');
/*eslint-disable no-bitwise */
/*eslint-disable eqeqeq */
/**
* Based on the rfc4122-compliant solution posted at
* http://stackoverflow.com/questions/105034
*/
function uuidv4(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// **Temporary workaround**
// TODO(#24654): Use turbomodules for the Blob module.
// Blob collector is a jsi::HostObject that is used by native to know
// when the a Blob instance is deallocated. This allows to free the
// underlying native resources. This is a hack to workaround the fact
// that the current bridge infra doesn't allow to track js objects
// deallocation. Ideally the whole Blob object should be a jsi::HostObject.
function createBlobCollector(blobId: string): BlobCollector | null {
if (global.__blobCollectorProvider == null) {
return null;
} else {
return global.__blobCollectorProvider(blobId);
}
}
/**
* Module to manage blobs. Wrapper around the native blob module.
*/
class BlobManager {
/**
* If the native blob module is available.
*/
static isAvailable: boolean = !!NativeBlobModule;
/**
* Create blob from existing array of blobs.
*/
static createFromParts(
parts: Array<Blob | string>,
options?: BlobOptions,
): Blob {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
const blobId = uuidv4();
const items = parts.map(part => {
if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
throw new Error(
"Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported",
);
}
if (part instanceof Blob) {
return {
data: part.data,
type: 'blob',
};
} else {
return {
data: String(part),
type: 'string',
};
}
});
const size = items.reduce((acc, curr) => {
if (curr.type === 'string') {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return acc + global.unescape(encodeURI(curr.data)).length;
} else {
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return acc + curr.data.size;
}
}, 0);
NativeBlobModule.createFromParts(items, blobId);
return BlobManager.createFromOptions({
blobId,
offset: 0,
size,
type: options ? options.type : '',
lastModified: options ? options.lastModified : Date.now(),
});
}
/**
* Create blob instance from blob data from native.
* Used internally by modules like XHR, WebSocket, etc.
*/
static createFromOptions(options: BlobData): Blob {
BlobRegistry.register(options.blobId);
// $FlowFixMe[prop-missing]
// $FlowFixMe[unsafe-object-assign]
return Object.assign(Object.create(Blob.prototype), {
data:
// Reuse the collector instance when creating from an existing blob.
// This will make sure that the underlying resource is only deallocated
// when all blobs that refer to it are deallocated.
options.__collector == null
? {
...options,
__collector: createBlobCollector(options.blobId),
}
: options,
});
}
/**
* Deallocate resources for a blob.
*/
static release(blobId: string): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
BlobRegistry.unregister(blobId);
if (BlobRegistry.has(blobId)) {
return;
}
NativeBlobModule.release(blobId);
}
/**
* Inject the blob content handler in the networking module to support blob
* requests and responses.
*/
static addNetworkingHandler(): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.addNetworkingHandler();
}
/**
* Indicate the websocket should return a blob for incoming binary
* messages.
*/
static addWebSocketHandler(socketId: number): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.addWebSocketHandler(socketId);
}
/**
* Indicate the websocket should no longer return a blob for incoming
* binary messages.
*/
static removeWebSocketHandler(socketId: number): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.removeWebSocketHandler(socketId);
}
/**
* Send a blob message to a websocket.
*/
static sendOverSocket(blob: Blob, socketId: number): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.sendOverSocket(blob.data, socketId);
}
}
export default BlobManager;

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
const registry: Map<string, number> = new Map();
export const register = (id: string) => {
const used = registry.get(id);
if (used != null) {
registry.set(id, used + 1);
} else {
registry.set(id, 1);
}
};
export const unregister = (id: string) => {
const used = registry.get(id);
if (used != null) {
if (used <= 1) {
registry.delete(id);
} else {
registry.set(id, used - 1);
}
}
};
export const has = (id: string): number | boolean => {
return registry.get(id) || false;
};

30
node_modules/react-native/Libraries/Blob/BlobTypes.js generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
export opaque type BlobCollector = {...};
export type BlobData = {
blobId: string,
offset: number,
size: number,
name?: string,
type?: string,
lastModified?: number,
__collector?: ?BlobCollector,
...
};
export type BlobOptions = {
type: string,
lastModified: number,
...
};

56
node_modules/react-native/Libraries/Blob/File.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {BlobOptions} from './BlobTypes';
import Blob from './Blob';
const invariant = require('invariant');
/**
* The File interface provides information about files.
*/
class File extends Blob {
/**
* Constructor for JS consumers.
*/
constructor(
parts: Array<Blob | string>,
name: string,
options?: BlobOptions,
) {
invariant(
parts != null && name != null,
'Failed to construct `File`: Must pass both `parts` and `name` arguments.',
);
super(parts, options);
this.data.name = name;
}
/**
* Name of the file.
*/
get name(): string {
invariant(this.data.name != null, 'Files must have a name set.');
return this.data.name;
}
/*
* Last modified time of the file.
*/
get lastModified(): number {
return this.data.lastModified || 0;
}
}
export default File;

231
node_modules/react-native/Libraries/Blob/FileReader.js generated vendored Normal file
View File

@@ -0,0 +1,231 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import type {EventCallback} from '../../src/private/webapis/dom/events/EventTarget';
import type Blob from './Blob';
import Event from '../../src/private/webapis/dom/events/Event';
import {
getEventHandlerAttribute,
setEventHandlerAttribute,
} from '../../src/private/webapis/dom/events/EventHandlerAttributes';
import EventTarget from '../../src/private/webapis/dom/events/EventTarget';
import NativeFileReaderModule from './NativeFileReaderModule';
import {toByteArray} from 'base64-js';
type ReadyState =
| 0 // EMPTY
| 1 // LOADING
| 2; // DONE
type ReaderResult = string | ArrayBuffer;
const EMPTY = 0;
const LOADING = 1;
const DONE = 2;
class FileReader extends EventTarget {
static EMPTY: number = EMPTY;
static LOADING: number = LOADING;
static DONE: number = DONE;
EMPTY: number = EMPTY;
LOADING: number = LOADING;
DONE: number = DONE;
_readyState: ReadyState;
_error: ?Error;
_result: ?ReaderResult;
_aborted: boolean = false;
constructor() {
super();
this._reset();
}
_reset(): void {
this._readyState = EMPTY;
this._error = null;
this._result = null;
}
_setReadyState(newState: ReadyState) {
this._readyState = newState;
this.dispatchEvent(new Event('readystatechange'));
if (newState === DONE) {
if (this._aborted) {
this.dispatchEvent(new Event('abort'));
} else if (this._error) {
this.dispatchEvent(new Event('error'));
} else {
this.dispatchEvent(new Event('load'));
}
this.dispatchEvent(new Event('loadend'));
}
}
readAsArrayBuffer(blob: ?Blob): void {
this._aborted = false;
if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsArrayBuffer' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}
NativeFileReaderModule.readAsDataURL(blob.data).then(
(text: string) => {
if (this._aborted) {
return;
}
const base64 = text.split(',')[1];
const typedArray = toByteArray(base64);
this._result = typedArray.buffer;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}
readAsDataURL(blob: ?Blob): void {
this._aborted = false;
if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}
NativeFileReaderModule.readAsDataURL(blob.data).then(
(text: string) => {
if (this._aborted) {
return;
}
this._result = text;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}
readAsText(blob: ?Blob, encoding: string = 'UTF-8'): void {
this._aborted = false;
if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsText' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}
NativeFileReaderModule.readAsText(blob.data, encoding).then(
(text: string) => {
if (this._aborted) {
return;
}
this._result = text;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}
abort() {
this._aborted = true;
// only call onreadystatechange if there is something to abort, as per spec
if (this._readyState !== EMPTY && this._readyState !== DONE) {
this._reset();
this._setReadyState(DONE);
}
// Reset again after, in case modified in handler
this._reset();
}
get readyState(): ReadyState {
return this._readyState;
}
get error(): ?Error {
return this._error;
}
get result(): ?ReaderResult {
return this._result;
}
get onabort(): EventCallback | null {
return getEventHandlerAttribute(this, 'abort');
}
set onabort(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'abort', listener);
}
get onerror(): EventCallback | null {
return getEventHandlerAttribute(this, 'error');
}
set onerror(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'error', listener);
}
get onload(): EventCallback | null {
return getEventHandlerAttribute(this, 'load');
}
set onload(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'load', listener);
}
get onloadstart(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadstart');
}
set onloadstart(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadstart', listener);
}
get onloadend(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadend');
}
set onloadend(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadend', listener);
}
get onprogress(): EventCallback | null {
return getEventHandlerAttribute(this, 'progress');
}
set onprogress(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'progress', listener);
}
}
export default FileReader;

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeBlobModule';
import NativeBlobModule from '../../src/private/specs_DEPRECATED/modules/NativeBlobModule';
export default NativeBlobModule;

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeFileReaderModule';
import NativeFileReaderModule from '../../src/private/specs_DEPRECATED/modules/NativeFileReaderModule';
export default NativeFileReaderModule;

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <jsi/jsi.h>
@class RCTBlobManager;
namespace facebook::react {
class JSI_EXPORT RCTBlobCollector : public jsi::HostObject {
public:
RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId);
~RCTBlobCollector();
static void install(RCTBlobManager *blobManager);
private:
const std::string blobId_;
RCTBlobManager *blobManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTBlobCollector.h"
#import <React/RCTBlobManager.h>
#import <React/RCTBridge+Private.h>
namespace facebook::react {
RCTBlobCollector::RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId)
: blobId_(blobId), blobManager_(blobManager)
{
}
RCTBlobCollector::~RCTBlobCollector()
{
RCTBlobManager *blobManager = blobManager_;
NSString *blobId = [NSString stringWithUTF8String:blobId_.c_str()];
dispatch_async(blobManager_.methodQueue, ^{
[blobManager remove:blobId];
});
}
void RCTBlobCollector::install(RCTBlobManager *blobManager)
{
__weak RCTCxxBridge *cxxBridge = (RCTCxxBridge *)blobManager.bridge;
[cxxBridge
dispatchBlock:^{
if ((cxxBridge == nullptr) || cxxBridge.runtime == nullptr) {
return;
}
jsi::Runtime &runtime = *(jsi::Runtime *)cxxBridge.runtime;
runtime.global().setProperty(
runtime,
"__blobCollectorProvider",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
1,
[blobManager](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
auto blobId = args[0].asString(rt).utf8(rt);
auto blobCollector = std::make_shared<RCTBlobCollector>(blobManager, blobId);
auto blobCollectorJsObject = jsi::Object::createFromHostObject(rt, blobCollector);
blobCollectorJsObject.setExternalMemoryPressure(
rt, [blobManager lengthOfBlobWithId:[NSString stringWithUTF8String:blobId.c_str()]]);
return blobCollectorJsObject;
}));
}
queue:RCTJSThread];
}
} // namespace facebook::react

31
node_modules/react-native/Libraries/Blob/RCTBlobManager.h generated vendored Executable file
View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInitializing.h>
#import <React/RCTURLRequestHandler.h>
@interface RCTBlobManager : NSObject <RCTBridgeModule, RCTURLRequestHandler, RCTInitializing>
- (NSString *)store:(NSData *)data;
- (void)store:(NSData *)data withId:(NSString *)blobId;
- (NSUInteger)lengthOfBlobWithId:(NSString *)blobId;
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob;
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size;
- (NSData *)resolveURL:(NSURL *)url;
- (void)remove:(NSString *)blobId;
- (void)createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId;
@end

341
node_modules/react-native/Libraries/Blob/RCTBlobManager.mm generated vendored Executable file
View File

@@ -0,0 +1,341 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBlobManager.h>
#import <mutex>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTConvert.h>
#import <React/RCTMockDef.h>
#import <React/RCTNetworking.h>
#import <React/RCTUtils.h>
#import <React/RCTWebSocketModule.h>
#import "RCTBlobCollector.h"
#import "RCTBlobPlugins.h"
RCT_MOCK_DEF(RCTBlobManager, dispatch_async);
#define dispatch_async RCT_MOCK_USE(RCTBlobManager, dispatch_async)
static NSString *const kBlobURIScheme = @"blob";
@interface RCTBlobManager () <
RCTNetworkingRequestHandler,
RCTNetworkingResponseHandler,
RCTWebSocketContentHandler,
NativeBlobModuleSpec>
@end
@implementation RCTBlobManager {
// Blobs should be thread safe since they are used from the websocket and networking module,
// make sure to use proper locking when accessing this.
NSMutableDictionary<NSString *, NSData *> *_blobs;
std::mutex _blobsMutex;
NSOperationQueue *_queue;
dispatch_queue_t _processingQueue;
}
RCT_EXPORT_MODULE(BlobModule)
@synthesize bridge = _bridge;
@synthesize moduleRegistry = _moduleRegistry;
@synthesize methodQueue = _methodQueue;
- (void)initialize
{
std::lock_guard<std::mutex> lock(_blobsMutex);
_blobs = [NSMutableDictionary new];
facebook::react::RCTBlobCollector::install(self);
}
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (NSDictionary<NSString *, id> *)constantsToExport
{
return [self getConstants];
}
- (NSDictionary<NSString *, id> *)getConstants
{
return @{
@"BLOB_URI_SCHEME" : kBlobURIScheme,
@"BLOB_URI_HOST" : [NSNull null],
};
}
- (NSString *)store:(NSData *)data
{
NSString *blobId = [NSUUID UUID].UUIDString;
[self store:data withId:blobId];
return blobId;
}
- (void)store:(NSData *)data withId:(NSString *)blobId
{
std::lock_guard<std::mutex> lock(_blobsMutex);
_blobs[blobId] = data;
}
- (NSUInteger)lengthOfBlobWithId:(NSString *)blobId
{
std::lock_guard<std::mutex> lock(_blobsMutex);
return _blobs[blobId].length;
}
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob
{
NSString *blobId = [RCTConvert NSString:blob[@"blobId"]];
NSNumber *offset = [RCTConvert NSNumber:blob[@"offset"]];
NSNumber *size = [RCTConvert NSNumber:blob[@"size"]];
return [self resolve:blobId offset:offset ? [offset integerValue] : 0 size:size ? [size integerValue] : -1];
}
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size
{
NSData *data;
{
std::lock_guard<std::mutex> lock(_blobsMutex);
data = _blobs[blobId];
}
if (!data) {
return nil;
}
if (offset != 0 || (size != -1 && size != data.length)) {
data = [data subdataWithRange:NSMakeRange(offset, size)];
}
return data;
}
- (NSData *)resolveURL:(NSURL *)url
{
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
NSString *blobId = components.path;
NSInteger offset = 0;
NSInteger size = -1;
if (components.queryItems) {
for (NSURLQueryItem *queryItem in components.queryItems) {
if ([queryItem.name isEqualToString:@"offset"]) {
offset = [queryItem.value integerValue];
}
if ([queryItem.name isEqualToString:@"size"]) {
size = [queryItem.value integerValue];
}
}
}
if (blobId) {
return [self resolve:blobId offset:offset size:size];
}
return nil;
}
- (void)remove:(NSString *)blobId
{
std::lock_guard<std::mutex> lock(_blobsMutex);
[_blobs removeObjectForKey:blobId];
}
RCT_EXPORT_METHOD(addNetworkingHandler)
{
RCTNetworking *const networking = [_moduleRegistry moduleForName:"Networking"];
// TODO(T63516227): Why can methodQueue be nil here?
// We don't want to do anything when methodQueue is nil.
if (!networking.methodQueue) {
return;
}
dispatch_async(networking.methodQueue, ^{
[networking addRequestHandler:self];
[networking addResponseHandler:self];
});
}
RCT_EXPORT_METHOD(addWebSocketHandler : (double)socketID)
{
dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{
[[self->_moduleRegistry moduleForName:"WebSocketModule"] setContentHandler:self
forSocketID:[NSNumber numberWithDouble:socketID]];
});
}
RCT_EXPORT_METHOD(removeWebSocketHandler : (double)socketID)
{
dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{
[[self->_moduleRegistry moduleForName:"WebSocketModule"] setContentHandler:nil
forSocketID:[NSNumber numberWithDouble:socketID]];
});
}
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
RCT_EXPORT_METHOD(sendOverSocket : (NSDictionary *)blob socketID : (double)socketID)
{
dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{
[[self->_moduleRegistry moduleForName:"WebSocketModule"] sendData:[self resolve:blob]
forSocketID:[NSNumber numberWithDouble:socketID]];
});
}
RCT_EXPORT_METHOD(createFromParts : (NSArray<NSDictionary<NSString *, id> *> *)parts withId : (NSString *)blobId)
{
NSMutableData *data = [NSMutableData new];
for (NSDictionary<NSString *, id> *part in parts) {
NSString *type = [RCTConvert NSString:part[@"type"]];
if ([type isEqualToString:@"blob"]) {
NSData *partData = [self resolve:part[@"data"]];
[data appendData:partData];
} else if ([type isEqualToString:@"string"]) {
NSData *partData = [[RCTConvert NSString:part[@"data"]] dataUsingEncoding:NSUTF8StringEncoding];
[data appendData:partData];
} else {
[NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type];
}
}
dispatch_async(_methodQueue, ^{
[self store:data withId:blobId];
});
}
RCT_EXPORT_METHOD(release : (NSString *)blobId)
{
dispatch_async(_methodQueue, ^{
[self remove:blobId];
});
}
#pragma mark - RCTURLRequestHandler methods
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:kBlobURIScheme] == NSOrderedSame;
}
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_queue) {
_queue = [NSOperationQueue new];
_queue.maxConcurrentOperationCount = 2;
}
__weak __typeof(self) weakSelf = self;
__weak __block NSBlockOperation *weakOp;
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
__typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:nil
expectedContentLength:-1
textEncodingName:nil];
[delegate URLRequest:weakOp didReceiveResponse:response];
NSData *data = [strongSelf resolveURL:response.URL];
NSError *error;
if (data) {
[delegate URLRequest:weakOp didReceiveData:data];
} else {
error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
}
[delegate URLRequest:weakOp didCompleteWithError:error];
}];
weakOp = op;
[_queue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
[op cancel];
}
#pragma mark - RCTNetworkingRequestHandler methods
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- (BOOL)canHandleNetworkingRequest:(NSDictionary *)data
{
return data[@"blob"] != nil;
}
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data
{
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
NSDictionary *blob = [RCTConvert NSDictionary:data[@"blob"]];
NSString *contentType = @"application/octet-stream";
NSString *blobType = [RCTConvert NSString:RCTNilIfNull(blob[@"type"])];
if (blobType != nil && blobType.length > 0) {
contentType = blob[@"type"];
}
return @{@"body" : [self resolve:blob], @"contentType" : contentType};
}
- (BOOL)canHandleNetworkingResponse:(NSString *)responseType
{
return [responseType isEqualToString:@"blob"];
}
- (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data
{
// An empty body will have nil for data, in this case we need to return
// an empty blob as per the XMLHttpRequest spec.
data = data ?: [NSData new];
return @{
@"blobId" : [self store:data],
@"offset" : @0,
@"size" : @(data.length),
@"name" : RCTNullIfNil([response suggestedFilename]),
@"type" : RCTNullIfNil([response MIMEType]),
};
}
#pragma mark - RCTWebSocketContentHandler methods
- (id)processWebsocketMessage:(id)message
forSocketID:(NSNumber *)socketID
withType:(NSString *__autoreleasing _Nonnull *)type
{
if (![message isKindOfClass:[NSData class]]) {
*type = @"text";
return message;
}
*type = @"blob";
return @{
@"blobId" : [self store:message],
@"offset" : @0,
@"size" : @(((NSData *)message).length),
};
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeBlobModuleSpecJSI>(params);
}
@end
Class RCTBlobManagerCls(void)
{
return RCTBlobManager.class;
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated by an internal plugin build system
*/
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
// FB Internal: FBRCTBlobPlugins.h is autogenerated by the build system.
#import <React/FBRCTBlobPlugins.h>
#else
// OSS-compatibility layer
#import <Foundation/Foundation.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#ifdef __cplusplus
extern "C" {
#endif
// RCTTurboModuleManagerDelegate should call this to resolve module classes.
Class RCTBlobClassProvider(const char *name);
// Lookup functions
Class RCTBlobManagerCls(void) __attribute__((used));
Class RCTFileReaderModuleCls(void) __attribute__((used));
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated by an internal plugin build system
*/
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
// OSS-compatibility layer
#import "RCTBlobPlugins.h"
#import <string>
#import <unordered_map>
Class RCTBlobClassProvider(const char *name) {
// Intentionally leak to avoid crashing after static destructors are run.
static const auto sCoreModuleClassMap = new const std::unordered_map<std::string, Class (*)(void)>{
{"BlobModule", RCTBlobManagerCls},
{"FileReaderModule", RCTFileReaderModuleCls},
};
auto p = sCoreModuleClassMap->find(name);
if (p != sCoreModuleClassMap->end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,12 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridgeModule.h>
@interface RCTFileReaderModule : NSObject <RCTBridgeModule>
@end

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTFileReaderModule.h>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTBlobManager.h>
#import "RCTBlobPlugins.h"
@interface RCTFileReaderModule () <NativeFileReaderModuleSpec>
@end
@implementation RCTFileReaderModule
RCT_EXPORT_MODULE(FileReaderModule)
@synthesize moduleRegistry = _moduleRegistry;
RCT_EXPORT_METHOD(
readAsText : (NSDictionary<NSString *, id> *)blob encoding : (NSString *)encoding resolve : (RCTPromiseResolveBlock)
resolve reject : (RCTPromiseRejectBlock)reject)
{
RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"];
dispatch_async(blobManager.methodQueue, ^{
NSData *data = [blobManager resolve:blob];
if (data == nil) {
reject(
RCTErrorUnspecified,
[NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]],
nil);
} else {
NSStringEncoding stringEncoding;
if (encoding == nil) {
stringEncoding = NSUTF8StringEncoding;
} else {
stringEncoding =
CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encoding));
}
NSString *text = [[NSString alloc] initWithData:data encoding:stringEncoding];
resolve(text);
}
});
}
RCT_EXPORT_METHOD(
readAsDataURL : (NSDictionary<NSString *, id> *)blob resolve : (RCTPromiseResolveBlock)
resolve reject : (RCTPromiseRejectBlock)reject)
{
RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"];
dispatch_async(blobManager.methodQueue, ^{
NSData *data = [blobManager resolve:blob];
if (data == nil) {
reject(
RCTErrorUnspecified,
[NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]],
nil);
} else {
NSString *type = [RCTConvert NSString:blob[@"type"]];
NSString *text = [NSString
stringWithFormat:@"data:%@;base64,%@",
![type isEqual:[NSNull null]] && [type length] > 0 ? type : @"application/octet-stream",
[data base64EncodedStringWithOptions:0]];
resolve(text);
}
});
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeFileReaderModuleSpecJSI>(params);
}
@end
Class RCTFileReaderModuleCls(void)
{
return RCTFileReaderModule.class;
}

View File

@@ -0,0 +1,59 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = [
"\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
]
Pod::Spec.new do |s|
s.name = "React-RCTBlob"
s.version = version
s.summary = "An API for displaying iOS action sheets and share sheets."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.compiler_flags = '-Wno-nullability-completeness'
s.source = source
s.source_files = podspec_sources("*.{h,m,mm}", "**/*.h")
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTBlob"
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')
}
s.dependency "React-jsi"
s.dependency "React-Core/RCTBlobHeaders"
s.dependency "React-Core/RCTWebSocket"
s.dependency "React-RCTNetwork"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-NativeModulesApple")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
if use_hermes()
s.dependency "hermes-engine"
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

198
node_modules/react-native/Libraries/Blob/URL.js generated vendored Normal file
View File

@@ -0,0 +1,198 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import type Blob from './Blob';
import NativeBlobModule from './NativeBlobModule';
let BLOB_URL_PREFIX = null;
if (
NativeBlobModule &&
typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string'
) {
const constants = NativeBlobModule.getConstants();
// $FlowFixMe[incompatible-type] asserted above
// $FlowFixMe[unsafe-addition]
BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':';
if (typeof constants.BLOB_URI_HOST === 'string') {
BLOB_URL_PREFIX += `//${constants.BLOB_URI_HOST}/`;
}
}
/*
* To allow Blobs be accessed via `content://` URIs,
* you need to register `BlobProvider` as a ContentProvider in your app's `AndroidManifest.xml`:
*
* ```xml
* <manifest>
* <application>
* <provider
* android:name="com.facebook.react.modules.blob.BlobProvider"
* android:authorities="@string/blob_provider_authority"
* android:exported="false"
* />
* </application>
* </manifest>
* ```
* And then define the `blob_provider_authority` string in `res/values/strings.xml`.
* Use a dotted name that's entirely unique to your app:
*
* ```xml
* <resources>
* <string name="blob_provider_authority">your.app.package.blobs</string>
* </resources>
* ```
*/
export {URLSearchParams} from './URLSearchParams';
function validateBaseUrl(url: string) {
// from this MIT-licensed gist: https://gist.github.com/dperini/729294
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(
url,
);
}
export class URL {
_url: string;
_searchParamsInstance: ?URLSearchParams = null;
static createObjectURL(blob: Blob): string {
if (BLOB_URL_PREFIX === null) {
throw new Error('Cannot create URL for blob!');
}
return `${BLOB_URL_PREFIX}${blob.data.blobId}?offset=${blob.data.offset}&size=${blob.size}`;
}
static revokeObjectURL(url: string) {
// Do nothing.
}
// $FlowFixMe[missing-local-annot]
constructor(url: string, base?: string | URL) {
let baseUrl = null;
if (!base || validateBaseUrl(url)) {
this._url = url;
if (this._url.includes('#')) {
const split = this._url.split('#');
const beforeHash = split[0];
const website = beforeHash.split('://')[1];
if (!website.includes('/')) {
this._url = split.join('/#');
}
}
if (
!this._url.endsWith('/') &&
!(this._url.includes('?') || this._url.includes('#'))
) {
this._url += '/';
}
} else {
if (typeof base === 'string') {
baseUrl = base;
if (!validateBaseUrl(baseUrl)) {
throw new TypeError(`Invalid base URL: ${baseUrl}`);
}
} else {
baseUrl = base.toString();
}
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
}
if (!url.startsWith('/')) {
url = `/${url}`;
}
if (baseUrl.endsWith(url)) {
url = '';
}
this._url = `${baseUrl}${url}`;
}
}
get hash(): string {
const hashMatch = this._url.match(/#([^/]*)/);
return hashMatch ? `#${hashMatch[1]}` : '';
}
get host(): string {
const hostMatch = this._url.match(/^https?:\/\/(?:[^@]+@)?([^:/?#]+)/);
const portMatch = this._url.match(/:(\d+)(?=[/?#]|$)/);
return hostMatch
? hostMatch[1] + (portMatch ? `:${portMatch[1]}` : '')
: '';
}
get hostname(): string {
const hostnameMatch = this._url.match(/^https?:\/\/(?:[^@]+@)?([^:/?#]+)/);
return hostnameMatch ? hostnameMatch[1] : '';
}
get href(): string {
return this.toString();
}
get origin(): string {
const matches = this._url.match(/^(https?:\/\/[^/]+)/);
return matches ? matches[1] : '';
}
get password(): string {
const passwordMatch = this._url.match(/https?:\/\/.*:(.*)@/);
return passwordMatch ? passwordMatch[1] : '';
}
get pathname(): string {
const pathMatch = this._url.match(/https?:\/\/[^/]+(\/[^?#]*)?/);
return pathMatch ? pathMatch[1] || '/' : '/';
}
get port(): string {
const portMatch = this._url.match(/:(\d+)(?=[/?#]|$)/);
return portMatch ? portMatch[1] : '';
}
get protocol(): string {
const protocolMatch = this._url.match(/^([a-zA-Z][a-zA-Z\d+\-.]*):/);
return protocolMatch ? protocolMatch[1] + ':' : '';
}
get search(): string {
const searchMatch = this._url.match(/\?([^#]*)/);
return searchMatch ? `?${searchMatch[1]}` : '';
}
get searchParams(): URLSearchParams {
if (this._searchParamsInstance == null) {
this._searchParamsInstance = new URLSearchParams(this.search);
}
return this._searchParamsInstance;
}
toJSON(): string {
return this.toString();
}
toString(): string {
if (this._searchParamsInstance === null) {
return this._url;
}
// $FlowFixMe[incompatible-use]
const instanceString = this._searchParamsInstance.toString();
const separator = this._url.indexOf('?') > -1 ? '&' : '?';
return this._url + separator + instanceString;
}
get username(): string {
const usernameMatch = this._url.match(/^https?:\/\/([^:@]+)(?::[^@]*)?@/);
return usernameMatch ? usernameMatch[1] : '';
}
}

View File

@@ -0,0 +1,151 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
// Small subset from whatwg-url: https://github.com/jsdom/whatwg-url/tree/master/src
// The reference code bloat comes from Unicode issues with URLs, so those won't work here.
export class URLSearchParams {
_searchParams: Map<string, string[]> = new Map();
get size(): number {
return this._searchParams.size;
}
constructor(params?: Record<string, string> | string | [string, string][]) {
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
if (params === null) {
return;
}
if (typeof params === 'string') {
// URLSearchParams("key1=value1&key2=value2");
params
.replace(/^\?/, '')
.split('&')
.forEach(pair => {
if (!pair) {
return;
}
const [key, value] = pair
.split('=')
.map(part => decodeURIComponent(part.replace(/\+/g, ' ')));
this.append(key, value);
});
} else if (Array.isArray(params)) {
//URLSearchParams([["key1", "value1"], ["key2", "value2"]]);
params.forEach(([key, value]) => this.append(key, value));
} else if (typeof params === 'object') {
//URLSearchParams({ key1: "value1", key2: "value2" });
Object.entries(params).forEach(([key, value]) => this.append(key, value));
}
}
append(key: string, value: string): void {
if (!this._searchParams.has(key)) {
this._searchParams.set(key, [value]); // Initialize with an array if key is missing
} else {
this._searchParams.get(key)?.push(value); // Else push the value to the array
}
}
delete(name: string): void {
this._searchParams.delete(name);
}
get(name: string): string | null {
const values = this._searchParams.get(name);
return values ? values[0] : null;
}
getAll(name: string): string[] {
return this._searchParams.get(name) ?? [];
}
has(name: string): boolean {
return this._searchParams.has(name);
}
set(name: string, value: string): void {
this._searchParams.set(name, [value]);
}
keys(): Iterator<string> {
return this._searchParams.keys();
}
values(): Iterator<string> {
function* generateValues(params: Map<string, string[]>): Iterator<string> {
for (const valueArray of params.values()) {
for (const value of valueArray) {
yield value;
}
}
}
return generateValues(this._searchParams);
}
entries(): Iterator<[string, string]> {
function* generateEntries(
params: Map<string, string[]>,
): Iterator<[string, string]> {
for (const [key, values] of params) {
for (const value of values) {
yield [key, value];
}
}
}
return generateEntries(this._searchParams);
}
forEach(
callback: (value: string, key: string, searchParams: this) => void,
): void {
for (const [key, values] of this._searchParams) {
for (const value of values) {
callback(value, key, this);
}
}
}
sort(): void {
this._searchParams = new Map(
[...this._searchParams.entries()].sort(([a], [b]) => a.localeCompare(b)),
);
}
// $FlowFixMe[unsupported-syntax]
[Symbol.iterator](): Iterator<[string, string]> {
const entries: [string, string][] = [];
for (const [key, values] of this._searchParams) {
for (const value of values) {
entries.push([key, value]);
}
}
return entries[Symbol.iterator]();
}
toString(): string {
return Array.from(this._searchParams.entries())
.map(([key, values]) =>
values
.map(
value =>
`${encodeURIComponent(key).replace(/%20/g, '+')}=${encodeURIComponent(
value,
).replace(/%20/g, '+')}`, // Convert only spaces to '+'
)
.join('&'),
)
.join('&');
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
declare export class URLSearchParams {
_searchParams: Array<[string, string]>;
+size: number;
constructor(
params?: Record<string, string> | string | Array<[string, string]>,
): void;
append(key: string, value: string): void;
delete(name: string): void;
get(name: string): string;
getAll(name: string): Array<string>;
has(name: string): boolean;
set(name: string, value: string): void;
sort(): void;
@@iterator(): Iterator<[string, string]>;
toString(): string;
keys(): Iterator<string>;
values(): Iterator<string>;
entries(): Iterator<[string, string]>;
}