Skip to content

Commit

Permalink
update README
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeymild committed Nov 17, 2023
1 parent 24720ee commit 2381ac2
Show file tree
Hide file tree
Showing 12 changed files with 537 additions and 61 deletions.
191 changes: 180 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,194 @@
# react-native-jsi-cpr

R
React Native JSI wrapper for [cpr](https://github.com/libcpr/cpr) curl HTTP client

###Features
- High performance because everything is written in C++ (even the JS functions have C++ bodies!)
- iOS, Android support

## Installation

```sh
npm install react-native-jsi-cpr
#add to package.json
"react-native-jsi-cpr":"sergeymild/react-native-jsi-cpr#1.5.0"
# after that make yarn install
# and npx pod-install
```

### Import
```typescript
import { JsiHttp } from 'react-native-jsi-cpr';
```

## Usage
### Usage
```typescript
//create instance
const http = new JsiHttp({
baseUrl: 'url',
timeout: 1000,
}, /*isDebug*/ true)

```js
import { multiply } from 'react-native-jsi-cpr';
//simple get request
const getResponse = await http.get<{id: string; name: string}>('/user/{id}', {
params: {id: 20},
timeout: 500,
})
if (getResponse.type === 'error') {
console.log(getResponse.error)
} else {
console.log(getResponse.data)
}

// ...
//simple post request, will never throw any errors
const postResponse = await http.post<any>('/post', {
// also may be 'string' | 'json' | 'formUrlEncoded' | 'formData'
json: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
if (postResponse.type === 'error') {
console.log(postResponse.error)
} else {
console.log(postResponse.data)
}

const result = await multiply(3, 7);
//cancel request will throw expception with error status code 80
http.get<any>('/post', {
requestId: 'uniqueRequestId'
})
http.cancelRequest('uniqueRequestId')
```

### Default config passed on initialization
```typescript
{
baseURL: 'https://api.example.com',
// `headers` are base headers will be passed to each request
headers: {},
paramsSerializer?: ParamsSerializer;
errorInterceptor?: (request: JsiError) => Promise<JsiError>;
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
timeout: 1000, // default is `0` (no timeout)
timeout?: number;
logRequest?: LogRequest;
logResponse?: LogResponse;
logErrorResponse?: LogError;
skipResponseHeaders?: boolean;
requestInterceptor?: RequestInterceptor;
}
```

### Request Config
```typescript

// Base request

{
// `url` is the server URL that will be used for the request
url: '/user/{id}';
// `queries` are the URL query parameters to be sent with the request
queries?: {orderBy: 'id'};
// `params` params which will replace placeholders in url
params?: {id: 12345};
// `requestId` if not set will be generate automatically, request maybe cancelled with with id
requestId?: '345';
// `headers` are custom headers to be sent
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `paramsSerializer` is an optional function in charge of serializing `params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: (queries) => QS.stringify(queries, {arrayFormat: 'brackets'}),
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
timeout: 1000, // default is `0` (no timeout)
// `baseURL` will be prepended to `url` unless `url` is absolute.
// It can be convenient to set `baseURL` for an instance of axios to pass relative URLs
// to methods of that instance.
baseURL: 'https://some-domain.com/api',
errorInterceptor: (error) => error
}

// Only applicable for request methods 'PUT', 'POST', 'DELETE' and 'PATCH'
{
// syntax for `json`
json: {
firstName: 'Fred'
}

// syntax for `string`
string: 'Fred'

// syntax for `formUrlEncoded`
formUrlEncoded: {
firstName: 'Fred'
}

// syntax for `formData`
formData: [
// for file
{name: 'fileName', path: 'absoluteFilePath'},
{name: 'fileName2', path: 'absoluteFilePath'},
// for other parameters
{name: 'fileName', value: 'value'}
]
}

```

### Response Schema
```typescript
interface CommonResponse {
// time which took for request
readonly elapsed: number;
// if skipResponseHeaders passed in defaultConfig is true
// `headers` the HTTP headers that the server responded with
// All header names are lower cased and can be accessed using the bracket notation.
// Example: `response.headers['content-type']`
readonly headers?: Record<string, string>;
// request id
readonly requestId: string;
// full endpoint where request was made
readonly endpoint: string;
// `status` is the HTTP status code from the server response
readonly status: number;
}

export interface JsiError extends CommonResponse {
readonly type: 'error';
// `error` is the response that was provided by the server
error: string;
// `data` is the response that was provided by the server on error
readonly data?: string;
}

export interface JsiSuccess<T> extends CommonResponse {
readonly type: 'success';
// `data` is the response that was provided by the server
readonly data: T;
}

export type JsiResponse<T> = JsiError | JsiSuccess<T>;
```

## Tips
1. Depending Another Android Library Containing libcrypto.so, libssl.so.
`react-native-jsi-cpr` packages specified in Android C++ include `libcrypto.so, libssl.so` libraries. If a second library which also includes `libcrypto.so, libssl.so` is added as a dependency, gradle fails with More than one file was found with OS independent path `lib/x86/libcrypto.so` or `lib/x86/libssl.so` error message.

You can fix this error by adding the following block into your build.gradle.
```
android {
packagingOptions {
pickFirst 'lib/x86/libcrypto.so'
pickFirst 'lib/x86/libssl.so'
pickFirst 'lib/x86_64/libcrypto.so'
pickFirst 'lib/x86_64/libssl.so'
pickFirst 'lib/armeabi-v7a/libcrypto.so'
pickFirst 'lib/armeabi-v7a/libssl.so'
pickFirst 'lib/arm64-v8a/libcrypto.so'
pickFirst 'lib/arm64-v8a/libssl.so'
}
}
```

## Contributing
Expand All @@ -25,7 +198,3 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the
## License

MIT

---

Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
64 changes: 56 additions & 8 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,63 @@
cmake_minimum_required(VERSION 3.4.1)
cmake_minimum_required(VERSION 3.10)

set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_FLAGS "-DFOLLY_NO_CONFIG=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_HAVE_MEMRCHR=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_MOBILE=1 -DON_ANDROID -DONANDROID")

add_library(cpp
SHARED
../cpp/react-native-jsi-cpr.cpp
cpp-adapter.cpp
set (PACKAGE_NAME "react-native-jsi-cpr")
set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
set (CPPHTTPLIB_ZLIB_SUPPORT 0)

# Consume shared libraries and headers from prefabs
find_package(fbjni REQUIRED CONFIG)
find_package(ReactAndroid REQUIRED CONFIG)


file(GLOB cpr_library "../cpp/cpr/*.cpp")

add_library(
${PACKAGE_NAME}
SHARED
src/main/cpp/HttpHelper.cpp

../cpp/JsiHttp.cpp
../cpp/JsiUtils.cpp
../cpp/Logger.cpp

${cpr_library}
)


target_include_directories(
${PACKAGE_NAME}
PRIVATE
"${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni/react/turbomodule"
"${REACT_NATIVE_DIR}/ReactCommon"
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
"src/main/cpp"
"../cpp"
)

# Specifies a path to native header files.
include_directories(
../cpp
"../cpp/include"
)


find_library(
LOG_LIB
log
)

find_library(z-lib z)
find_package(curl REQUIRED CONFIG)

target_link_libraries(
${PACKAGE_NAME}
${LOG_LIB}
ReactAndroid::jsi
ReactAndroid::reactnativejni
fbjni::fbjni
curl::curl
${z-lib}
android
)
70 changes: 54 additions & 16 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
}

dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
classpath "com.android.tools.build:gradle:+"
}
}

Expand All @@ -27,26 +27,42 @@ def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["JsiCpr_" + name]).toInteger()
}

def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

// Namespace support was added in 7.3.0
return (major == 7 && minor >= 3) || major >= 8
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "arm64-v8a"]
}

android {
if (supportsNamespace()) {
namespace "com.jsicpr"
def resolveReactNativeDirectory() {
def reactNativeLocation = safeExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
if (reactNativeLocation != null) {
return file(reactNativeLocation)
}

sourceSets {
main {
manifest.srcFile "src/main/AndroidManifestNew.xml"
}
}
// monorepo workaround
// react-native can be hoisted or in project's own node_modules
def reactNativeFromProjectNodeModules = file("${rootProject.projectDir}/../node_modules/react-native")
if (reactNativeFromProjectNodeModules.exists()) {
return reactNativeFromProjectNodeModules
}

throw new GradleException(
"[Reanimated] Unable to resolve react-native location in " +
"node_modules. You should project extension property (in app/build.gradle) " +
"`REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
)
}

def reactNativeRootDir = resolveReactNativeDirectory()

android {
buildFeatures {
prefab true
}
namespace "com.jsicpr"
ndkVersion getExtOrDefault("ndkVersion")
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")

Expand All @@ -57,9 +73,29 @@ android {
externalNativeBuild {
cmake {
cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
abiFilters "armeabi-v7a", "arm64-v8a"
arguments '-DANDROID_STL=c++_shared',
"-DREACT_NATIVE_DIR=${reactNativeRootDir.path}"
}
}
ndk {
abiFilters (*reactNativeArchitectures())
}
}

packagingOptions {
// Exclude all Libraries that are already present in the user's app (through React Native or by him installing REA)
excludes = [
"lib/arm64-v8a/libcrypto.so",
"lib/arm64-v8a/libjsi.so",
"lib/arm64-v8a/libreactnativejni.so",
"lib/arm64-v8a/libssl.so"
]
}

configurations {
extractHeaders
extractJNI
}

externalNativeBuild {
Expand Down Expand Up @@ -95,5 +131,7 @@ dependencies {
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation 'com.android.ndk.thirdparty:curl:+'
implementation 'com.android.ndk.thirdparty:openssl:+'
}

Loading

0 comments on commit 2381ac2

Please sign in to comment.