Skip to content

Commit 2226f51

Browse files
christophpurrermeta-codesync[bot]
authored andcommitted
Add ArrayBuffer sample methods to TurboModule examples (facebook#56690)
Summary: Pull Request resolved: facebook#56690 ## Changelog: [General] [Added] - Add ArrayBuffer sample methods to TurboModule examples react-native-community/discussions-and-proposals#947 Add `getArrayBuffer` method to NativeCxxModuleExample and NativeSampleTurboModule specs to demonstrate and validate ArrayBuffer support across all platforms. - **JS Specs:** Added `getArrayBuffer: (arg: ArrayBuffer) => ArrayBuffer` to both NativeCxxModuleExample.js and NativeSampleTurboModule.js - **C++:** Implemented in NativeCxxModuleExample.cpp using `OwnedMutableBuffer` to copy and return the ArrayBuffer - **Java/Kotlin:** Implemented in SampleTurboModule.kt with `ByteBuffer` param/return - **ObjC:** Implemented in RCTSampleTurboModule.mm (iOS + macOS) with `NSData *` param/return Reviewed By: javache Differential Revision: D95652569
1 parent fb155df commit 2226f51

14 files changed

Lines changed: 160 additions & 1 deletion

File tree

packages/react-native/React/Base/RCTConvert.mm

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,24 @@ +(type *)type : (id)json \
6060
RCT_JSON_CONVERTER(NSNumber)
6161

6262
RCT_CUSTOM_CONVERTER(NSSet *, NSSet, [NSSet setWithArray:json])
63-
RCT_CUSTOM_CONVERTER(NSData *, NSData, [json dataUsingEncoding:NSUTF8StringEncoding])
63+
64+
+ (NSData *)NSData:(id)json RCT_DYNAMIC
65+
{
66+
// Return NSData as-is (e.g., when bridging ArrayBuffer from JS).
67+
if ([json isKindOfClass:[NSData class]]) {
68+
return json;
69+
}
70+
if (!RCT_DEBUG) {
71+
return [json dataUsingEncoding:NSUTF8StringEncoding];
72+
} else {
73+
@try {
74+
return [json dataUsingEncoding:NSUTF8StringEncoding];
75+
} @catch (__unused NSException *e) {
76+
RCTLogConvertError(json, @"NSData");
77+
return nil;
78+
}
79+
}
80+
}
6481

6582
+ (NSIndexSet *)NSIndexSet:(id)json
6683
{

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.facebook.react.bridge.WritableMap;
2222
import com.facebook.react.common.build.ReactBuildConfig;
2323
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
24+
import java.nio.ByteBuffer;
2425
import java.util.Arrays;
2526
import java.util.HashSet;
2627
import java.util.Map;
@@ -165,4 +166,10 @@ public void promiseAssert(Promise promise) {}
165166
@ReactMethod
166167
@DoNotStrip
167168
public void getImageUrl(Promise promise) {}
169+
170+
@ReactMethod(isBlockingSynchronousMethod = true)
171+
@DoNotStrip
172+
public ByteBuffer getArrayBuffer(ByteBuffer arg) {
173+
return null;
174+
}
168175
}

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,24 @@ __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl(
328328
cachedMethodId);
329329
}
330330

331+
static facebook::jsi::Value
332+
__hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer(
333+
facebook::jsi::Runtime& rt,
334+
TurboModule& turboModule,
335+
const facebook::jsi::Value* args,
336+
size_t count) {
337+
static jmethodID cachedMethodId = nullptr;
338+
return static_cast<JavaTurboModule&>(turboModule)
339+
.invokeJavaMethod(
340+
rt,
341+
ArrayBufferKind,
342+
"getArrayBuffer",
343+
"(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;",
344+
args,
345+
count,
346+
cachedMethodId);
347+
}
348+
331349
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
332350
const JavaTurboModule::InitParams& params)
333351
: JavaTurboModule(params) {
@@ -393,6 +411,9 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
393411
methodMap_["getImageUrl"] = MethodMetadata{
394412
.argCount = 0,
395413
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl};
414+
methodMap_["getArrayBuffer"] = MethodMetadata{
415+
.argCount = 1,
416+
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer};
396417
eventEmitterMap_["onPress"] =
397418
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
398419
eventEmitterMap_["onClick"] =

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.facebook.react.bridge.WritableNativeMap
2727
import com.facebook.react.module.annotations.ReactModule
2828
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
2929
import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
30+
import java.nio.ByteBuffer
3031
import java.util.UUID
3132

3233
@DoNotStrip
@@ -225,6 +226,20 @@ public class SampleTurboModule(private val context: ReactApplicationContext) :
225226
assert(false) { "Intentional assert from JVM promiseAssert" }
226227
}
227228

229+
@DoNotStrip
230+
@Suppress("unused")
231+
override fun getArrayBuffer(arg: ByteBuffer): ByteBuffer {
232+
// Reverse the bytes in place, mutating the JS-owned ByteBuffer directly.
233+
val length = arg.capacity()
234+
for (i in 0 until length / 2) {
235+
val tmp = arg.get(i)
236+
arg.put(i, arg.get(length - 1 - i))
237+
arg.put(length - 1 - i, tmp)
238+
}
239+
log("getArrayBuffer", arg, arg)
240+
return arg
241+
}
242+
228243
@DoNotStrip
229244
@Suppress("unused")
230245
override fun getImageUrl(promise: Promise) {

packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ inline JS::NativeSampleTurboModule::Constants::Builder::Builder(Constants i)
104104
- (NSDictionary *)getObjectAssert:(NSDictionary *)arg;
105105
- (void)promiseAssert:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
106106
- (void)getImageUrl:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
107+
- (NSMutableData *)getArrayBuffer:(NSMutableData *)arg;
107108
- (facebook::react::ModuleConstants<JS::NativeSampleTurboModule::Constants>)constantsToExport;
108109
- (facebook::react::ModuleConstants<JS::NativeSampleTurboModule::Constants>)getConstants;
109110

packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,16 @@ - (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallb
224224
.invokeObjCMethod(rt, PromiseKind, "getImageUrl", @selector(getImageUrl:reject:), args, count);
225225
}
226226

227+
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer(
228+
facebook::jsi::Runtime &rt,
229+
TurboModule &turboModule,
230+
const facebook::jsi::Value *args,
231+
size_t count)
232+
{
233+
return static_cast<ObjCTurboModule &>(turboModule)
234+
.invokeObjCMethod(rt, ArrayBufferKind, "getArrayBuffer", @selector(getArrayBuffer:), args, count);
235+
}
236+
227237
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
228238
facebook::jsi::Runtime &rt,
229239
TurboModule &turboModule,
@@ -277,6 +287,9 @@ - (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallb
277287

278288
methodMap_["getImageUrl"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl};
279289

290+
methodMap_["getArrayBuffer"] =
291+
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer};
292+
280293
methodMap_["getConstants"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
281294

282295
eventEmitterMap_["onPress"] = std::make_shared<AsyncEventEmitter<id>>();

packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#import <React/RCTUtils.h>
1414
#import <ReactCommon/RCTTurboModuleWithJSIBindings.h>
1515
#import <UIKit/UIKit.h>
16+
#import <algorithm>
17+
#import <span>
1618

1719
using namespace facebook::react;
1820

@@ -210,6 +212,16 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime
210212
RCTAssert(false, @"Intentional assert from ObjC promiseAssert");
211213
}
212214

215+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSMutableData *, getArrayBuffer : (NSMutableData *)arg)
216+
{
217+
// Reverse the bytes in place. The NSMutableData is backed by JS-owned memory
218+
// wrapped via [NSMutableData dataWithBytesNoCopy:freeWhenDone:NO] for sync
219+
// calls, so the underlying buffer is mutable.
220+
std::span<uint8_t> bytes(static_cast<uint8_t *>(arg.mutableBytes), arg.length);
221+
std::reverse(bytes.begin(), bytes.end());
222+
return arg;
223+
}
224+
213225
@end
214226

215227
Class _Nonnull RCTSampleTurboModuleCls(void)

packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface Spec extends TurboModule {
4747
+getNumber: (arg: number) => number;
4848
+getString: (arg: string) => string;
4949
+getArray: (arg: Array<any>) => Array<any>;
50+
+getArrayBuffer: (arg: ArrayBuffer) => ArrayBuffer;
5051
+getObject: (arg: Object) => Object;
5152
+getUnsafeObject: (arg: UnsafeObject) => UnsafeObject;
5253
+getRootTag: (arg: RootTag) => RootTag;

packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
*/
77

88
#include "NativeCxxModuleExample.h"
9+
#include <react/bridging/ArrayBuffer.h>
910
#include <react/debug/react_native_assert.h>
11+
#include <cstring>
1012
#include <iomanip>
1113
#include <ostream>
1214
#include <sstream>
@@ -265,4 +267,17 @@ AsyncPromise<> NativeCxxModuleExample::promiseAssert(jsi::Runtime& rt) {
265267
return promise;
266268
};
267269

270+
jsi::ArrayBuffer NativeCxxModuleExample::getArrayBuffer(
271+
jsi::Runtime& rt,
272+
jsi::ArrayBuffer arg) {
273+
// Reverse the bytes of the input ArrayBuffer in place, mutating the
274+
// JS-owned memory directly.
275+
auto* data = arg.data(rt);
276+
auto size = arg.size(rt);
277+
for (size_t i = 0; i < size / 2; ++i) {
278+
std::swap(data[i], data[size - 1 - i]);
279+
}
280+
return arg;
281+
}
282+
268283
} // namespace facebook::react

packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ class NativeCxxModuleExample : public NativeCxxModuleExampleCxxSpec<NativeCxxMod
182182

183183
AsyncPromise<> promiseAssert(jsi::Runtime &rt);
184184

185+
jsi::ArrayBuffer getArrayBuffer(jsi::Runtime &rt, jsi::ArrayBuffer arg);
186+
185187
private:
186188
std::optional<AsyncCallback<std::string>> valueCallback_;
187189
};

0 commit comments

Comments
 (0)