├── .gitignore
├── .gitmodules
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── example
├── .bundle
│ └── config
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── .watchmanconfig
├── App.tsx
├── Gemfile
├── README.md
├── android
│ ├── app
│ │ ├── build.gradle
│ │ ├── debug.keystore
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ │ ├── debug
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── ReactNativeFlipper.java
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ ├── ExampleJsiModule.java
│ │ │ │ │ ├── ExampleJsiPackage.java
│ │ │ │ │ ├── MainActivity.java
│ │ │ │ │ └── MainApplication.java
│ │ │ └── res
│ │ │ │ ├── drawable
│ │ │ │ └── rn_edit_text_material.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ └── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ └── release
│ │ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── ReactNativeFlipper.java
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── app.json
├── babel.config.js
├── example-jsi-module
│ ├── Cargo.lock
│ ├── Cargo.toml
│ └── src
│ │ ├── android.rs
│ │ ├── ios.rs
│ │ └── lib.rs
├── index.js
├── ios
│ ├── .xcode.env
│ ├── Podfile
│ ├── example.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── example.xcscheme
│ ├── example
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.mm
│ │ ├── ExampleJsiModule.h
│ │ ├── Images.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ ├── LaunchScreen.storyboard
│ │ ├── RCTExampleJsiModule.h
│ │ ├── RTCExampleJsiModule.mm
│ │ └── main.m
│ └── exampleTests
│ │ ├── Info.plist
│ │ └── exampleTests.m
├── jest.config.js
├── metro.config.js
├── package.json
├── tsconfig.json
└── yarn.lock
├── jsi-macros
├── Cargo.lock
├── Cargo.toml
└── src
│ ├── host_event.rs
│ ├── host_object.rs
│ └── lib.rs
├── jsi-sys
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── include
│ ├── host.h
│ └── wrapper.h
├── src
│ ├── ffi
│ │ ├── android.rs
│ │ ├── base.rs
│ │ ├── host.rs
│ │ └── mod.rs
│ ├── lib.rs
│ └── shim.rs
└── vendor
├── jsi-tests
├── Cargo.lock
├── Cargo.toml
├── README.md
├── build.rs
├── include
│ └── helper.h
├── src
│ ├── ffi.rs
│ └── lib.rs
└── tests
│ ├── basic.rs
│ ├── common.rs
│ └── function.rs
├── jsi
├── Cargo.lock
├── Cargo.toml
├── README.md
└── src
│ ├── array.rs
│ ├── array_buffer.rs
│ ├── call_invoker.rs
│ ├── convert
│ ├── de.rs
│ ├── mod.rs
│ └── ser.rs
│ ├── function.rs
│ ├── host_function.rs
│ ├── host_object.rs
│ ├── lib.rs
│ ├── object.rs
│ ├── prop_name.rs
│ ├── runtime.rs
│ ├── string.rs
│ ├── symbol.rs
│ └── value.rs
└── vendor
└── build-hermes.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | **/target
2 | **/lib
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "react-native"]
2 | path = vendor/react-native
3 | url = https://github.com/facebook/react-native.git
4 | ignore = dirty
5 | [submodule "fbjni"]
6 | path = vendor/fbjni
7 | url = https://github.com/facebookincubator/fbjni.git
8 | ignore = dirty
9 | [submodule "hermes"]
10 | path = vendor/hermes
11 | url = https://github.com/facebook/hermes.git
12 | ignore = dirty
13 |
14 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "jsi-sys",
4 | "jsi-macros",
5 | "jsi-tests",
6 | "jsi",
7 | "example/example-jsi-module"
8 | ]
9 | resolver = "2"
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Ibiyemi Abiodun
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `jsi-rs`
2 |
3 | This library makes it possible to write React Native JSI modules in Rust.
4 |
5 | For an example, check out the `example` folder.
6 |
7 | ## Getting Started
8 |
9 | 1. Clone this repo
10 | 2. Run `git submodule init`
11 | 3. Run `git submodule update`, this will ensure that all the vendor dependencies are cloned locally
12 | 4. Make sure you have Ninja installed locally, which is necessary for building Hermes. You can find instructions [here](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages). On macOS, you can install it with `brew install ninja`
13 | 5. Install dependencies for the example app: `cd example && yarn install`
14 | 6. Run the example app on android with `yarn android`
15 |
16 | > NOTE: Make sure that you have not installed rust with homebrew on mac, use the `rustup` toolchain instead.
17 |
18 | ## Contributing
19 |
20 | I wrote this code in winter 2022 as part of another project. A few months later,
21 | I have decided to release it to the world. However, I'm not planning to maintain
22 | it unless I encounter another project that requires it, so for now, the code is
23 | given to you as-is. Feel free to contribute PRs that would improve the API or
24 | stability of the library.
25 |
26 | ## Safety
27 |
28 | Right now, this library is quite `unsafe`.
29 |
30 | ## Copyright / license
31 |
32 | Copyright Ibiyemi Abiodun. MIT License.
33 |
--------------------------------------------------------------------------------
/example/.bundle/config:
--------------------------------------------------------------------------------
1 | BUNDLE_PATH: "vendor/bundle"
2 | BUNDLE_FORCE_RUBY_PLATFORM: 1
3 |
--------------------------------------------------------------------------------
/example/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: '@react-native',
4 | };
5 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | ios/.xcode.env.local
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 | *.hprof
33 | .cxx/
34 | *.keystore
35 | !debug.keystore
36 |
37 | # node.js
38 | #
39 | node_modules/
40 | npm-debug.log
41 | yarn-error.log
42 |
43 | # fastlane
44 | #
45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
46 | # screenshots whenever they are needed.
47 | # For more information about the recommended setup visit:
48 | # https://docs.fastlane.tools/best-practices/source-control/
49 |
50 | **/fastlane/report.xml
51 | **/fastlane/Preview.html
52 | **/fastlane/screenshots
53 | **/fastlane/test_output
54 |
55 | # Bundle artifact
56 | *.jsbundle
57 |
58 | # Ruby / CocoaPods
59 | /ios/Pods/
60 | /vendor/bundle/
61 |
62 | # Temporary files created by Metro to check the health of the file watcher
63 | .metro-health-check*
64 |
65 | # testing
66 | /coverage
67 |
--------------------------------------------------------------------------------
/example/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'avoid',
3 | bracketSameLine: true,
4 | bracketSpacing: false,
5 | singleQuote: true,
6 | trailingComma: 'all',
7 | };
8 |
--------------------------------------------------------------------------------
/example/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/example/App.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Sample React Native App
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | import React from 'react';
9 | import type {PropsWithChildren} from 'react';
10 | import {
11 | SafeAreaView,
12 | ScrollView,
13 | StatusBar,
14 | StyleSheet,
15 | Text,
16 | useColorScheme,
17 | View,
18 | NativeModules,
19 | } from 'react-native';
20 |
21 | import {
22 | Colors,
23 | DebugInstructions,
24 | Header,
25 | LearnMoreLinks,
26 | ReloadInstructions,
27 | } from 'react-native/Libraries/NewAppScreen';
28 |
29 | // call our Rust module
30 | const {ExampleJsiModule} = NativeModules;
31 | ExampleJsiModule.install();
32 |
33 | // call our global object injected by Rust
34 | console.log('the current time is: ' + ExampleGlobal.time());
35 |
36 | type SectionProps = PropsWithChildren<{
37 | title: string;
38 | }>;
39 |
40 | function Section({children, title}: SectionProps): JSX.Element {
41 | const isDarkMode = useColorScheme() === 'dark';
42 | return (
43 |
44 |
51 | {title}
52 |
53 |
60 | {children}
61 |
62 |
63 | );
64 | }
65 |
66 | function App(): JSX.Element {
67 | const isDarkMode = useColorScheme() === 'dark';
68 |
69 | const backgroundStyle = {
70 | backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
71 | };
72 |
73 | return (
74 |
75 |
79 |
82 |
83 |
87 |
88 | Edit App.tsx to change this
89 | screen and then come back to see your edits.
90 |
91 |
94 |
97 |
98 | Read the docs to discover what to do next:
99 |
100 |
101 |
102 |
103 |
104 | );
105 | }
106 |
107 | const styles = StyleSheet.create({
108 | sectionContainer: {
109 | marginTop: 32,
110 | paddingHorizontal: 24,
111 | },
112 | sectionTitle: {
113 | fontSize: 24,
114 | fontWeight: '600',
115 | },
116 | sectionDescription: {
117 | marginTop: 8,
118 | fontSize: 18,
119 | fontWeight: '400',
120 | },
121 | highlight: {
122 | fontWeight: '700',
123 | },
124 | });
125 |
126 | export default App;
127 |
--------------------------------------------------------------------------------
/example/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | gem 'cocoapods', '~> 1.12'
7 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # An example React Native project using a `jsi-rs` module
2 |
3 | ## How to reproduce (Android)
4 |
5 | 1. Create a blank React Native project: `npx react-native@latest init example`
6 | 2. Create a blank Cargo project inside: `cd example && cargo init --lib example-jsi-module`
7 | 3. Add `jsi`, `jni`, and `cxx` as dependencies of `example-jsi-module`
8 | - `jsi` will allow us to create a React Native module
9 | - `jni` is needed for interop with Java code on Android
10 | - `cxx` is needed to initialize `jsi` because JSI is implemented in C++ with smart pointers
11 | 4. Write the code in `example-jsi-module/src`
12 | 5. Update `android/build.gradle` to add the following plugin under `buildscript.dependencies`:
13 | ```groovy
14 | buildscript {
15 | repositories {
16 | // don't remove the existing repositories, just add maven b/c the Rust plugin is hosted there
17 | maven {
18 | url "https://plugins.gradle.org/m2/"
19 | }
20 | }
21 | dependencies {
22 | // don't remove the existing dependencies, just add this one
23 | classpath("io.github.MatrixDev.android-rust:plugin:0.3.2")
24 | }
25 | }
26 | ```
27 |
28 | This plugin will compile `example-jsi-module` using the Android NDK as part
29 | of building our application for Android
30 | 6. Make sure the [Android NDK](https://developer.android.com/ndk) is installed.
31 | Version 23 should work
32 | 7. Update `android/app/build.gradle` to add the following lines:
33 | ```groovy
34 | apply plugin: "io.github.MatrixDev.android-rust"
35 | androidRust {
36 | module("example-jsi-module") {
37 | it.path = file("../example-jsi-module")
38 |
39 | // default abi targets are arm and arm64; if you want to run on Android
40 | // Emulator then you may need to add x86_64
41 |
42 | // targets = ["arm", "arm64", "x86", "x86_64"]
43 | }
44 | }
45 | ```
46 |
47 | **Note:** when you compile the program, this Gradle plugin will install all of
48 | the Rust Android targets if they are not already installed
49 | 8. Write the code in
50 | `android/app/src/main/java/com/example/ExampleJsiModule.java`. This will be
51 | called when the application starts, and it gives us a pointer to the React
52 | Native runtime so we can initialize our Rust code
53 | 9. Write the code in
54 | `android/app/src/main/java/com/example/ExampleJsiPackage.java`. This lets
55 | React Native discover our module
56 | 10. In `android/app/src/main/java/com/example/MainApplication.java`, add the following line to `getPackages()`:
57 | ```java
58 | packages.add(new ExampleJsiPackage());
59 | ```
60 | 11. Add a couple of lines to `App.tsx` to call our native module (I added them near the top):
61 | ```typescript
62 | // call our Rust module
63 | const {ExampleJsiModule} = NativeModules;
64 | ExampleJsiModule.install();
65 | ```
66 | 12. Connect your Android device or start an emulator
67 | 13. Run `npm run start` and press A to deploy to Android
68 | 14. You should see `hello from Rust` in the terminal after the app loads
69 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 | apply plugin: "com.facebook.react"
3 |
4 | /**
5 | * This is the configuration block to customize your React Native Android app.
6 | * By default you don't need to apply any configuration, just uncomment the lines you need.
7 | */
8 | react {
9 | /* Folders */
10 | // The root of your project, i.e. where "package.json" lives. Default is '..'
11 | // root = file("../")
12 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native
13 | // reactNativeDir = file("../node_modules/react-native")
14 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
15 | // codegenDir = file("../node_modules/@react-native/codegen")
16 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
17 | // cliFile = file("../node_modules/react-native/cli.js")
18 |
19 | /* Variants */
20 | // The list of variants to that are debuggable. For those we're going to
21 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
22 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
23 | // debuggableVariants = ["liteDebug", "prodDebug"]
24 |
25 | /* Bundling */
26 | // A list containing the node command and its flags. Default is just 'node'.
27 | // nodeExecutableAndArgs = ["node"]
28 | //
29 | // The command to run when bundling. By default is 'bundle'
30 | // bundleCommand = "ram-bundle"
31 | //
32 | // The path to the CLI configuration file. Default is empty.
33 | // bundleConfig = file(../rn-cli.config.js)
34 | //
35 | // The name of the generated asset file containing your JS bundle
36 | // bundleAssetName = "MyApplication.android.bundle"
37 | //
38 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
39 | // entryFile = file("../js/MyApplication.android.js")
40 | //
41 | // A list of extra flags to pass to the 'bundle' commands.
42 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
43 | // extraPackagerArgs = []
44 |
45 | /* Hermes Commands */
46 | // The hermes compiler command to run. By default it is 'hermesc'
47 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
48 | //
49 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
50 | // hermesFlags = ["-O", "-output-source-map"]
51 | }
52 |
53 | /**
54 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
55 | */
56 | def enableProguardInReleaseBuilds = false
57 |
58 | /**
59 | * The preferred build flavor of JavaScriptCore (JSC)
60 | *
61 | * For example, to use the international variant, you can use:
62 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
63 | *
64 | * The international variant includes ICU i18n library and necessary data
65 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
66 | * give correct results when using with locales other than en-US. Note that
67 | * this variant is about 6MiB larger per architecture than default.
68 | */
69 | def jscFlavor = 'org.webkit:android-jsc:+'
70 |
71 | android {
72 | ndkVersion rootProject.ext.ndkVersion
73 |
74 | compileSdkVersion rootProject.ext.compileSdkVersion
75 |
76 | namespace "com.example"
77 | defaultConfig {
78 | applicationId "com.example"
79 | minSdkVersion rootProject.ext.minSdkVersion
80 | targetSdkVersion rootProject.ext.targetSdkVersion
81 | versionCode 1
82 | versionName "1.0"
83 | }
84 | signingConfigs {
85 | debug {
86 | storeFile file('debug.keystore')
87 | storePassword 'android'
88 | keyAlias 'androiddebugkey'
89 | keyPassword 'android'
90 | }
91 | }
92 | buildTypes {
93 | debug {
94 | signingConfig signingConfigs.debug
95 |
96 | // if you need to debug your native module in Android Studio, this
97 | // will prevent Gradle from stripping out the native symbols that
98 | // are needed
99 | packagingOptions {
100 | doNotStrip '**/libexample_jsi_module.so'
101 | }
102 | }
103 | release {
104 | // Caution! In production, you need to generate your own keystore file.
105 | // see https://reactnative.dev/docs/signed-apk-android.
106 | signingConfig signingConfigs.debug
107 | minifyEnabled enableProguardInReleaseBuilds
108 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
109 | }
110 | }
111 | }
112 |
113 | dependencies {
114 | // The version of react-native is set by the React Native Gradle Plugin
115 | implementation("com.facebook.react:react-android")
116 |
117 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
118 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
119 | exclude group:'com.squareup.okhttp3', module:'okhttp'
120 | }
121 |
122 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
123 | if (hermesEnabled.toBoolean()) {
124 | implementation("com.facebook.react:hermes-android")
125 | } else {
126 | implementation jscFlavor
127 | }
128 | }
129 |
130 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
131 |
132 | // apply plugin: "com.github.willir.rust.cargo-ndk-android"
133 | // cargoNdk {
134 | // module = "../example-jsi-module"
135 | // librariesNames = ["libexample_jsi_module.so"]
136 | // targets = ["arm64", "x86_64"]
137 | // }
138 |
139 | apply plugin: "io.github.MatrixDev.android-rust"
140 |
141 | androidRust {
142 | module("example-jsi-module") {
143 | it.path = file("../../example-jsi-module")
144 |
145 | // default abi targets are arm and arm64; if you want to run on Android
146 | // Emulator then you probably need to add x86_64
147 |
148 | targets = ["arm", "arm64", "x86", "x86_64"]
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/example/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/debug.keystore
--------------------------------------------------------------------------------
/example/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Meta Platforms, Inc. and affiliates.
3 | *
4 | *
This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.example;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
21 | import com.facebook.react.ReactInstanceEventListener;
22 | import com.facebook.react.ReactInstanceManager;
23 | import com.facebook.react.bridge.ReactContext;
24 | import com.facebook.react.modules.network.NetworkingModule;
25 | import okhttp3.OkHttpClient;
26 |
27 | /**
28 | * Class responsible of loading Flipper inside your React Native application. This is the debug
29 | * flavor of it. Here you can add your own plugins and customize the Flipper setup.
30 | */
31 | public class ReactNativeFlipper {
32 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
33 | if (FlipperUtils.shouldEnableFlipper(context)) {
34 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
35 |
36 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
37 | client.addPlugin(new DatabasesFlipperPlugin(context));
38 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
39 | client.addPlugin(CrashReporterPlugin.getInstance());
40 |
41 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
42 | NetworkingModule.setCustomClientBuilder(
43 | new NetworkingModule.CustomClientBuilder() {
44 | @Override
45 | public void apply(OkHttpClient.Builder builder) {
46 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
47 | }
48 | });
49 | client.addPlugin(networkFlipperPlugin);
50 | client.start();
51 |
52 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
53 | // Hence we run if after all native modules have been initialized
54 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
55 | if (reactContext == null) {
56 | reactInstanceManager.addReactInstanceEventListener(
57 | new ReactInstanceEventListener() {
58 | @Override
59 | public void onReactContextInitialized(ReactContext reactContext) {
60 | reactInstanceManager.removeReactInstanceEventListener(this);
61 | reactContext.runOnNativeModulesQueueThread(
62 | new Runnable() {
63 | @Override
64 | public void run() {
65 | client.addPlugin(new FrescoFlipperPlugin());
66 | }
67 | });
68 | }
69 | });
70 | } else {
71 | client.addPlugin(new FrescoFlipperPlugin());
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/ExampleJsiModule.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import android.util.Log;
4 |
5 | import com.facebook.react.bridge.JSIModulePackage;
6 | import com.facebook.react.bridge.JSIModuleProvider;
7 | import com.facebook.react.bridge.JSIModuleSpec;
8 | import com.facebook.react.bridge.JSIModuleType;
9 | import com.facebook.react.bridge.JavaScriptContextHolder;
10 | import com.facebook.react.bridge.ReactApplicationContext;
11 | import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
12 |
13 | import com.facebook.react.bridge.Arguments;
14 | import com.facebook.react.bridge.Callback;
15 | import com.facebook.react.bridge.JavaScriptContextHolder;
16 | import com.facebook.react.bridge.ReactApplicationContext;
17 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
18 | import com.facebook.react.bridge.ReactMethod;
19 | import com.facebook.react.bridge.ReadableMap;
20 | import com.facebook.react.bridge.WritableArray;
21 | import com.facebook.react.bridge.WritableMap;
22 | import com.facebook.react.module.annotations.ReactModule;
23 |
24 | import java.util.Arrays;
25 | import java.util.List;
26 |
27 | @ReactModule(name = "ExampleJsiModule")
28 | public class ExampleJsiModule extends ReactContextBaseJavaModule {
29 | public static native void init(long runtimePtr, CallInvokerHolderImpl callInvoker);
30 |
31 | public ExampleJsiModule(ReactApplicationContext reactContext) {
32 | super(reactContext);
33 | }
34 |
35 | @Override
36 | public String getName() {
37 | return "ExampleJsiModule";
38 | }
39 |
40 | @ReactMethod(isBlockingSynchronousMethod = true)
41 | public boolean install() {
42 | // load our dynamic library
43 | System.loadLibrary("example_jsi_module");
44 |
45 | var ctx = getReactApplicationContext();
46 | var jsContext = ctx.getJavaScriptContextHolder();
47 | var runtimePtr = jsContext.get();
48 | var callInvoker = (CallInvokerHolderImpl)ctx.getCatalystInstance().getJSCallInvokerHolder();
49 |
50 | Log.i("ExampleJsiModule", "initializing");
51 |
52 | if (jsContext.get() != 0) {
53 | ExampleJsiModule.init(runtimePtr, callInvoker);
54 | Log.i("ExampleJsiModule", "initialized");
55 |
56 | return true;
57 | } else {
58 | Log.e("ExampleJsiModule","initialization failed: no runtime");
59 | return false;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/ExampleJsiPackage.java:
--------------------------------------------------------------------------------
1 |
2 | package com.example;
3 |
4 | import java.util.Arrays;
5 | import java.util.Collections;
6 | import java.util.List;
7 |
8 | import com.facebook.react.ReactPackage;
9 | import com.facebook.react.bridge.NativeModule;
10 | import com.facebook.react.bridge.ReactApplicationContext;
11 | import com.facebook.react.uimanager.ViewManager;
12 | import com.facebook.react.bridge.JavaScriptModule;
13 |
14 | public class ExampleJsiPackage implements ReactPackage {
15 | @Override
16 | public List createNativeModules(ReactApplicationContext reactContext) {
17 | return Arrays.asList(new ExampleJsiModule(reactContext));
18 | }
19 |
20 | @Override
21 | public List createViewManagers(ReactApplicationContext reactContext) {
22 | return Collections.emptyList();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import com.facebook.react.ReactActivity;
4 | import com.facebook.react.ReactActivityDelegate;
5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
6 | import com.facebook.react.defaults.DefaultReactActivityDelegate;
7 |
8 | public class MainActivity extends ReactActivity {
9 |
10 | /**
11 | * Returns the name of the main component registered from JavaScript. This is used to schedule
12 | * rendering of the component.
13 | */
14 | @Override
15 | protected String getMainComponentName() {
16 | return "example";
17 | }
18 |
19 | /**
20 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
21 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
22 | * (aka React 18) with two boolean flags.
23 | */
24 | @Override
25 | protected ReactActivityDelegate createReactActivityDelegate() {
26 | return new DefaultReactActivityDelegate(
27 | this,
28 | getMainComponentName(),
29 | // If you opted-in for the New Architecture, we enable the Fabric Renderer.
30 | DefaultNewArchitectureEntryPoint.getFabricEnabled());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import android.app.Application;
4 | import com.facebook.react.PackageList;
5 | import com.facebook.react.ReactApplication;
6 | import com.facebook.react.ReactNativeHost;
7 | import com.facebook.react.ReactPackage;
8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
9 | import com.facebook.react.defaults.DefaultReactNativeHost;
10 | import com.facebook.soloader.SoLoader;
11 | import java.util.List;
12 |
13 | public class MainApplication extends Application implements ReactApplication {
14 |
15 | private final ReactNativeHost mReactNativeHost =
16 | new DefaultReactNativeHost(this) {
17 | @Override
18 | public boolean getUseDeveloperSupport() {
19 | return BuildConfig.DEBUG;
20 | }
21 |
22 | @Override
23 | protected List getPackages() {
24 | @SuppressWarnings("UnnecessaryLocalVariable")
25 | List packages = new PackageList(this).getPackages();
26 | // Packages that cannot be autolinked yet can be added manually here, for example:
27 | // packages.add(new MyReactNativePackage());
28 | packages.add(new ExampleJsiPackage());
29 | return packages;
30 | }
31 |
32 | @Override
33 | protected String getJSMainModuleName() {
34 | return "index";
35 | }
36 |
37 | @Override
38 | protected boolean isNewArchEnabled() {
39 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
40 | }
41 |
42 | @Override
43 | protected Boolean isHermesEnabled() {
44 | return BuildConfig.IS_HERMES_ENABLED;
45 | }
46 | };
47 |
48 | @Override
49 | public ReactNativeHost getReactNativeHost() {
50 | return mReactNativeHost;
51 | }
52 |
53 | @Override
54 | public void onCreate() {
55 | super.onCreate();
56 | SoLoader.init(this, /* native exopackage */ false);
57 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
58 | // If you opted-in for the New Architecture, we load the native entry point for this app.
59 | DefaultNewArchitectureEntryPoint.load();
60 | }
61 | ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | example
3 |
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/android/app/src/release/java/com/example/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Meta Platforms, Inc. and affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.example;
8 |
9 | import android.content.Context;
10 | import com.facebook.react.ReactInstanceManager;
11 |
12 | /**
13 | * Class responsible of loading Flipper inside your React Native application. This is the release
14 | * flavor of it so it's empty as we don't want to load Flipper.
15 | */
16 | public class ReactNativeFlipper {
17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
18 | // Do nothing as we don't want to initialize Flipper on Release.
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | buildToolsVersion = "33.0.0"
6 | minSdkVersion = 21
7 | compileSdkVersion = 33
8 | targetSdkVersion = 33
9 |
10 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
11 | ndkVersion = "23.1.7779620"
12 | }
13 | repositories {
14 | google()
15 | mavenCentral()
16 | maven {
17 | url "https://plugins.gradle.org/m2/"
18 | }
19 | }
20 | dependencies {
21 | classpath("com.android.tools.build:gradle")
22 | classpath("com.facebook.react:react-native-gradle-plugin")
23 | // classpath("gradle.plugin.com.github.willir.rust:plugin:0.3.4")
24 | classpath("io.github.MatrixDev.android-rust:plugin:0.3.2")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 |
27 | # Version of flipper SDK to use with React Native
28 | FLIPPER_VERSION=0.182.0
29 |
30 | # Use this property to specify which architecture you want to build.
31 | # You can also override it from the CLI using
32 | # ./gradlew -PreactNativeArchitectures=x86_64
33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
34 |
35 | # Use this property to enable support to the new architecture.
36 | # This will allow you to use TurboModules and the Fabric render in
37 | # your application. You should enable this flag either if you want
38 | # to write custom TurboModules/Fabric components OR use libraries that
39 | # are providing them.
40 | newArchEnabled=false
41 |
42 | # Use this property to enable or disable the Hermes JS engine.
43 | # If set to false, you will be using JSC instead.
44 | hermesEnabled=true
45 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laptou/jsi-rs/72afac135eb930b1184c8fe2b4b2f92ee1376b1c/example/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/example/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 | # Collect all arguments for the java command;
201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202 | # shell script including quotes and variable substitutions, so put them in
203 | # double quotes to make sure that they get re-expanded; and
204 | # * put everything else in single quotes, so that it's not re-expanded.
205 |
206 | set -- \
207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
208 | -classpath "$CLASSPATH" \
209 | org.gradle.wrapper.GradleWrapperMain \
210 | "$@"
211 |
212 | # Stop when "xargs" is not available.
213 | if ! command -v xargs >/dev/null 2>&1
214 | then
215 | die "xargs is not available"
216 | fi
217 |
218 | # Use "xargs" to parse quoted args.
219 | #
220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
221 | #
222 | # In Bash we could simply go:
223 | #
224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
225 | # set -- "${ARGS[@]}" "$@"
226 | #
227 | # but POSIX shell has neither arrays nor command substitution, so instead we
228 | # post-process each arg (as a line of input to sed) to backslash-escape any
229 | # character that might be a shell metacharacter, then use eval to reverse
230 | # that process (while maintaining the separation between arguments), and wrap
231 | # the whole thing up as a single "set" statement.
232 | #
233 | # This will of course break if any of these variables contains a newline or
234 | # an unmatched quote.
235 | #
236 |
237 | eval "set -- $(
238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
239 | xargs -n1 |
240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
241 | tr '\n' ' '
242 | )" '"$@"'
243 |
244 | exec "$JAVACMD" "$@"
245 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'example'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 | includeBuild('../node_modules/@react-native/gradle-plugin')
5 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "displayName": "example"
4 | }
5 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/example/example-jsi-module/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "example-jsi-module"
3 | version = "0.1.1-alpha.1"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | # Android requires dynamic libraries, and iOS requires static ones
9 | crate-type = ["cdylib", "staticlib"]
10 |
11 | [dependencies]
12 | jsi = { path = "../../jsi" }
13 | anyhow = { version = "1.0", features = ["backtrace"] }
14 | jni = "0.21.1"
15 | cxx = "1.0.106"
16 |
--------------------------------------------------------------------------------
/example/example-jsi-module/src/android.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Context;
2 | use jni::objects::{JClass, JObject, JString};
3 | use jni::sys::{jlong, jstring};
4 | use jni::JNIEnv;
5 |
6 | // This function signature allows this function to be called from Java
7 | #[no_mangle]
8 | pub extern "system" fn Java_com_example_ExampleJsiModule_init<'env>(
9 | mut env: JNIEnv<'env>,
10 | _class: JClass<'env>,
11 | runtime_ptr: jlong,
12 | call_invoker_holder: JObject<'env>,
13 | ) -> () {
14 | // from https://github.com/facebookincubator/fbjni/blob/8b5aa9eb323184b27b87b5955b242e6e5a342c1a/cxx/fbjni/detail/Hybrid.h
15 | let hybrid_data: JObject = env
16 | .get_field(
17 | call_invoker_holder,
18 | "mHybridData",
19 | "Lcom/facebook/jni/HybridData;",
20 | )
21 | .expect("can't find hybrid data on CallInvokerHolderImpl")
22 | .try_into()
23 | .unwrap();
24 |
25 | let destructor: JObject = env
26 | .get_field(
27 | hybrid_data,
28 | "mDestructor",
29 | "Lcom/facebook/jni/HybridData$Destructor;",
30 | )
31 | .expect("can't find internal destructor on CallInvokerHolderImpl")
32 | .try_into()
33 | .unwrap();
34 |
35 | let call_invoker_holder_ptr: jlong = env
36 | .get_field(destructor, "mNativePointer", "J")
37 | .expect("can't find native pointer on CallInvokerHolderImpl")
38 | .try_into()
39 | .unwrap();
40 |
41 | let call_invoker_holder = call_invoker_holder_ptr as *mut jsi::sys::CallInvokerHolder;
42 | let call_invoker_holder = unsafe { std::pin::Pin::new_unchecked(&mut *call_invoker_holder) };
43 | let call_invoker = call_invoker_holder.getCallInvoker();
44 |
45 | let runtime_ptr = runtime_ptr as *mut _;
46 |
47 | crate::init(runtime_ptr, call_invoker);
48 | }
49 |
--------------------------------------------------------------------------------
/example/example-jsi-module/src/ios.rs:
--------------------------------------------------------------------------------
1 | #[no_mangle]
2 | pub extern "C" fn ExampleJsiModule_init(
3 | rt: *mut jsi::sys::Runtime,
4 | call_invoker: cxx::SharedPtr,
5 | ) {
6 | crate::init(rt, call_invoker);
7 | }
8 |
--------------------------------------------------------------------------------
/example/example-jsi-module/src/lib.rs:
--------------------------------------------------------------------------------
1 | use jsi::{
2 | host_object, FromObject, FromValue, IntoValue, JsiFn, JsiObject, JsiString, JsiValue, PropName, RuntimeHandle
3 | };
4 |
5 | #[cfg(target_os = "android")]
6 | mod android;
7 |
8 | #[cfg(target_os = "ios")]
9 | mod ios;
10 |
11 | pub fn init(rt: *mut jsi::sys::Runtime, call_invoker: cxx::SharedPtr) {
12 | let (mut rt, _) = jsi::init(rt, call_invoker);
13 |
14 | let console = PropName::new("console", &mut rt);
15 | let console = rt.global().get(console, &mut rt);
16 | let console = JsiObject::from_value(&console, &mut rt).unwrap();
17 |
18 | let console_log = console.get(PropName::new("log", &mut rt), &mut rt);
19 | let console_log = JsiObject::from_value(&console_log, &mut rt).unwrap();
20 | let console_log = JsiFn::from_object(&console_log, &mut rt).unwrap();
21 | console_log
22 | .call(
23 | [JsiString::new("hello from Rust", &mut rt).into_value(&mut rt)],
24 | &mut rt,
25 | )
26 | .unwrap();
27 |
28 | // we called console.log("hello from Rust") using JSI! you should see the
29 | // log in your React Native bundler terminal
30 |
31 | // this is just an example, but from here, you could spawn threads or really
32 | // do whatever you want with the RuntimeHandle
33 |
34 | // make sure that any multithreaded operations use the CallInvoker if they
35 | // want to call back to JavaScript
36 |
37 | // now, for my next trick, I will add a host object to the global namespace
38 | let host_object = ExampleHostObject;
39 | let host_object = host_object.into_value(&mut rt);
40 |
41 | rt.global().set(PropName::new("ExampleGlobal", &mut rt), &host_object, &mut rt);
42 |
43 | let global_str = JsiString::new("hallo", &mut rt);
44 | let global_str = global_str.into_value(&mut rt);
45 | rt.global().set(PropName::new("ExampleGlobal2", &mut rt), &global_str, &mut rt);
46 |
47 | let global_num = JsiValue::new_number(3.200);
48 | rt.global().set(PropName::new("ExampleGlobal3", &mut rt), &global_num, &mut rt);
49 | }
50 |
51 | struct ExampleHostObject;
52 |
53 | #[host_object]
54 | impl ExampleHostObject {
55 | pub fn time(&self, _rt: &mut RuntimeHandle) -> anyhow::Result {
56 | Ok(3200)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import {AppRegistry} from 'react-native';
6 | import App from './App';
7 | import {name as appName} from './app.json';
8 |
9 | AppRegistry.registerComponent(appName, () => App);
10 |
--------------------------------------------------------------------------------
/example/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Resolve react_native_pods.rb with node to allow for hoisting
2 | require Pod::Executable.execute_command('node', ['-p',
3 | 'require.resolve(
4 | "react-native/scripts/react_native_pods.rb",
5 | {paths: [process.argv[1]]},
6 | )', __dir__]).strip
7 |
8 | platform :ios, min_ios_version_supported
9 | prepare_react_native_project!
10 |
11 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
12 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
13 | #
14 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
15 | # ```js
16 | # module.exports = {
17 | # dependencies: {
18 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
19 | # ```
20 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
21 |
22 | linkage = ENV['USE_FRAMEWORKS']
23 | if linkage != nil
24 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
25 | use_frameworks! :linkage => linkage.to_sym
26 | end
27 |
28 | target 'example' do
29 | config = use_native_modules!
30 |
31 | # Flags change depending on the env values.
32 | flags = get_default_flags()
33 |
34 | use_react_native!(
35 | :path => config[:reactNativePath],
36 | # Hermes is now enabled by default. Disable by setting this flag to false.
37 | :hermes_enabled => flags[:hermes_enabled],
38 | :fabric_enabled => flags[:fabric_enabled],
39 | # Enables Flipper.
40 | #
41 | # Note that if you have use_frameworks! enabled, Flipper will not work and
42 | # you should disable the next line.
43 | :flipper_configuration => flipper_config,
44 | # An absolute path to your application root.
45 | :app_path => "#{Pod::Config.instance.installation_root}/.."
46 | )
47 |
48 | target 'exampleTests' do
49 | inherit! :complete
50 | # Pods for testing
51 | end
52 |
53 | post_install do |installer|
54 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
55 | react_native_post_install(
56 | installer,
57 | config[:reactNativePath],
58 | :mac_catalyst_enabled => false
59 | )
60 | __apply_Xcode_12_5_M1_post_install_workaround(installer)
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/example/ios/example/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : RCTAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/example/ios/example/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 |
5 | @implementation AppDelegate
6 |
7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8 | {
9 | self.moduleName = @"example";
10 | // You can add your custom initial props in the dictionary below.
11 | // They will be passed down to the ViewController used by React Native.
12 | self.initialProps = @{};
13 |
14 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
15 | }
16 |
17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
18 | {
19 | #if DEBUG
20 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
21 | #else
22 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
23 | #endif
24 | }
25 |
26 | @end
27 |
--------------------------------------------------------------------------------
/example/ios/example/ExampleJsiModule.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef ExampleJsiModule_h
3 | #define ExampleJsiModule_h
4 |
5 | #include
6 |
7 | extern "C" {
8 | void ExampleJsiModule_init(void* rt, std::shared_ptr ctx);
9 | }
10 |
11 | #endif
12 |
--------------------------------------------------------------------------------
/example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "scale" : "1x",
46 | "size" : "1024x1024"
47 | }
48 | ],
49 | "info" : {
50 | "author" : "xcode",
51 | "version" : 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/example/ios/example/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSExceptionDomains
30 |
31 | localhost
32 |
33 | NSExceptionAllowsInsecureHTTPLoads
34 |
35 |
36 |
37 |
38 | NSLocationWhenInUseUsageDescription
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UIViewControllerBasedStatusBarAppearance
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/example/ios/example/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/example/ios/example/RCTExampleJsiModule.h:
--------------------------------------------------------------------------------
1 | #ifndef RCTExampleJsiModule_h
2 | #define RCTExampleJsiModule_h
3 |
4 | #include "ExampleJsiModule.h"
5 |
6 | #import
7 | @interface RCTExampleJsiModule : NSObject
8 | @end
9 |
10 |
11 | #endif /* RCTExampleJsiModule_h */
12 |
--------------------------------------------------------------------------------
/example/ios/example/RTCExampleJsiModule.mm:
--------------------------------------------------------------------------------
1 | #import
2 | #import "RCTExampleJsiModule.h"
3 |
4 | #import
5 | #import
6 | #import
7 | #import
8 | #import
9 |
10 | @implementation RCTExampleJsiModule
11 |
12 | @synthesize bridge = _bridge;
13 |
14 | RCT_EXPORT_MODULE(ExampleJsiModule)
15 |
16 | - (void)invalidate {
17 | _bridge = nil;
18 | }
19 |
20 | - (void)setBridge:(RCTBridge *)bridge {
21 | _bridge = bridge;
22 | }
23 |
24 | + (BOOL)requiresMainQueueSetup {
25 | return YES;
26 | }
27 |
28 |
29 | void installApi(std::shared_ptr callInvoker,
30 | facebook::jsi::Runtime *runtime) {
31 |
32 | ExampleJsiModule_init((void*)runtime, (std::shared_ptr) callInvoker);
33 | }
34 |
35 | RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
36 | RCTCxxBridge *cxxBridge = (RCTCxxBridge *)_bridge;
37 | if (cxxBridge.runtime != nullptr) {
38 | auto callInvoker = cxxBridge.jsCallInvoker;
39 | facebook::jsi::Runtime *jsRuntime = (facebook::jsi::Runtime *)cxxBridge.runtime;
40 |
41 | installApi(callInvoker, jsRuntime);
42 | return @true;
43 | }
44 | return @false;
45 | }
46 |
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/example/ios/example/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | @autoreleasepool {
8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example/ios/exampleTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/ios/exampleTests/exampleTests.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import
5 | #import
6 |
7 | #define TIMEOUT_SECONDS 600
8 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
9 |
10 | @interface exampleTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation exampleTests
15 |
16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test
17 | {
18 | if (test(view)) {
19 | return YES;
20 | }
21 | for (UIView *subview in [view subviews]) {
22 | if ([self findSubviewInView:subview matching:test]) {
23 | return YES;
24 | }
25 | }
26 | return NO;
27 | }
28 |
29 | - (void)testRendersWelcomeScreen
30 | {
31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
33 | BOOL foundElement = NO;
34 |
35 | __block NSString *redboxError = nil;
36 | #ifdef DEBUG
37 | RCTSetLogFunction(
38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
39 | if (level >= RCTLogLevelError) {
40 | redboxError = message;
41 | }
42 | });
43 | #endif
44 |
45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
48 |
49 | foundElement = [self findSubviewInView:vc.view
50 | matching:^BOOL(UIView *view) {
51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
52 | return YES;
53 | }
54 | return NO;
55 | }];
56 | }
57 |
58 | #ifdef DEBUG
59 | RCTSetLogFunction(RCTDefaultLogFunction);
60 | #endif
61 |
62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/example/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'react-native',
3 | };
4 |
--------------------------------------------------------------------------------
/example/metro.config.js:
--------------------------------------------------------------------------------
1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
2 |
3 | /**
4 | * Metro configuration
5 | * https://facebook.github.io/metro/docs/configuration
6 | *
7 | * @type {import('metro-config').MetroConfig}
8 | */
9 | const config = {};
10 |
11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config);
12 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "lint": "eslint .",
9 | "start": "react-native start",
10 | "test": "jest"
11 | },
12 | "dependencies": {
13 | "react": "18.2.0",
14 | "react-native": "0.72.4"
15 | },
16 | "devDependencies": {
17 | "@babel/core": "^7.20.0",
18 | "@babel/preset-env": "^7.20.0",
19 | "@babel/runtime": "^7.20.0",
20 | "@react-native/eslint-config": "^0.72.2",
21 | "@react-native/metro-config": "^0.72.11",
22 | "@tsconfig/react-native": "^3.0.0",
23 | "@types/react": "^18.0.24",
24 | "@types/react-test-renderer": "^18.0.0",
25 | "babel-jest": "^29.2.1",
26 | "eslint": "^8.19.0",
27 | "jest": "^29.2.1",
28 | "metro-react-native-babel-preset": "0.76.8",
29 | "prettier": "^2.4.1",
30 | "react-test-renderer": "18.2.0",
31 | "typescript": "4.8.4"
32 | },
33 | "engines": {
34 | "node": ">=16"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/react-native/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/jsi-macros/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "Inflector"
7 | version = "0.11.4"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10 | dependencies = [
11 | "lazy_static",
12 | "regex",
13 | ]
14 |
15 | [[package]]
16 | name = "aho-corasick"
17 | version = "1.0.4"
18 | source = "registry+https://github.com/rust-lang/crates.io-index"
19 | checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
20 | dependencies = [
21 | "memchr",
22 | ]
23 |
24 | [[package]]
25 | name = "lazy_static"
26 | version = "1.4.0"
27 | source = "registry+https://github.com/rust-lang/crates.io-index"
28 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
29 |
30 | [[package]]
31 | name = "memchr"
32 | version = "2.5.0"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
35 |
36 | [[package]]
37 | name = "proc-macro-error"
38 | version = "1.0.4"
39 | source = "registry+https://github.com/rust-lang/crates.io-index"
40 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
41 | dependencies = [
42 | "proc-macro-error-attr",
43 | "proc-macro2",
44 | "quote",
45 | "syn",
46 | "version_check",
47 | ]
48 |
49 | [[package]]
50 | name = "proc-macro-error-attr"
51 | version = "1.0.4"
52 | source = "registry+https://github.com/rust-lang/crates.io-index"
53 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
54 | dependencies = [
55 | "proc-macro2",
56 | "quote",
57 | "version_check",
58 | ]
59 |
60 | [[package]]
61 | name = "proc-macro2"
62 | version = "1.0.66"
63 | source = "registry+https://github.com/rust-lang/crates.io-index"
64 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
65 | dependencies = [
66 | "unicode-ident",
67 | ]
68 |
69 | [[package]]
70 | name = "quote"
71 | version = "1.0.33"
72 | source = "registry+https://github.com/rust-lang/crates.io-index"
73 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
74 | dependencies = [
75 | "proc-macro2",
76 | ]
77 |
78 | [[package]]
79 | name = "regex"
80 | version = "1.9.3"
81 | source = "registry+https://github.com/rust-lang/crates.io-index"
82 | checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
83 | dependencies = [
84 | "aho-corasick",
85 | "memchr",
86 | "regex-automata",
87 | "regex-syntax",
88 | ]
89 |
90 | [[package]]
91 | name = "regex-automata"
92 | version = "0.3.6"
93 | source = "registry+https://github.com/rust-lang/crates.io-index"
94 | checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
95 | dependencies = [
96 | "aho-corasick",
97 | "memchr",
98 | "regex-syntax",
99 | ]
100 |
101 | [[package]]
102 | name = "regex-syntax"
103 | version = "0.7.4"
104 | source = "registry+https://github.com/rust-lang/crates.io-index"
105 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
106 |
107 | [[package]]
108 | name = "splicer-js-api-macros"
109 | version = "0.1.0"
110 | dependencies = [
111 | "Inflector",
112 | "proc-macro-error",
113 | "proc-macro2",
114 | "quote",
115 | "syn",
116 | ]
117 |
118 | [[package]]
119 | name = "syn"
120 | version = "1.0.109"
121 | source = "registry+https://github.com/rust-lang/crates.io-index"
122 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
123 | dependencies = [
124 | "proc-macro2",
125 | "quote",
126 | "unicode-ident",
127 | ]
128 |
129 | [[package]]
130 | name = "unicode-ident"
131 | version = "1.0.11"
132 | source = "registry+https://github.com/rust-lang/crates.io-index"
133 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
134 |
135 | [[package]]
136 | name = "version_check"
137 | version = "0.9.4"
138 | source = "registry+https://github.com/rust-lang/crates.io-index"
139 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
140 |
--------------------------------------------------------------------------------
/jsi-macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "jsi-macros"
3 | version = "0.3.1-alpha.5"
4 | edition = "2021"
5 | license = "MIT"
6 | description = "Support macros for jsi-rs"
7 | authors = ["Ibiyemi Abiodun "]
8 | repository = "https://github.com/laptou/jsi-rs/"
9 |
10 | [lib]
11 | proc-macro = true
12 |
13 | [dependencies]
14 | Inflector = "0.11.4"
15 | proc-macro-error = "1.0"
16 | proc-macro2 = "1.0"
17 | quote = "1.0"
18 | syn = { version = "2", features = ["full", "quote"] }
19 |
20 | [features]
21 | host-object-trace = []
22 |
--------------------------------------------------------------------------------
/jsi-macros/src/host_event.rs:
--------------------------------------------------------------------------------
1 | use inflector::Inflector;
2 | use proc_macro2::TokenStream;
3 |
4 | use quote::{format_ident, quote};
5 | use syn::{parse::Parse, ItemEnum};
6 |
7 | extern crate proc_macro;
8 | pub struct HostEventImpl(pub TokenStream);
9 |
10 | impl Parse for HostEventImpl {
11 | fn parse(input: syn::parse::ParseStream) -> syn::Result {
12 | let enum_def: ItemEnum = input.parse()?;
13 |
14 | let enum_name = &enum_def.ident;
15 |
16 | let event_key_name = format_ident!("{}Key", enum_name);
17 |
18 | let mut event_names = vec![];
19 | let mut event_key_mappers = vec![];
20 | let mut event_args_mappers = vec![];
21 |
22 | for variant in &enum_def.variants {
23 | let variant_name = variant.ident.clone();
24 | event_names.push(variant_name.clone());
25 |
26 | event_key_mappers.push(match &variant.fields {
27 | syn::Fields::Named(_) => {
28 | quote! { Self::#variant_name { .. } => #event_key_name::#variant_name }
29 | }
30 | syn::Fields::Unnamed(_) => {
31 | quote! { Self::#variant_name ( .. ) => #event_key_name::#variant_name }
32 | }
33 | syn::Fields::Unit => {
34 | quote! { Self::#variant_name => #event_key_name::#variant_name }
35 | }
36 | });
37 |
38 | event_args_mappers.push(match &variant.fields {
39 | syn::Fields::Named(fields) => {
40 | let field_names: Vec<_> = fields
41 | .named
42 | .iter()
43 | .map(|f| f.ident.clone().unwrap())
44 | .collect();
45 |
46 | quote! { Self::#variant_name { #(#field_names),* } => {
47 | let mut args: Vec<::jsi::JsiValue> = vec![];
48 | #( args.push(::jsi::IntoValue::into_value(#field_names, rt)); )*
49 | args
50 | } }
51 | }
52 | syn::Fields::Unnamed(fields) => {
53 | let field_names: Vec<_> = fields
54 | .unnamed
55 | .iter()
56 | .enumerate()
57 | .map(|(idx, _)| format_ident!("f{}", idx))
58 | .collect();
59 |
60 | quote! { Self::#variant_name { #(#field_names),* } => {
61 | let mut args: Vec<::jsi::JsiValue> = vec![];
62 | #( args.push(::jsi::IntoValue::into_value(#field_names, rt)); )*
63 | args
64 | } }
65 | }
66 | syn::Fields::Unit => {
67 | quote! { Self::#variant_name => vec![], }
68 | }
69 | });
70 | }
71 |
72 | let event_key_def = quote! {
73 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
74 | #[automatically_derived]
75 | pub enum #event_key_name {
76 | #(#event_names),*
77 | }
78 | };
79 |
80 | let event_names_str = event_names.iter().map(|id| id.to_string().to_camel_case());
81 |
82 | let event_key_impl_def = quote! {
83 | #[automatically_derived]
84 | impl ::std::str::FromStr for #event_key_name {
85 | type Err = ::anyhow::Error;
86 |
87 | fn from_str(s: &str) -> Result {
88 | match s {
89 | #(#event_names_str => Ok(Self::#event_names),)*
90 | _ => bail!("invalid event name")
91 | }
92 | }
93 | }
94 |
95 | #[automatically_derived]
96 | impl ::splicer_js_api::event::HostEventKey for #event_key_name {
97 | }
98 | };
99 |
100 | let has_rt_lifetime = enum_def.generics.params.iter().any(|p| match p {
101 | syn::GenericParam::Lifetime(lt) => lt.lifetime.ident == "rt",
102 | _ => false,
103 | });
104 |
105 | let enum_impl_generics = if has_rt_lifetime {
106 | quote! { <'rt> }
107 | } else {
108 | quote! {}
109 | };
110 |
111 | let event_impl_def = quote! {
112 | #[automatically_derived]
113 | impl<'rt> ::splicer_js_api::event::HostEvent<'rt> for #enum_name #enum_impl_generics {
114 | type Key = #event_key_name;
115 |
116 | fn key(&self) -> Self::Key {
117 | match self {
118 | #(#event_key_mappers),*
119 | }
120 | }
121 |
122 | fn args(self, rt: &mut ::jsi::RuntimeHandle<'rt>) -> Vec> {
123 | match self {
124 | #(#event_args_mappers),*
125 | }
126 | }
127 | }
128 | };
129 |
130 | Ok(Self(quote! {
131 | #event_key_def
132 |
133 | #event_key_impl_def
134 |
135 | #event_impl_def
136 | }))
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/jsi-macros/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro_error::proc_macro_error;
2 | use syn::parse_macro_input;
3 |
4 | mod host_event;
5 | mod host_object;
6 |
7 |
8 | /// A macro that makes it easy to define functions and properies on host
9 | /// objects.
10 | ///
11 | /// ```no_run
12 | /// struct ExampleHostObject;
13 | ///
14 | /// #[host_object]
15 | /// impl ExampleHostObject {
16 | /// pub fn time(&self, _rt: &mut RuntimeHandle) -> anyhow::Result {
17 | /// Ok(3200)
18 | /// }
19 | /// }
20 | /// ```
21 | ///
22 | /// It is used on `impl` blocks and on the functions within the `impl` block.
23 | ///
24 | /// - Add `#[host_object(getter)]` to designate an `fn` as a getter. This will
25 | /// allow JavaScript code to read the member like a property instead of having
26 | /// to call it like a function. Getters should be `fn(&self, &mut
27 | /// RuntimeHandle) -> Result` where `T: IntoValue`.
28 | ///
29 | /// If the `fn` name in Rust starts with `get_`, this prefix will be removed.
30 | ///
31 | /// - Add `#[host_object(setter)]` to designate an `fn` as a setter. This will
32 | /// allow JavaScript code to write the like a property instead of having to
33 | /// call it like a function. Setters should be `fn(&self, &mut RuntimeHandle,
34 | /// value: JsiValue) -> Result<()>`.
35 | ///
36 | /// If the `fn` name in Rust starts with `set_`, this prefix will be removed.
37 | ///
38 | /// - All other methods will appear as methods on the host object in JavaScript.
39 | /// The first two arguments should be `&self` and a `&mut Runtime`.
40 | ///
41 | /// By default, all member names in a `host_object` block are converted from
42 | /// `snake_case` to `camelCase` to give them idiomatic names in JavaScript. To
43 | /// override this, you can set a name with `#[host_object(method as
44 | /// custom_method_name)]` or `#[host_object(getter as custom_prop_name)]`.
45 | ///
46 | #[proc_macro_attribute]
47 | pub fn host_object(
48 | _attr_input: proc_macro::TokenStream,
49 | attr_target: proc_macro::TokenStream,
50 | ) -> proc_macro::TokenStream {
51 | let impl_block = parse_macro_input!(attr_target as host_object::HostObjectImpl);
52 | let s = proc_macro::TokenStream::from(impl_block.0);
53 | // println!("{}", s);
54 | s
55 | }
56 |
57 | #[proc_macro_error]
58 | #[proc_macro_derive(HostEvent)]
59 | pub fn host_event_emitter(target: proc_macro::TokenStream) -> proc_macro::TokenStream {
60 | let impl_block = parse_macro_input!(target as host_event::HostEventImpl);
61 | proc_macro::TokenStream::from(impl_block.0)
62 | }
63 |
--------------------------------------------------------------------------------
/jsi-sys/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "anyhow"
7 | version = "1.0.75"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
10 |
11 | [[package]]
12 | name = "cc"
13 | version = "1.0.83"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
16 | dependencies = [
17 | "libc",
18 | ]
19 |
20 | [[package]]
21 | name = "codespan-reporting"
22 | version = "0.11.1"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
25 | dependencies = [
26 | "termcolor",
27 | "unicode-width",
28 | ]
29 |
30 | [[package]]
31 | name = "cxx"
32 | version = "1.0.106"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28"
35 | dependencies = [
36 | "cc",
37 | "cxxbridge-flags",
38 | "cxxbridge-macro",
39 | "link-cplusplus",
40 | ]
41 |
42 | [[package]]
43 | name = "cxx-build"
44 | version = "1.0.106"
45 | source = "registry+https://github.com/rust-lang/crates.io-index"
46 | checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285"
47 | dependencies = [
48 | "cc",
49 | "codespan-reporting",
50 | "once_cell",
51 | "proc-macro2",
52 | "quote",
53 | "scratch",
54 | "syn",
55 | ]
56 |
57 | [[package]]
58 | name = "cxxbridge-flags"
59 | version = "1.0.106"
60 | source = "registry+https://github.com/rust-lang/crates.io-index"
61 | checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809"
62 |
63 | [[package]]
64 | name = "cxxbridge-macro"
65 | version = "1.0.106"
66 | source = "registry+https://github.com/rust-lang/crates.io-index"
67 | checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d"
68 | dependencies = [
69 | "proc-macro2",
70 | "quote",
71 | "syn",
72 | ]
73 |
74 | [[package]]
75 | name = "dunce"
76 | version = "1.0.4"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
79 |
80 | [[package]]
81 | name = "libc"
82 | version = "0.2.147"
83 | source = "registry+https://github.com/rust-lang/crates.io-index"
84 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
85 |
86 | [[package]]
87 | name = "link-cplusplus"
88 | version = "1.0.9"
89 | source = "registry+https://github.com/rust-lang/crates.io-index"
90 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
91 | dependencies = [
92 | "cc",
93 | ]
94 |
95 | [[package]]
96 | name = "once_cell"
97 | version = "1.18.0"
98 | source = "registry+https://github.com/rust-lang/crates.io-index"
99 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
100 |
101 | [[package]]
102 | name = "proc-macro2"
103 | version = "1.0.66"
104 | source = "registry+https://github.com/rust-lang/crates.io-index"
105 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
106 | dependencies = [
107 | "unicode-ident",
108 | ]
109 |
110 | [[package]]
111 | name = "quote"
112 | version = "1.0.33"
113 | source = "registry+https://github.com/rust-lang/crates.io-index"
114 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
115 | dependencies = [
116 | "proc-macro2",
117 | ]
118 |
119 | [[package]]
120 | name = "scratch"
121 | version = "1.0.7"
122 | source = "registry+https://github.com/rust-lang/crates.io-index"
123 | checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
124 |
125 | [[package]]
126 | name = "splicer-jsi-sys"
127 | version = "0.1.0"
128 | dependencies = [
129 | "anyhow",
130 | "cxx",
131 | "cxx-build",
132 | "dunce",
133 | ]
134 |
135 | [[package]]
136 | name = "syn"
137 | version = "2.0.29"
138 | source = "registry+https://github.com/rust-lang/crates.io-index"
139 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
140 | dependencies = [
141 | "proc-macro2",
142 | "quote",
143 | "unicode-ident",
144 | ]
145 |
146 | [[package]]
147 | name = "termcolor"
148 | version = "1.2.0"
149 | source = "registry+https://github.com/rust-lang/crates.io-index"
150 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
151 | dependencies = [
152 | "winapi-util",
153 | ]
154 |
155 | [[package]]
156 | name = "unicode-ident"
157 | version = "1.0.11"
158 | source = "registry+https://github.com/rust-lang/crates.io-index"
159 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
160 |
161 | [[package]]
162 | name = "unicode-width"
163 | version = "0.1.10"
164 | source = "registry+https://github.com/rust-lang/crates.io-index"
165 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
166 |
167 | [[package]]
168 | name = "winapi"
169 | version = "0.3.9"
170 | source = "registry+https://github.com/rust-lang/crates.io-index"
171 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
172 | dependencies = [
173 | "winapi-i686-pc-windows-gnu",
174 | "winapi-x86_64-pc-windows-gnu",
175 | ]
176 |
177 | [[package]]
178 | name = "winapi-i686-pc-windows-gnu"
179 | version = "0.4.0"
180 | source = "registry+https://github.com/rust-lang/crates.io-index"
181 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
182 |
183 | [[package]]
184 | name = "winapi-util"
185 | version = "0.1.5"
186 | source = "registry+https://github.com/rust-lang/crates.io-index"
187 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
188 | dependencies = [
189 | "winapi",
190 | ]
191 |
192 | [[package]]
193 | name = "winapi-x86_64-pc-windows-gnu"
194 | version = "0.4.0"
195 | source = "registry+https://github.com/rust-lang/crates.io-index"
196 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
197 |
--------------------------------------------------------------------------------
/jsi-sys/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "jsi-sys"
3 | version = "0.3.0-alpha.5"
4 | edition = "2021"
5 | links = "jsi"
6 | license = "MIT"
7 | description = "Low-level interface to React Native JSI"
8 | authors = ["Ibiyemi Abiodun "]
9 | repository = "https://github.com/laptou/jsi-rs/"
10 |
11 | exclude = [
12 | "vendor/hermes"
13 | ]
14 |
15 | [dependencies]
16 | cxx = "1.0"
17 | anyhow = "1.0"
18 |
19 | [build-dependencies]
20 | cxx-build = "1.0"
21 | dunce = "1.0"
22 |
--------------------------------------------------------------------------------
/jsi-sys/build.rs:
--------------------------------------------------------------------------------
1 | use std::{env, path::PathBuf};
2 |
3 | fn main() {
4 | let base = env::var_os("CARGO_MANIFEST_DIR").unwrap();
5 | let pkg_base = PathBuf::from(base);
6 |
7 | let target_os = env::var_os("CARGO_CFG_TARGET_OS");
8 | let target_os = if let Some(s) = &target_os {
9 | s.to_str()
10 | } else {
11 | None
12 | };
13 |
14 | let rn_base = pkg_base.join("vendor/react-native/packages/react-native");
15 |
16 | let mut includes = vec![
17 | rn_base.join("React"),
18 | rn_base.join("React/Base"),
19 | rn_base.join("ReactCommon/jsi"),
20 | rn_base.join("ReactCommon/callinvoker"),
21 | pkg_base.join("include"),
22 | ];
23 |
24 | if let Some("android") = target_os {
25 | includes.push(
26 | rn_base.join("ReactAndroid/src/main/jni/react/turbomodule/"),
27 | );
28 | includes.push(pkg_base.join("vendor/fbjni/cxx"));
29 | }
30 |
31 | let includes: Vec<_> = IntoIterator::into_iter(includes)
32 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing include path {:?}", p)))
33 | .collect();
34 |
35 | let mut compiles = vec![rn_base.join("ReactCommon/jsi/jsi/jsi.cpp")];
36 |
37 | if let Some("android") = target_os {
38 | compiles.push(
39 | rn_base.join("ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/CallInvokerHolder.cpp")
40 | );
41 | }
42 |
43 | let compiles: Vec<_> = IntoIterator::into_iter(compiles)
44 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing compile file {:?}", p)))
45 | .collect();
46 |
47 | cxx_build::CFG
48 | .exported_header_dirs
49 | .extend(includes.iter().map(|e| e.as_path()));
50 |
51 | let mut bridges = vec!["src/ffi/base.rs", "src/ffi/host.rs"];
52 |
53 | if let Some("android") = target_os {
54 | bridges.push("src/ffi/android.rs");
55 | }
56 |
57 | for bridge in &bridges {
58 | println!("cargo:rerun-if-changed={}", bridge);
59 | }
60 |
61 | cxx_build::bridges(bridges)
62 | .flag_if_supported("-std=c++17")
63 | .files(compiles)
64 | .compile("jsi");
65 |
66 | println!("cargo:rerun-if-changed=include/wrapper.h");
67 | println!("cargo:rerun-if-changed=include/host.h");
68 | }
69 |
--------------------------------------------------------------------------------
/jsi-sys/include/host.h:
--------------------------------------------------------------------------------
1 | #ifndef JSI_HOST_H
2 | #define JSI_HOST_H
3 | #pragma once
4 |
5 | #include "rust/cxx.h"
6 | #include "jsi/jsi.h"
7 |
8 | namespace jsi_rs
9 | {
10 | namespace ffi
11 | {
12 | using Value = ::facebook::jsi::Value;
13 | using HostObject = ::facebook::jsi::HostObject;
14 | using Runtime = ::facebook::jsi::Runtime;
15 | using PropNameID = ::facebook::jsi::PropNameID;
16 | using JSError = ::facebook::jsi::JSError;
17 |
18 | struct RustHostObject;
19 |
20 | ::std::unique_ptr rho_get(
21 | RustHostObject &_self, Runtime &rt, const PropNameID &name);
22 |
23 | void rho_set(
24 | RustHostObject &_self, Runtime &rt, const PropNameID &name, const Value &value);
25 |
26 | ::std::unique_ptr<::std::vector> rho_properties(
27 | RustHostObject &_self, Runtime &rt) noexcept;
28 |
29 | class CxxHostObject : public HostObject
30 | {
31 | public:
32 | rust::Box inner;
33 |
34 | CxxHostObject(rust::Box it) : HostObject(), inner(std::move(it)) {}
35 |
36 | Value get(Runtime &rt, const PropNameID &name)
37 | {
38 | try
39 | {
40 | auto value = rho_get(*inner, rt, name);
41 | return std::move(*value.release());
42 | }
43 | catch (rust::Error &e)
44 | {
45 | throw JSError(rt, e.what());
46 | }
47 | }
48 |
49 | void set(Runtime &rt, const PropNameID &name, Value const &value)
50 | {
51 | try
52 | {
53 | rho_set(*inner, rt, name, value);
54 | }
55 | catch (rust::Error &e)
56 | {
57 | throw JSError(rt, e.what());
58 | }
59 | }
60 |
61 | std::vector getPropertyNames(Runtime &rt)
62 | {
63 | auto value = rho_properties(*inner, rt);
64 | return std::move(*value.release());
65 | }
66 | };
67 |
68 | ::std::unique_ptr CxxHostObject_create(
69 | rust::Box<::jsi_rs::ffi::RustHostObject> rho) noexcept
70 | {
71 | return std::make_unique(std::move(rho));
72 | }
73 |
74 | ::std::unique_ptr CxxHostObject_toHostObjectU(
75 | ::std::unique_ptr cho) noexcept
76 | {
77 | return cho;
78 | }
79 |
80 | ::std::unique_ptr CxxHostObject_fromHostObjectU(
81 | ::std::unique_ptr ho) noexcept
82 | {
83 | return std::unique_ptr(dynamic_cast(ho.release()));
84 | }
85 |
86 | ::std::shared_ptr CxxHostObject_toHostObjectS(
87 | ::std::shared_ptr cho) noexcept
88 | {
89 | return cho;
90 | }
91 |
92 | ::std::shared_ptr CxxHostObject_fromHostObjectS(
93 | ::std::shared_ptr ho) noexcept
94 | {
95 | return std::dynamic_pointer_cast(ho);
96 | }
97 |
98 | RustHostObject const &CxxHostObject_getInner(
99 | CxxHostObject const &cho) noexcept
100 | {
101 | return *cho.inner;
102 | }
103 |
104 | RustHostObject &CxxHostObject_getInnerMut(
105 | CxxHostObject &cho) noexcept
106 | {
107 | return *cho.inner;
108 | }
109 | }
110 | }
111 |
112 | #endif
113 |
--------------------------------------------------------------------------------
/jsi-sys/src/ffi/android.rs:
--------------------------------------------------------------------------------
1 | #[cxx::bridge]
2 | pub mod ffi {
3 | #[namespace = "facebook::react"]
4 | unsafe extern "C++" {
5 | include!("ReactCommon/CallInvokerHolder.h");
6 |
7 | type CallInvoker = crate::ffi::base::CallInvoker;
8 | pub type CallInvokerHolder;
9 |
10 | pub fn getCallInvoker(self: Pin<&mut CallInvokerHolder>) -> SharedPtr;
11 | }
12 | }
13 |
14 | pub use ffi::CallInvokerHolder;
15 |
--------------------------------------------------------------------------------
/jsi-sys/src/ffi/host.rs:
--------------------------------------------------------------------------------
1 | use crate::shim::{rho_get, rho_properties, rho_set, RustHostObject};
2 |
3 | #[cxx::bridge]
4 | pub(crate) mod ffi {
5 | #[namespace = "jsi_rs::ffi"]
6 | unsafe extern "C++" {
7 | include!("host.h");
8 |
9 | #[namespace = "facebook::jsi"]
10 | pub type HostObject = crate::ffi::base::HostObject;
11 | #[namespace = "facebook::jsi"]
12 | pub type Runtime = crate::ffi::base::Runtime;
13 | #[cxx_name = "Value"]
14 | #[namespace = "facebook::jsi"]
15 | pub type JsiValue = crate::ffi::base::JsiValue;
16 | #[namespace = "facebook::jsi"]
17 | pub type PropNameID = crate::ffi::base::PropNameID;
18 |
19 | pub type CxxHostObject;
20 | pub fn CxxHostObject_create(rho: Box>) -> UniquePtr;
21 |
22 | pub fn CxxHostObject_toHostObjectU(ptr: UniquePtr) -> UniquePtr;
23 | pub fn CxxHostObject_fromHostObjectU(
24 | ptr: UniquePtr,
25 | ) -> UniquePtr;
26 |
27 | pub fn CxxHostObject_toHostObjectS(ptr: SharedPtr) -> SharedPtr;
28 | pub fn CxxHostObject_fromHostObjectS(
29 | ptr: SharedPtr,
30 | ) -> SharedPtr;
31 |
32 | pub fn CxxHostObject_getInner(ptr: &CxxHostObject) -> &RustHostObject;
33 | pub fn CxxHostObject_getInnerMut(ptr: Pin<&mut CxxHostObject>) -> &mut RustHostObject;
34 | }
35 |
36 | #[namespace = "jsi_rs::ffi"]
37 | extern "Rust" {
38 | type RustHostObject<'a>;
39 |
40 | unsafe fn rho_get<'a>(
41 | _self: &mut RustHostObject<'a>,
42 | rt: Pin<&mut Runtime>,
43 | name: &PropNameID,
44 | ) -> Result>;
45 | unsafe fn rho_set<'a>(
46 | _self: &mut RustHostObject<'a>,
47 | rt: Pin<&mut Runtime>,
48 | name: &PropNameID,
49 | value: &JsiValue,
50 | ) -> Result<()>;
51 | unsafe fn rho_properties<'a>(
52 | _self: &mut RustHostObject<'a>,
53 | rt: Pin<&mut Runtime>,
54 | ) -> UniquePtr>;
55 | }
56 | }
57 |
58 | pub use ffi::*;
59 |
--------------------------------------------------------------------------------
/jsi-sys/src/ffi/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(target_os = "android")]
2 | mod android;
3 | mod base;
4 | mod host;
5 |
6 | #[cfg(target_os = "android")]
7 | pub use android::*;
8 | pub use base::*;
9 | pub use host::*;
10 |
--------------------------------------------------------------------------------
/jsi-sys/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod ffi;
2 | mod shim;
3 |
4 | pub use ffi::*;
5 | pub use shim::*;
6 |
--------------------------------------------------------------------------------
/jsi-sys/src/shim.rs:
--------------------------------------------------------------------------------
1 | use std::pin::Pin;
2 |
3 | use crate::ffi::*;
4 | use cxx::*;
5 |
6 | impl HostObject {
7 | pub fn get(
8 | self: Pin<&mut HostObject>,
9 | rt: Pin<&mut Runtime>,
10 | name: &PropNameID,
11 | ) -> UniquePtr {
12 | unsafe { HostObject_get(self, rt, name) }
13 | }
14 |
15 | pub fn get_property_names(
16 | self: Pin<&mut HostObject>,
17 | rt: Pin<&mut Runtime>,
18 | ) -> UniquePtr> {
19 | unsafe { HostObject_getPropertyNames(self, rt) }
20 | }
21 | }
22 |
23 | #[repr(transparent)]
24 | pub struct RustHostObject<'a>(pub Box);
25 |
26 | // i wanted to put these functions inside of an impl block, but this create a
27 | // circular dependency headache on the C++ side b/c you can't access the members
28 | // of a type before it is defined, so they're just normal functions
29 |
30 | // functions are used by FFI interface, but Rust thinks it's dead code b/c it's
31 | // pub(crate)
32 | #[allow(dead_code)]
33 | pub(crate) fn rho_get(
34 | rho: &mut RustHostObject,
35 | rt: Pin<&mut Runtime>,
36 | name: &PropNameID,
37 | ) -> anyhow::Result> {
38 | rho.0.get(rt, name)
39 | }
40 |
41 | #[allow(dead_code)]
42 | pub(crate) fn rho_set(
43 | rho: &mut RustHostObject,
44 | rt: Pin<&mut Runtime>,
45 | name: &PropNameID,
46 | value: &JsiValue,
47 | ) -> anyhow::Result<()> {
48 | rho.0.set(rt, name, value)
49 | }
50 |
51 | #[allow(dead_code)]
52 | pub(crate) fn rho_properties(
53 | rho: &mut RustHostObject,
54 | rt: Pin<&mut Runtime>,
55 | ) -> UniquePtr> {
56 | unsafe {
57 | let props = rho.0.properties(rt);
58 | let mut vec = create_prop_name_vector();
59 | for prop in props {
60 | push_prop_name_vector(vec.pin_mut(), prop);
61 | }
62 | vec
63 | }
64 | }
65 |
66 | pub trait HostObjectImpl {
67 | fn get(
68 | &mut self,
69 | rt: Pin<&mut Runtime>,
70 | name: &PropNameID,
71 | ) -> anyhow::Result>;
72 | fn set(
73 | &mut self,
74 | rt: Pin<&mut Runtime>,
75 | name: &PropNameID,
76 | value: &JsiValue,
77 | ) -> anyhow::Result<()>;
78 | fn properties(&mut self, rt: Pin<&mut Runtime>) -> Vec>;
79 | }
80 |
81 | impl Runtime {
82 | pub fn evaluate_javascript(
83 | self: Pin<&mut Runtime>,
84 | buffer: &SharedPtr,
85 | source_url: &str,
86 | ) -> UniquePtr {
87 | unsafe { Runtime_evaluateJavaScript(self, buffer, source_url) }
88 | }
89 |
90 | pub fn prepare_javascript(
91 | self: Pin<&mut Runtime>,
92 | buffer: &SharedPtr,
93 | source_url: &str,
94 | ) -> SharedPtr {
95 | unsafe { Runtime_prepareJavaScript(self, buffer, source_url) }
96 | }
97 |
98 | pub fn evaluate_prepared_javascript(
99 | self: Pin<&mut Runtime>,
100 | js: &SharedPtr,
101 | ) -> UniquePtr {
102 | unsafe { Runtime_evaluatePreparedJavaScript(self, &js) }
103 | }
104 |
105 | pub fn global(self: Pin<&mut Runtime>) -> UniquePtr {
106 | unsafe { Runtime_global(self) }
107 | }
108 |
109 | pub fn description(self: Pin<&mut Runtime>) -> UniquePtr {
110 | unsafe { Runtime_description(self) }
111 | }
112 | }
113 |
114 | impl PropNameID {
115 | pub fn from_str(rt: Pin<&mut Runtime>, s: &str) -> UniquePtr {
116 | unsafe { PropNameID_forUtf8(rt, s) }
117 | }
118 |
119 | pub fn from_jsi_string(rt: Pin<&mut Runtime>, s: &JsiString) -> UniquePtr {
120 | unsafe { PropNameID_forString(rt, s) }
121 | }
122 |
123 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr {
124 | unsafe { PropNameID_toUtf8(self, rt) }
125 | }
126 |
127 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool {
128 | unsafe { PropNameID_compare(rt, self, other) }
129 | }
130 | }
131 |
132 | impl JsiSymbol {
133 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr {
134 | unsafe { Symbol_toString(self, rt) }
135 | }
136 |
137 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool {
138 | unsafe { Symbol_compare(rt, self, other) }
139 | }
140 | }
141 |
142 | impl JsiString {
143 | pub fn from_str(rt: Pin<&mut Runtime>, s: &str) -> UniquePtr {
144 | unsafe { String_fromUtf8(rt, s) }
145 | }
146 |
147 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr {
148 | unsafe { String_toString(self, rt) }
149 | }
150 |
151 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool {
152 | unsafe { String_compare(rt, self, other) }
153 | }
154 | }
155 |
156 | impl JsiObject {
157 | pub fn new(rt: Pin<&mut Runtime>) -> UniquePtr {
158 | unsafe { Object_create(rt) }
159 | }
160 |
161 | pub fn from_host_object(rt: Pin<&mut Runtime>, ho: SharedPtr) -> UniquePtr {
162 | unsafe { Object_createFromHostObjectShared(rt, ho) }
163 | }
164 |
165 | pub fn compare(&self, other: &Self, rt: Pin<&mut Runtime>) -> bool {
166 | unsafe { Object_compare(rt, self, other) }
167 | }
168 |
169 | pub fn get_property(&self, rt: Pin<&mut Runtime>, prop: &PropNameID) -> UniquePtr {
170 | unsafe { Object_getProperty(self, rt, prop) }
171 | }
172 |
173 | pub fn set_property(
174 | self: Pin<&mut Self>,
175 | rt: Pin<&mut Runtime>,
176 | prop: &PropNameID,
177 | value: &JsiValue,
178 | ) {
179 | unsafe { Object_setProperty(self, rt, prop, value) }
180 | }
181 |
182 | pub fn as_array(&self, rt: Pin<&mut Runtime>) -> Option> {
183 | unsafe { Object_asArray(self, rt).ok() }
184 | }
185 |
186 | pub fn as_array_buffer(&self, rt: Pin<&mut Runtime>) -> Option> {
187 | unsafe { Object_asArrayBuffer(self, rt).ok() }
188 | }
189 |
190 | pub fn as_function(&self, rt: Pin<&mut Runtime>) -> Option> {
191 | unsafe { Object_asFunction(self, rt).ok() }
192 | }
193 |
194 | pub fn get_property_names(self: Pin<&mut Self>, rt: Pin<&mut Runtime>) -> UniquePtr {
195 | unsafe { Object_getPropertyNames(self, rt) }
196 | }
197 | }
198 |
199 | impl JsiValue {
200 | pub fn undefined() -> UniquePtr {
201 | unsafe { Value_fromUndefined() }
202 | }
203 |
204 | pub fn null() -> UniquePtr {
205 | unsafe { Value_fromNull() }
206 | }
207 |
208 | pub fn int(i: i32) -> UniquePtr {
209 | unsafe { Value_fromInt(i) }
210 | }
211 |
212 | pub fn bool(b: bool) -> UniquePtr {
213 | unsafe { Value_fromBool(b) }
214 | }
215 |
216 | pub fn double(d: f64) -> UniquePtr {
217 | unsafe { Value_fromDouble(d) }
218 | }
219 |
220 | pub fn object(rt: Pin<&mut Runtime>, o: &JsiObject) -> UniquePtr {
221 | unsafe { Value_copyFromObject(rt, o) }
222 | }
223 |
224 | pub fn symbol(rt: Pin<&mut Runtime>, s: &JsiSymbol) -> UniquePtr {
225 | unsafe { Value_copyFromSymbol(rt, s) }
226 | }
227 |
228 | pub fn string(rt: Pin<&mut Runtime>, s: &JsiString) -> UniquePtr {
229 | unsafe { Value_copyFromString(rt, s) }
230 | }
231 |
232 | pub fn from_json(rt: Pin<&mut Runtime>, json: &str) -> UniquePtr {
233 | unsafe { Value_fromJson(rt, json) }
234 | }
235 |
236 | pub fn as_object(&self, rt: Pin<&mut Runtime>) -> Result, cxx::Exception> {
237 | unsafe { Value_asObject(self, rt) }
238 | }
239 |
240 | pub fn as_symbol(&self, rt: Pin<&mut Runtime>) -> Result, cxx::Exception> {
241 | unsafe { Value_asSymbol(self, rt) }
242 | }
243 |
244 | pub fn as_string(&self, rt: Pin<&mut Runtime>) -> Result, cxx::Exception> {
245 | unsafe { Value_asString(self, rt) }
246 | }
247 |
248 | pub fn to_string(&self, rt: Pin<&mut Runtime>) -> UniquePtr {
249 | unsafe { Value_toString(self, rt) }
250 | }
251 | }
252 |
253 | impl JsiWeakObject {
254 | pub fn from_object(rt: Pin<&mut Runtime>, object: &JsiObject) -> UniquePtr {
255 | unsafe { WeakObject_fromObject(rt, object) }
256 | }
257 |
258 | pub fn lock(self: Pin<&mut Self>, rt: Pin<&mut Runtime>) -> UniquePtr {
259 | unsafe { WeakObject_lock(self, rt) }
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/jsi-sys/vendor:
--------------------------------------------------------------------------------
1 | ../vendor
--------------------------------------------------------------------------------
/jsi-tests/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "anyhow"
7 | version = "1.0.75"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
10 |
11 | [[package]]
12 | name = "better_any"
13 | version = "0.1.1"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "b359aebd937c17c725e19efcb661200883f04c49c53e7132224dac26da39d4a0"
16 | dependencies = [
17 | "better_typeid_derive",
18 | ]
19 |
20 | [[package]]
21 | name = "better_typeid_derive"
22 | version = "0.1.1"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "3deeecb812ca5300b7d3f66f730cc2ebd3511c3d36c691dd79c165d5b19a26e3"
25 | dependencies = [
26 | "proc-macro2",
27 | "quote",
28 | "syn 1.0.109",
29 | ]
30 |
31 | [[package]]
32 | name = "cc"
33 | version = "1.0.83"
34 | source = "registry+https://github.com/rust-lang/crates.io-index"
35 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
36 | dependencies = [
37 | "libc",
38 | ]
39 |
40 | [[package]]
41 | name = "codespan-reporting"
42 | version = "0.11.1"
43 | source = "registry+https://github.com/rust-lang/crates.io-index"
44 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
45 | dependencies = [
46 | "termcolor",
47 | "unicode-width",
48 | ]
49 |
50 | [[package]]
51 | name = "cxx"
52 | version = "1.0.106"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28"
55 | dependencies = [
56 | "cc",
57 | "cxxbridge-flags",
58 | "cxxbridge-macro",
59 | "link-cplusplus",
60 | ]
61 |
62 | [[package]]
63 | name = "cxx-build"
64 | version = "1.0.106"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285"
67 | dependencies = [
68 | "cc",
69 | "codespan-reporting",
70 | "once_cell",
71 | "proc-macro2",
72 | "quote",
73 | "scratch",
74 | "syn 2.0.29",
75 | ]
76 |
77 | [[package]]
78 | name = "cxxbridge-flags"
79 | version = "1.0.106"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809"
82 |
83 | [[package]]
84 | name = "cxxbridge-macro"
85 | version = "1.0.106"
86 | source = "registry+https://github.com/rust-lang/crates.io-index"
87 | checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d"
88 | dependencies = [
89 | "proc-macro2",
90 | "quote",
91 | "syn 2.0.29",
92 | ]
93 |
94 | [[package]]
95 | name = "dunce"
96 | version = "1.0.4"
97 | source = "registry+https://github.com/rust-lang/crates.io-index"
98 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
99 |
100 | [[package]]
101 | name = "libc"
102 | version = "0.2.147"
103 | source = "registry+https://github.com/rust-lang/crates.io-index"
104 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
105 |
106 | [[package]]
107 | name = "link-cplusplus"
108 | version = "1.0.9"
109 | source = "registry+https://github.com/rust-lang/crates.io-index"
110 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
111 | dependencies = [
112 | "cc",
113 | ]
114 |
115 | [[package]]
116 | name = "log"
117 | version = "0.4.20"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
120 |
121 | [[package]]
122 | name = "once_cell"
123 | version = "1.18.0"
124 | source = "registry+https://github.com/rust-lang/crates.io-index"
125 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
126 |
127 | [[package]]
128 | name = "proc-macro2"
129 | version = "1.0.66"
130 | source = "registry+https://github.com/rust-lang/crates.io-index"
131 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
132 | dependencies = [
133 | "unicode-ident",
134 | ]
135 |
136 | [[package]]
137 | name = "quote"
138 | version = "1.0.33"
139 | source = "registry+https://github.com/rust-lang/crates.io-index"
140 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
141 | dependencies = [
142 | "proc-macro2",
143 | ]
144 |
145 | [[package]]
146 | name = "scratch"
147 | version = "1.0.7"
148 | source = "registry+https://github.com/rust-lang/crates.io-index"
149 | checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
150 |
151 | [[package]]
152 | name = "splicer-js-tests"
153 | version = "0.1.0"
154 | dependencies = [
155 | "anyhow",
156 | "cxx",
157 | "cxx-build",
158 | "dunce",
159 | "splicer-jsi",
160 | "splicer-jsi-sys",
161 | ]
162 |
163 | [[package]]
164 | name = "splicer-jsi"
165 | version = "0.2.0"
166 | dependencies = [
167 | "anyhow",
168 | "better_any",
169 | "cxx",
170 | "log",
171 | "splicer-jsi-sys",
172 | ]
173 |
174 | [[package]]
175 | name = "splicer-jsi-sys"
176 | version = "0.1.0"
177 | dependencies = [
178 | "anyhow",
179 | "cxx",
180 | "cxx-build",
181 | "dunce",
182 | ]
183 |
184 | [[package]]
185 | name = "syn"
186 | version = "1.0.109"
187 | source = "registry+https://github.com/rust-lang/crates.io-index"
188 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
189 | dependencies = [
190 | "proc-macro2",
191 | "quote",
192 | "unicode-ident",
193 | ]
194 |
195 | [[package]]
196 | name = "syn"
197 | version = "2.0.29"
198 | source = "registry+https://github.com/rust-lang/crates.io-index"
199 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
200 | dependencies = [
201 | "proc-macro2",
202 | "quote",
203 | "unicode-ident",
204 | ]
205 |
206 | [[package]]
207 | name = "termcolor"
208 | version = "1.2.0"
209 | source = "registry+https://github.com/rust-lang/crates.io-index"
210 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
211 | dependencies = [
212 | "winapi-util",
213 | ]
214 |
215 | [[package]]
216 | name = "unicode-ident"
217 | version = "1.0.11"
218 | source = "registry+https://github.com/rust-lang/crates.io-index"
219 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
220 |
221 | [[package]]
222 | name = "unicode-width"
223 | version = "0.1.10"
224 | source = "registry+https://github.com/rust-lang/crates.io-index"
225 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
226 |
227 | [[package]]
228 | name = "winapi"
229 | version = "0.3.9"
230 | source = "registry+https://github.com/rust-lang/crates.io-index"
231 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
232 | dependencies = [
233 | "winapi-i686-pc-windows-gnu",
234 | "winapi-x86_64-pc-windows-gnu",
235 | ]
236 |
237 | [[package]]
238 | name = "winapi-i686-pc-windows-gnu"
239 | version = "0.4.0"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
242 |
243 | [[package]]
244 | name = "winapi-util"
245 | version = "0.1.5"
246 | source = "registry+https://github.com/rust-lang/crates.io-index"
247 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
248 | dependencies = [
249 | "winapi",
250 | ]
251 |
252 | [[package]]
253 | name = "winapi-x86_64-pc-windows-gnu"
254 | version = "0.4.0"
255 | source = "registry+https://github.com/rust-lang/crates.io-index"
256 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
257 |
--------------------------------------------------------------------------------
/jsi-tests/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "jsi-tests"
3 | version = "0.3.1-alpha.1"
4 | edition = "2021"
5 | links = "hermes"
6 | publish = false
7 |
8 | [dependencies]
9 | cxx = "1.0"
10 | anyhow = "1.0"
11 | jsi-sys = { path = "../jsi-sys" }
12 | jsi = { path = "../jsi" }
13 |
14 | [build-dependencies]
15 | cxx-build = "1.0"
16 | dunce = "1.0"
17 |
--------------------------------------------------------------------------------
/jsi-tests/README.md:
--------------------------------------------------------------------------------
1 | # `js-tests`
2 |
3 | This crate contains tests for `jsi-rs`. The tests are run against
4 | [Hermes](https://github.com/facebook/hermes), which is Facebook's reference
5 | implementation of JavaScript for React Native.
6 |
7 | ## Building and running tests
8 |
9 | Hermes is a Git submodule of this repository, so it should be cloned
10 | automatically when you clone this repo. The initial build of this crate will
11 | take a while because it builds Hermes first.
12 |
13 | Once that build finishes, you can run `cargo test` as normal.
14 |
--------------------------------------------------------------------------------
/jsi-tests/build.rs:
--------------------------------------------------------------------------------
1 | use std::{env, path::PathBuf, process::Command};
2 |
3 | fn main() {
4 | let pkg_base = env::var_os("CARGO_MANIFEST_DIR").unwrap();
5 | let pkg_base = PathBuf::from(pkg_base);
6 |
7 | let hermes_build_status = Command::new("bash")
8 | .args([pkg_base.join("../vendor/build-hermes.sh")])
9 | .current_dir(pkg_base.join("../vendor"))
10 | .output()
11 | .expect("hermes build script could not be executed");
12 |
13 | if !hermes_build_status.status.success() {
14 | panic!(
15 | "hermes build script failed\n\nstdout: {}\n\nstderr: {}",
16 | String::from_utf8_lossy(&hermes_build_status.stdout),
17 | String::from_utf8_lossy(&hermes_build_status.stderr),
18 | )
19 | }
20 |
21 | let rn_base = pkg_base.join("../vendor/react-native/packages/react-native");
22 |
23 | let includes = vec![
24 | rn_base.join("React"),
25 | rn_base.join("React/Base"),
26 | rn_base.join("ReactCommon/jsi"),
27 | rn_base.join("ReactCommon/callinvoker"),
28 | pkg_base.join("../vendor/hermes/API"),
29 | pkg_base.join("../vendor/hermes/public"),
30 | pkg_base.join("include"),
31 | ];
32 |
33 | for include in &includes {
34 | println!("cargo:rerun-if-changed={:?}", include);
35 | }
36 |
37 | let includes: Vec<_> = IntoIterator::into_iter(includes)
38 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing include path {:?}", p)))
39 | .collect();
40 |
41 | let compiles: Vec = vec![];
42 |
43 | let compiles: Vec<_> = IntoIterator::into_iter(compiles)
44 | .map(|p| dunce::canonicalize(&p).expect(&format!("missing compile file {:?}", p)))
45 | .collect();
46 |
47 | cxx_build::CFG
48 | .exported_header_dirs
49 | .extend(includes.iter().map(|e| e.as_path()));
50 |
51 | let bridges = vec!["src/ffi.rs"];
52 |
53 | for bridge in &bridges {
54 | println!("cargo:rerun-if-changed={}", bridge);
55 | }
56 |
57 | cxx_build::bridges(bridges)
58 | .flag_if_supported("-std=c++17")
59 | .files(compiles)
60 | .compile("js-tests");
61 |
62 | println!("cargo:rustc-link-lib=hermes");
63 | println!(
64 | "cargo:rustc-link-search={}",
65 | pkg_base
66 | .join("../vendor/hermes/build/API/hermes/")
67 | .to_string_lossy()
68 | );
69 | println!(
70 | "cargo:rustc-env=LD_LIBRARY_PATH={}",
71 | pkg_base
72 | .join("../vendor/hermes/build/API/hermes/")
73 | .to_string_lossy()
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/jsi-tests/include/helper.h:
--------------------------------------------------------------------------------
1 | #include
2 | // #include
3 | #include
4 | #include
5 | #include "rust/cxx.h"
6 |
7 | std::unique_ptr cast_hermes_runtime(std::unique_ptr runtime)
8 | {
9 | return runtime;
10 | }
11 |
12 | std::unique_ptr create_runtime_config()
13 | {
14 | return std::make_unique();
15 | }
16 |
17 | std::unique_ptr eval_js(facebook::jsi::Runtime& rt, rust::Str js)
18 | {
19 | // std::string bytecode;
20 | // assert(hermes::compileJS(std::string(js), bytecode));
21 | auto out = rt.evaluateJavaScript(
22 | std::make_unique(std::string(js)),
23 | "");
24 | return std::make_unique(std::move(out));
25 | }
26 |
--------------------------------------------------------------------------------
/jsi-tests/src/ffi.rs:
--------------------------------------------------------------------------------
1 | #[cxx::bridge]
2 | pub mod bridge {
3 | unsafe extern "C++" {
4 | include!("helper.h");
5 | include!("jsi/jsi.h");
6 |
7 | #[namespace = "facebook::jsi"]
8 | type Runtime = jsi_sys::Runtime;
9 | #[namespace = "facebook::jsi"]
10 | #[cxx_name = "Value"]
11 | type JsiValue = jsi_sys::JsiValue;
12 |
13 | pub fn cast_hermes_runtime(ptr: UniquePtr) -> UniquePtr;
14 | pub fn create_runtime_config() -> UniquePtr;
15 | pub fn eval_js(rt: Pin<&mut Runtime>, js: &str) -> UniquePtr;
16 | }
17 |
18 | #[namespace = "hermes::vm"]
19 | extern "C++" {
20 | include!("hermes/Public/RuntimeConfig.h");
21 |
22 | type RuntimeConfig;
23 | }
24 |
25 | #[namespace = "facebook::hermes"]
26 | unsafe extern "C++" {
27 | include!("hermes/hermes.h");
28 |
29 | type HermesRuntime;
30 |
31 | #[cxx_name = "makeHermesRuntime"]
32 | pub fn create_hermes_runtime(config: &RuntimeConfig) -> UniquePtr;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/jsi-tests/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod ffi;
2 |
--------------------------------------------------------------------------------
/jsi-tests/tests/basic.rs:
--------------------------------------------------------------------------------
1 | use cxx::UniquePtr;
2 |
3 | use jsi_tests::ffi::bridge::*;
4 |
5 | #[test]
6 | fn create_runtime() {
7 | let config = create_runtime_config();
8 | let rt = create_hermes_runtime(&*config);
9 | let mut rt: UniquePtr = cast_hermes_runtime(rt);
10 | let rt = rt.as_mut().unwrap();
11 | assert_eq!("HermesRuntime", rt.description().to_string());
12 | }
13 |
14 | #[test]
15 | fn evaluate_exprs() {
16 | let config = create_runtime_config();
17 | let rt = create_hermes_runtime(&*config);
18 | let mut rt: UniquePtr = cast_hermes_runtime(rt);
19 |
20 | let out = eval_js(rt.pin_mut(), "1 + 1");
21 | let out = out.get_number().unwrap();
22 | assert_eq!(2., out);
23 |
24 | let out = eval_js(rt.pin_mut(), "'hello'");
25 | let out = out.as_string(rt.pin_mut()).unwrap();
26 | assert_eq!("hello", out.to_string(rt.pin_mut()).to_string());
27 | }
28 |
--------------------------------------------------------------------------------
/jsi-tests/tests/common.rs:
--------------------------------------------------------------------------------
1 | use cxx::UniquePtr;
2 |
3 | use jsi_tests::ffi::bridge::*;
4 |
5 | pub fn create_raw_runtime() -> UniquePtr {
6 | let config = create_runtime_config();
7 | let rt = create_hermes_runtime(&*config);
8 | cast_hermes_runtime(rt)
9 | }
10 |
--------------------------------------------------------------------------------
/jsi-tests/tests/function.rs:
--------------------------------------------------------------------------------
1 | use cxx::UniquePtr;
2 |
3 | use jsi_tests::ffi::bridge::*;
4 |
5 | #[test]
6 | fn run_host_function() {
7 | let config = create_runtime_config();
8 | let rt = create_hermes_runtime(&*config);
9 | let mut rt: UniquePtr = cast_hermes_runtime(rt);
10 | let rt = rt.as_mut().unwrap();
11 | assert_eq!("HermesRuntime", rt.description().to_string());
12 | }
13 |
14 | #[test]
15 | fn evaluate_exprs() {
16 | let config = create_runtime_config();
17 | let rt = create_hermes_runtime(&*config);
18 | let mut rt: UniquePtr = cast_hermes_runtime(rt);
19 |
20 | let out = eval_js(rt.pin_mut(), "1 + 1");
21 | let out = out.get_number().unwrap();
22 | assert_eq!(2., out);
23 |
24 | let out = eval_js(rt.pin_mut(), "'hello'");
25 | let out = out.as_string(rt.pin_mut()).unwrap();
26 | assert_eq!("hello", out.to_string(rt.pin_mut()).to_string());
27 | }
28 |
--------------------------------------------------------------------------------
/jsi/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "anyhow"
7 | version = "1.0.75"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
10 |
11 | [[package]]
12 | name = "better_any"
13 | version = "0.1.1"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "b359aebd937c17c725e19efcb661200883f04c49c53e7132224dac26da39d4a0"
16 | dependencies = [
17 | "better_typeid_derive",
18 | ]
19 |
20 | [[package]]
21 | name = "better_typeid_derive"
22 | version = "0.1.1"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "3deeecb812ca5300b7d3f66f730cc2ebd3511c3d36c691dd79c165d5b19a26e3"
25 | dependencies = [
26 | "proc-macro2",
27 | "quote",
28 | "syn 1.0.109",
29 | ]
30 |
31 | [[package]]
32 | name = "cc"
33 | version = "1.0.83"
34 | source = "registry+https://github.com/rust-lang/crates.io-index"
35 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
36 | dependencies = [
37 | "libc",
38 | ]
39 |
40 | [[package]]
41 | name = "codespan-reporting"
42 | version = "0.11.1"
43 | source = "registry+https://github.com/rust-lang/crates.io-index"
44 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
45 | dependencies = [
46 | "termcolor",
47 | "unicode-width",
48 | ]
49 |
50 | [[package]]
51 | name = "cxx"
52 | version = "1.0.106"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28"
55 | dependencies = [
56 | "cc",
57 | "cxxbridge-flags",
58 | "cxxbridge-macro",
59 | "link-cplusplus",
60 | ]
61 |
62 | [[package]]
63 | name = "cxx-build"
64 | version = "1.0.106"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285"
67 | dependencies = [
68 | "cc",
69 | "codespan-reporting",
70 | "once_cell",
71 | "proc-macro2",
72 | "quote",
73 | "scratch",
74 | "syn 2.0.29",
75 | ]
76 |
77 | [[package]]
78 | name = "cxxbridge-flags"
79 | version = "1.0.106"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809"
82 |
83 | [[package]]
84 | name = "cxxbridge-macro"
85 | version = "1.0.106"
86 | source = "registry+https://github.com/rust-lang/crates.io-index"
87 | checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d"
88 | dependencies = [
89 | "proc-macro2",
90 | "quote",
91 | "syn 2.0.29",
92 | ]
93 |
94 | [[package]]
95 | name = "dunce"
96 | version = "1.0.4"
97 | source = "registry+https://github.com/rust-lang/crates.io-index"
98 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
99 |
100 | [[package]]
101 | name = "libc"
102 | version = "0.2.147"
103 | source = "registry+https://github.com/rust-lang/crates.io-index"
104 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
105 |
106 | [[package]]
107 | name = "link-cplusplus"
108 | version = "1.0.9"
109 | source = "registry+https://github.com/rust-lang/crates.io-index"
110 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
111 | dependencies = [
112 | "cc",
113 | ]
114 |
115 | [[package]]
116 | name = "log"
117 | version = "0.4.20"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
120 |
121 | [[package]]
122 | name = "once_cell"
123 | version = "1.18.0"
124 | source = "registry+https://github.com/rust-lang/crates.io-index"
125 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
126 |
127 | [[package]]
128 | name = "proc-macro2"
129 | version = "1.0.66"
130 | source = "registry+https://github.com/rust-lang/crates.io-index"
131 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
132 | dependencies = [
133 | "unicode-ident",
134 | ]
135 |
136 | [[package]]
137 | name = "quote"
138 | version = "1.0.33"
139 | source = "registry+https://github.com/rust-lang/crates.io-index"
140 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
141 | dependencies = [
142 | "proc-macro2",
143 | ]
144 |
145 | [[package]]
146 | name = "scratch"
147 | version = "1.0.7"
148 | source = "registry+https://github.com/rust-lang/crates.io-index"
149 | checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
150 |
151 | [[package]]
152 | name = "splicer-jsi"
153 | version = "0.2.0"
154 | dependencies = [
155 | "anyhow",
156 | "better_any",
157 | "cxx",
158 | "log",
159 | "splicer-jsi-sys",
160 | ]
161 |
162 | [[package]]
163 | name = "splicer-jsi-sys"
164 | version = "0.1.0"
165 | dependencies = [
166 | "anyhow",
167 | "cxx",
168 | "cxx-build",
169 | "dunce",
170 | ]
171 |
172 | [[package]]
173 | name = "syn"
174 | version = "1.0.109"
175 | source = "registry+https://github.com/rust-lang/crates.io-index"
176 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
177 | dependencies = [
178 | "proc-macro2",
179 | "quote",
180 | "unicode-ident",
181 | ]
182 |
183 | [[package]]
184 | name = "syn"
185 | version = "2.0.29"
186 | source = "registry+https://github.com/rust-lang/crates.io-index"
187 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
188 | dependencies = [
189 | "proc-macro2",
190 | "quote",
191 | "unicode-ident",
192 | ]
193 |
194 | [[package]]
195 | name = "termcolor"
196 | version = "1.2.0"
197 | source = "registry+https://github.com/rust-lang/crates.io-index"
198 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
199 | dependencies = [
200 | "winapi-util",
201 | ]
202 |
203 | [[package]]
204 | name = "unicode-ident"
205 | version = "1.0.11"
206 | source = "registry+https://github.com/rust-lang/crates.io-index"
207 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
208 |
209 | [[package]]
210 | name = "unicode-width"
211 | version = "0.1.10"
212 | source = "registry+https://github.com/rust-lang/crates.io-index"
213 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
214 |
215 | [[package]]
216 | name = "winapi"
217 | version = "0.3.9"
218 | source = "registry+https://github.com/rust-lang/crates.io-index"
219 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
220 | dependencies = [
221 | "winapi-i686-pc-windows-gnu",
222 | "winapi-x86_64-pc-windows-gnu",
223 | ]
224 |
225 | [[package]]
226 | name = "winapi-i686-pc-windows-gnu"
227 | version = "0.4.0"
228 | source = "registry+https://github.com/rust-lang/crates.io-index"
229 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
230 |
231 | [[package]]
232 | name = "winapi-util"
233 | version = "0.1.5"
234 | source = "registry+https://github.com/rust-lang/crates.io-index"
235 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
236 | dependencies = [
237 | "winapi",
238 | ]
239 |
240 | [[package]]
241 | name = "winapi-x86_64-pc-windows-gnu"
242 | version = "0.4.0"
243 | source = "registry+https://github.com/rust-lang/crates.io-index"
244 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
245 |
--------------------------------------------------------------------------------
/jsi/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "jsi"
3 | version = "0.3.0-alpha.5"
4 | edition = "2021"
5 | license = "MIT"
6 | description = "Write React Native JSI modules in Rust"
7 | authors = ["Ibiyemi Abiodun "]
8 | repository = "https://github.com/laptou/jsi-rs/"
9 |
10 | [dependencies]
11 | jsi-sys = { path = "../jsi-sys", version = "0.3.0-alpha.5" }
12 | jsi-macros = { path = "../jsi-macros", version = "0.3.1-alpha.5", optional = true }
13 | cxx = "1.0"
14 | anyhow = "1.0"
15 | log = { version = "0.4", optional = true }
16 | better_any = "0.2"
17 | serde = { version = "1.0", optional = true }
18 | thiserror = "1.0.47"
19 |
20 | [features]
21 | default = ["macros", "serde"]
22 | host-fn-trace = ["log"]
23 | js-fn-trace = ["log"]
24 | call-invoker-trace = ["log"]
25 | macros = ["jsi-macros"]
26 | serde = ["dep:serde"]
27 |
--------------------------------------------------------------------------------
/jsi/README.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/jsi/src/array.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | use crate::{sys, JsiValue, RuntimeHandle};
4 |
5 | /// A JavaScript
6 | /// [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
7 | /// Can be used to share large buffers of data with a React Native application.
8 | pub struct JsiArray<'rt>(
9 | pub(crate) cxx::UniquePtr,
10 | pub(crate) PhantomData<&'rt mut ()>,
11 | );
12 |
13 | impl<'rt> JsiArray<'rt> {
14 | pub fn new(len: usize, rt: &mut RuntimeHandle<'rt>) -> Self {
15 | Self(
16 | sys::Array_createWithLength(rt.get_inner_mut(), len),
17 | PhantomData,
18 | )
19 | }
20 |
21 | pub fn len(&self, rt: &mut RuntimeHandle<'rt>) -> usize {
22 | self.0.length(rt.get_inner_mut())
23 | }
24 |
25 | pub fn get(&self, index: usize, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> {
26 | JsiValue(
27 | sys::Array_get(&*self.0, rt.get_inner_mut(), index),
28 | PhantomData,
29 | )
30 | }
31 |
32 | pub fn set(&mut self, index: usize, value: &JsiValue<'rt>, rt: &mut RuntimeHandle<'rt>) {
33 | sys::Array_set(
34 | self.0.pin_mut(),
35 | rt.get_inner_mut(),
36 | index,
37 | value.0.as_ref().unwrap(),
38 | )
39 | }
40 |
41 | pub fn iter<'a>(&'a self, rt: &'a mut RuntimeHandle<'rt>) -> JsiArrayIter<'a, 'rt> {
42 | JsiArrayIter(self, 0, rt)
43 | }
44 | }
45 |
46 | pub struct JsiArrayIter<'a, 'rt: 'a>(&'a JsiArray<'rt>, usize, &'a mut RuntimeHandle<'rt>);
47 |
48 | impl<'a, 'rt: 'a> Iterator for JsiArrayIter<'a, 'rt> {
49 | type Item = JsiValue<'rt>;
50 |
51 | fn next(&mut self) -> Option {
52 | if self.1 < self.0.len(self.2) {
53 | let val = self.0.get(self.1, self.2);
54 | self.1 += 1;
55 | Some(val)
56 | } else {
57 | None
58 | }
59 | }
60 | }
61 |
62 | // impl<'a, 'rt: 'a> ExactSizeIterator for JsiArrayIter<'a, 'rt> {
63 | // fn len(&self) -> usize {
64 | // self.0.len(self.2)
65 | // }
66 | // }
67 |
--------------------------------------------------------------------------------
/jsi/src/array_buffer.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | use crate::{sys, RuntimeHandle};
4 |
5 | /// A JavaScript
6 | /// [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer).
7 | /// Can be used to share large buffers of data with a React Native application.
8 | pub struct JsiArrayBuffer<'rt>(
9 | pub(crate) cxx::UniquePtr,
10 | pub(crate) PhantomData<&'rt mut ()>,
11 | );
12 |
13 | impl<'rt> JsiArrayBuffer<'rt> {
14 | pub fn data(&self, rt: &mut RuntimeHandle<'rt>) -> &mut [u8] {
15 | unsafe {
16 | std::slice::from_raw_parts_mut(
17 | self.0.data(rt.get_inner_mut()),
18 | self.0.length(rt.get_inner_mut()),
19 | )
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/jsi/src/call_invoker.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | pub use sys::CallInvokerCallback;
4 |
5 | use crate::sys;
6 |
7 | /// Used to run JavaScript functions in a given runtime from Rust. Required when
8 | /// trying to call a JavaScript function from a thread other than the JS thread.
9 | #[derive(Clone)]
10 | pub struct CallInvoker<'rt>(
11 | pub(crate) cxx::SharedPtr,
12 | pub(crate) PhantomData<&'rt ()>,
13 | );
14 |
15 | unsafe impl Send for CallInvoker<'_> {}
16 | unsafe impl Sync for CallInvoker<'_> {}
17 |
18 | impl<'rt> CallInvoker<'rt> {
19 | pub fn new(ptr: cxx::SharedPtr) -> Self {
20 | CallInvoker(ptr, PhantomData)
21 | }
22 |
23 | /// WARNING: currently crashes with message "Synchronous native -> JS calls are currently not supported"
24 | pub fn invoke_sync(&self, job: CallInvokerCallback<'rt>) {
25 | #[cfg(feature = "call-invoker-trace")]
26 | log::trace!("call invoker sync call with closure at {:p}", job);
27 |
28 | unsafe {
29 | sys::CallInvoker_invokeSync(self.0.clone(), Box::into_raw(Box::new(job)) as *mut _)
30 | }
31 | }
32 |
33 | pub fn invoke_async(&self, job: CallInvokerCallback<'rt>) {
34 | #[cfg(feature = "call-invoker-trace")]
35 | log::trace!("call invoker async call with closure at {:p}", job);
36 |
37 | unsafe {
38 | sys::CallInvoker_invokeAsync(self.0.clone(), Box::into_raw(Box::new(job)) as *mut _)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/jsi/src/convert/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod de;
2 | pub mod ser;
3 |
4 | pub use de::JsiDeserializer;
5 | pub use ser::JsiSerializer;
6 |
7 | use jsi::{JsiValue, RuntimeHandle};
8 | use serde::{Deserialize, Serialize};
9 |
10 | pub trait SerializeValue {
11 | fn serialize_value<'rt>(
12 | &self,
13 | rt: &mut RuntimeHandle<'rt>,
14 | ) -> Result, ser::JsiSerializeError>;
15 | }
16 |
17 | impl SerializeValue for T {
18 | fn serialize_value<'rt>(
19 | &self,
20 | rt: &mut RuntimeHandle<'rt>,
21 | ) -> Result, ser::JsiSerializeError> {
22 | T::serialize(&self, JsiSerializer::new(rt))
23 | }
24 | }
25 |
26 | pub trait DeserializeValue: Sized {
27 | fn deserialize_value<'rt>(
28 | val: JsiValue<'rt>,
29 | rt: &mut RuntimeHandle<'rt>,
30 | ) -> Result;
31 | }
32 |
33 | impl<'de, T: Deserialize<'de>> DeserializeValue for T {
34 | fn deserialize_value<'rt>(
35 | val: JsiValue<'rt>,
36 | rt: &mut RuntimeHandle<'rt>,
37 | ) -> Result {
38 | Self::deserialize(JsiDeserializer::new(val, rt))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/jsi/src/convert/ser.rs:
--------------------------------------------------------------------------------
1 | use jsi::*;
2 | use serde::{
3 | ser::{
4 | SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,
5 | SerializeTupleStruct, SerializeTupleVariant,
6 | },
7 | Serializer,
8 | };
9 |
10 | /// Serializes objects into JavaScript via JSI. Useful for transferring Rust
11 | /// structures and objects from `serde_json` into JavaScript.
12 | pub struct JsiSerializer<'a, 'rt: 'a> {
13 | rt: &'a mut RuntimeHandle<'rt>,
14 | }
15 |
16 | impl<'a, 'rt: 'a> JsiSerializer<'a, 'rt> {
17 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self {
18 | Self { rt }
19 | }
20 | }
21 |
22 | impl<'a, 'rt: 'a> Serializer for JsiSerializer<'a, 'rt> {
23 | type Ok = JsiValue<'rt>;
24 | type Error = JsiSerializeError;
25 |
26 | type SerializeSeq = JsiSeqSerializer<'a, 'rt>;
27 | type SerializeTuple = JsiTupleSerializer<'a, 'rt>;
28 | type SerializeTupleStruct = JsiTupleVariantSerializer<'a, 'rt>;
29 | type SerializeTupleVariant = JsiTupleVariantSerializer<'a, 'rt>;
30 | type SerializeMap = JsiMapSerializer<'a, 'rt>;
31 | type SerializeStruct = JsiStructSerializer<'a, 'rt>;
32 | type SerializeStructVariant = JsiStructVariantSerializer<'a, 'rt>;
33 |
34 | fn is_human_readable(&self) -> bool {
35 | true
36 | }
37 |
38 | fn serialize_bool(self, v: bool) -> Result {
39 | Ok(JsiValue::new_bool(v))
40 | }
41 |
42 | fn serialize_i8(self, v: i8) -> Result {
43 | Ok(JsiValue::new_number(v as f64))
44 | }
45 |
46 | fn serialize_i16(self, v: i16) -> Result {
47 | Ok(JsiValue::new_number(v as f64))
48 | }
49 |
50 | fn serialize_i32(self, v: i32) -> Result {
51 | Ok(JsiValue::new_number(v as f64))
52 | }
53 |
54 | fn serialize_i64(self, v: i64) -> Result {
55 | Ok(JsiValue::new_number(v as f64))
56 | }
57 |
58 | fn serialize_u8(self, v: u8) -> Result {
59 | Ok(JsiValue::new_number(v as f64))
60 | }
61 |
62 | fn serialize_u16(self, v: u16) -> Result {
63 | Ok(JsiValue::new_number(v as f64))
64 | }
65 |
66 | fn serialize_u32(self, v: u32) -> Result {
67 | Ok(JsiValue::new_number(v as f64))
68 | }
69 |
70 | fn serialize_u64(self, v: u64) -> Result {
71 | Ok(JsiValue::new_number(v as f64))
72 | }
73 |
74 | fn serialize_f32(self, v: f32) -> Result {
75 | Ok(JsiValue::new_number(v as f64))
76 | }
77 |
78 | fn serialize_f64(self, v: f64) -> Result {
79 | Ok(JsiValue::new_number(v))
80 | }
81 |
82 | fn serialize_char(self, v: char) -> Result {
83 | Ok(JsiString::new(v.encode_utf8(&mut [0; 4]), self.rt).into_value(self.rt))
84 | }
85 |
86 | fn serialize_str(self, v: &str) -> Result {
87 | Ok(JsiString::new(v, self.rt).into_value(self.rt))
88 | }
89 |
90 | fn serialize_bytes(mut self, v: &[u8]) -> Result {
91 | let rt = &mut self.rt;
92 | let array_buffer_ctor = rt.global().get(PropName::new("ArrayBuffer", rt), rt);
93 | let array_buffer_ctor: JsiFn = array_buffer_ctor
94 | .try_into_js(rt)
95 | .expect("ArrayBuffer constructor is not a function");
96 | let array_buffer = array_buffer_ctor
97 | .call_as_constructor(vec![JsiValue::new_number(v.len() as f64)], rt)
98 | .expect("ArrayBuffer constructor threw an exception");
99 | let array_buffer: JsiArrayBuffer = array_buffer
100 | .try_into_js(rt)
101 | .expect("ArrayBuffer constructor did not return an ArrayBuffer");
102 |
103 | array_buffer.data(rt).copy_from_slice(v);
104 |
105 | Ok(array_buffer.into_value(rt))
106 | }
107 |
108 | fn serialize_none(self) -> Result {
109 | Ok(JsiValue::new_null())
110 | }
111 |
112 | fn serialize_some(self, value: &T) -> Result
113 | where
114 | T: serde::Serialize,
115 | {
116 | value.serialize(self)
117 | }
118 |
119 | fn serialize_unit(self) -> Result {
120 | Ok(JsiObject::new(self.rt).into_value(self.rt))
121 | }
122 |
123 | fn serialize_unit_struct(self, _name: &'static str) -> Result {
124 | self.serialize_unit()
125 | }
126 |
127 | fn serialize_unit_variant(
128 | self,
129 | _name: &'static str,
130 | _variant_index: u32,
131 | variant: &'static str,
132 | ) -> Result {
133 | self.serialize_str(variant)
134 | }
135 |
136 | fn serialize_newtype_struct(
137 | self,
138 | _name: &'static str,
139 | value: &T,
140 | ) -> Result
141 | where
142 | T: serde::Serialize,
143 | {
144 | value.serialize(self)
145 | }
146 |
147 | fn serialize_newtype_variant(
148 | self,
149 | _name: &'static str,
150 | _variant_index: u32,
151 | _variant: &'static str,
152 | value: &T,
153 | ) -> Result
154 | where
155 | T: serde::Serialize,
156 | {
157 | value.serialize(self)
158 | }
159 |
160 | fn serialize_seq(self, len: Option) -> Result {
161 | match len {
162 | Some(len) => Ok(JsiSeqSerializer::new(len, self.rt)),
163 | None => Err(JsiSerializeError::UnsizedSequence),
164 | }
165 | }
166 |
167 | fn serialize_tuple(self, _len: usize) -> Result {
168 | Ok(JsiTupleSerializer::new(self.rt))
169 | }
170 |
171 | fn serialize_tuple_struct(
172 | self,
173 | name: &'static str,
174 | _len: usize,
175 | ) -> Result {
176 | Ok(JsiTupleVariantSerializer::new(name, self.rt))
177 | }
178 |
179 | fn serialize_tuple_variant(
180 | self,
181 | _name: &'static str,
182 | _variant_index: u32,
183 | variant: &'static str,
184 | _len: usize,
185 | ) -> Result {
186 | Ok(JsiTupleVariantSerializer::new(variant, self.rt))
187 | }
188 |
189 | fn serialize_map(self, _len: Option) -> Result {
190 | Ok(JsiMapSerializer::new(self.rt))
191 | }
192 |
193 | fn serialize_struct(
194 | self,
195 | _name: &'static str,
196 | _len: usize,
197 | ) -> Result {
198 | Ok(JsiStructSerializer::new(self.rt))
199 | }
200 |
201 | fn serialize_struct_variant(
202 | self,
203 | _name: &'static str,
204 | _variant_index: u32,
205 | variant: &'static str,
206 | _len: usize,
207 | ) -> Result {
208 | Ok(JsiStructVariantSerializer::new(variant, self.rt))
209 | }
210 | }
211 |
212 | pub struct JsiSeqSerializer<'a, 'rt: 'a> {
213 | rt: &'a mut RuntimeHandle<'rt>,
214 | arr: JsiArray<'rt>,
215 | idx: usize,
216 | }
217 |
218 | impl<'a, 'rt: 'a> JsiSeqSerializer<'a, 'rt> {
219 | pub fn new(len: usize, rt: &'a mut RuntimeHandle<'rt>) -> Self {
220 | let arr = JsiArray::new(len, rt);
221 | Self { rt, arr, idx: 0 }
222 | }
223 | }
224 |
225 | impl<'a, 'rt: 'a> SerializeSeq for JsiSeqSerializer<'a, 'rt> {
226 | type Ok = JsiValue<'rt>;
227 | type Error = JsiSerializeError;
228 |
229 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error>
230 | where
231 | T: serde::Serialize,
232 | {
233 | self.arr.set(
234 | self.idx,
235 | &value.serialize(JsiSerializer { rt: self.rt })?,
236 | self.rt,
237 | );
238 | self.idx += 1;
239 |
240 | Ok(())
241 | }
242 |
243 | fn end(self) -> Result {
244 | Ok(self.arr.into_value(self.rt))
245 | }
246 | }
247 |
248 | pub struct JsiTupleSerializer<'a, 'rt: 'a> {
249 | rt: &'a mut RuntimeHandle<'rt>,
250 | obj: JsiObject<'rt>,
251 | idx: usize,
252 | }
253 |
254 | impl<'a, 'rt: 'a> JsiTupleSerializer<'a, 'rt> {
255 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self {
256 | let obj = JsiObject::new(rt);
257 | Self { rt, obj, idx: 0 }
258 | }
259 | }
260 |
261 | impl<'a, 'rt: 'a> SerializeTuple for JsiTupleSerializer<'a, 'rt> {
262 | type Ok = JsiValue<'rt>;
263 | type Error = JsiSerializeError;
264 |
265 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error>
266 | where
267 | T: serde::Serialize,
268 | {
269 | self.obj.set(
270 | PropName::new(self.idx.to_string().as_str(), self.rt),
271 | &value.serialize(JsiSerializer { rt: self.rt })?,
272 | self.rt,
273 | );
274 | self.idx += 1;
275 |
276 | Ok(())
277 | }
278 |
279 | fn end(self) -> Result {
280 | Ok(self.obj.into_value(self.rt))
281 | }
282 | }
283 |
284 | pub struct JsiTupleVariantSerializer<'a, 'rt: 'a> {
285 | inner: JsiTupleSerializer<'a, 'rt>,
286 | variant: &'static str,
287 | }
288 |
289 | impl<'a, 'rt: 'a> JsiTupleVariantSerializer<'a, 'rt> {
290 | pub fn new(variant: &'static str, rt: &'a mut RuntimeHandle<'rt>) -> Self {
291 | Self {
292 | inner: JsiTupleSerializer::new(rt),
293 | variant,
294 | }
295 | }
296 | }
297 |
298 | impl<'a, 'rt: 'a> SerializeTupleVariant for JsiTupleVariantSerializer<'a, 'rt> {
299 | type Ok = JsiValue<'rt>;
300 | type Error = JsiSerializeError;
301 |
302 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error>
303 | where
304 | T: serde::Serialize,
305 | {
306 | self.inner.serialize_element(value)
307 | }
308 |
309 | fn end(mut self) -> Result {
310 | self.inner.obj.set(
311 | PropName::new("__variant", self.inner.rt),
312 | &JsiValue::new_string(self.variant, self.inner.rt),
313 | self.inner.rt,
314 | );
315 | self.inner.end()
316 | }
317 | }
318 |
319 | impl<'a, 'rt: 'a> SerializeTupleStruct for JsiTupleVariantSerializer<'a, 'rt> {
320 | type Ok = JsiValue<'rt>;
321 | type Error = JsiSerializeError;
322 |
323 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error>
324 | where
325 | T: serde::Serialize,
326 | {
327 | self.inner.serialize_element(value)
328 | }
329 |
330 | fn end(mut self) -> Result {
331 | self.inner.obj.set(
332 | PropName::new("__name", self.inner.rt),
333 | &JsiValue::new_string(self.variant, self.inner.rt),
334 | self.inner.rt,
335 | );
336 | self.inner.end()
337 | }
338 | }
339 |
340 | pub struct JsiMapSerializer<'a, 'rt: 'a> {
341 | rt: &'a mut RuntimeHandle<'rt>,
342 | map: JsiObject<'rt>,
343 | setter: JsiFn<'rt>,
344 | current_key: Option>,
345 | }
346 |
347 | impl<'a, 'rt: 'a> JsiMapSerializer<'a, 'rt> {
348 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self {
349 | let map_ctor = rt.global().get(PropName::new("Map", rt), rt);
350 | let map_ctor: JsiFn = map_ctor
351 | .try_into_js(rt)
352 | .expect("Map constructor is not a function");
353 | let map = map_ctor
354 | .call_as_constructor(vec![], rt)
355 | .expect("Map constructor threw an exception");
356 | let map: JsiObject = map
357 | .try_into_js(rt)
358 | .expect("Map constructor did not return an object");
359 | let map_setter = map.get(PropName::new("set", rt), rt);
360 | let map_setter: JsiFn = map_setter
361 | .try_into_js(rt)
362 | .expect("Map.set is not an function");
363 |
364 | Self {
365 | rt,
366 | map,
367 | setter: map_setter,
368 | current_key: None,
369 | }
370 | }
371 | }
372 |
373 | impl<'a, 'rt: 'a> SerializeMap for JsiMapSerializer<'a, 'rt> {
374 | type Ok = JsiValue<'rt>;
375 | type Error = JsiSerializeError;
376 |
377 | fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error>
378 | where
379 | T: serde::Serialize,
380 | {
381 | let ser = JsiSerializer { rt: self.rt };
382 | let key = key.serialize(ser)?;
383 | self.current_key = Some(key);
384 | Ok(())
385 | }
386 |
387 | fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error>
388 | where
389 | T: serde::Serialize,
390 | {
391 | let value = value.serialize(JsiSerializer { rt: self.rt })?;
392 | let key = self
393 | .current_key
394 | .take()
395 | .expect("tried to serialize value without serializing key first");
396 |
397 | self.setter
398 | .call_with_this(&self.map, vec![key, value], self.rt)
399 | .expect("Map.set threw an exception");
400 |
401 | Ok(())
402 | }
403 |
404 | fn end(self) -> Result {
405 | Ok(self.map.into_value(self.rt))
406 | }
407 | }
408 |
409 | pub struct JsiStructSerializer<'a, 'rt: 'a> {
410 | rt: &'a mut RuntimeHandle<'rt>,
411 | obj: JsiObject<'rt>,
412 | }
413 |
414 | impl<'a, 'rt: 'a> JsiStructSerializer<'a, 'rt> {
415 | pub fn new(rt: &'a mut RuntimeHandle<'rt>) -> Self {
416 | let obj = JsiObject::new(rt);
417 | Self { rt, obj }
418 | }
419 | }
420 |
421 | impl<'a, 'rt: 'a> SerializeStruct for JsiStructSerializer<'a, 'rt> {
422 | type Ok = JsiValue<'rt>;
423 | type Error = JsiSerializeError;
424 |
425 | fn serialize_field(
426 | &mut self,
427 | key: &'static str,
428 | value: &T,
429 | ) -> Result<(), Self::Error>
430 | where
431 | T: serde::Serialize,
432 | {
433 | self.obj.set(
434 | PropName::new(key, self.rt),
435 | &value.serialize(JsiSerializer { rt: self.rt })?,
436 | self.rt,
437 | );
438 | Ok(())
439 | }
440 |
441 | fn end(self) -> Result {
442 | Ok(self.obj.into_value(self.rt))
443 | }
444 | }
445 |
446 | pub struct JsiStructVariantSerializer<'a, 'rt: 'a> {
447 | inner: JsiStructSerializer<'a, 'rt>,
448 | variant: &'static str,
449 | }
450 |
451 | impl<'a, 'rt: 'a> JsiStructVariantSerializer<'a, 'rt> {
452 | pub fn new(variant: &'static str, rt: &'a mut RuntimeHandle<'rt>) -> Self {
453 | Self {
454 | inner: JsiStructSerializer::new(rt),
455 | variant,
456 | }
457 | }
458 | }
459 |
460 | impl<'a, 'rt: 'a> SerializeStructVariant for JsiStructVariantSerializer<'a, 'rt> {
461 | type Ok = JsiValue<'rt>;
462 | type Error = JsiSerializeError;
463 |
464 | fn serialize_field(
465 | &mut self,
466 | key: &'static str,
467 | value: &T,
468 | ) -> Result<(), Self::Error>
469 | where
470 | T: serde::Serialize,
471 | {
472 | self.inner.serialize_field(key, value)
473 | }
474 |
475 | fn end(mut self) -> Result {
476 | self.inner.obj.set(
477 | PropName::new("__variant", self.inner.rt),
478 | &JsiValue::new_string(self.variant, self.inner.rt),
479 | self.inner.rt,
480 | );
481 | self.inner.end()
482 | }
483 | }
484 |
485 | #[derive(Debug)]
486 | pub enum JsiSerializeError {
487 | Custom(String),
488 | UnsizedSequence,
489 | }
490 |
491 | impl std::fmt::Display for JsiSerializeError {
492 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493 | match self {
494 | JsiSerializeError::Custom(s) => f.write_str(s.as_str()),
495 | JsiSerializeError::UnsizedSequence => f.write_str("cannot serialize unsized sequences"),
496 | }
497 | }
498 | }
499 |
500 | impl serde::ser::Error for JsiSerializeError {
501 | fn custom(msg: T) -> Self
502 | where
503 | T: std::fmt::Display,
504 | {
505 | Self::Custom(msg.to_string())
506 | }
507 | }
508 |
509 | impl std::error::Error for JsiSerializeError {}
510 |
--------------------------------------------------------------------------------
/jsi/src/function.rs:
--------------------------------------------------------------------------------
1 | use crate::host_function::UserHostFunction;
2 | use crate::{sys, IntoValue, JsiObject, JsiValue, PropName, RuntimeHandle};
3 | use anyhow::{bail, Context};
4 | use std::marker::PhantomData;
5 | use std::pin::Pin;
6 |
7 | /// A JavaScript function.
8 | pub struct JsiFn<'rt>(
9 | pub(crate) cxx::UniquePtr,
10 | pub(crate) PhantomData<&'rt mut ()>,
11 | );
12 |
13 | impl<'rt> JsiFn<'rt> {
14 | pub fn call>>(
15 | &self,
16 | args: T,
17 | rt: &mut RuntimeHandle<'rt>,
18 | ) -> Result, cxx::Exception> {
19 | let mut args_cxx = sys::create_value_vector();
20 | for arg in args {
21 | sys::push_value_vector(args_cxx.pin_mut(), arg.0);
22 | }
23 |
24 | Ok(JsiValue(
25 | sys::Function_call(
26 | self.0.as_ref().unwrap(),
27 | rt.get_inner_mut(),
28 | args_cxx.as_ref().unwrap(),
29 | )?,
30 | PhantomData,
31 | ))
32 | }
33 |
34 | pub fn call_as_constructor>>(
35 | &self,
36 | args: T,
37 | rt: &mut RuntimeHandle<'rt>,
38 | ) -> Result, cxx::Exception> {
39 | let mut args_cxx = sys::create_value_vector();
40 | for arg in args {
41 | sys::push_value_vector(args_cxx.pin_mut(), arg.0);
42 | }
43 |
44 | Ok(JsiValue(
45 | sys::Function_callAsConstructor(
46 | self.0.as_ref().unwrap(),
47 | rt.get_inner_mut(),
48 | args_cxx.as_ref().unwrap(),
49 | )?,
50 | PhantomData,
51 | ))
52 | }
53 |
54 | pub fn call_with_this<'a, 'ret, T: IntoIterator- >>(
55 | &self,
56 | this: &JsiObject,
57 | args: T,
58 | rt: &mut RuntimeHandle<'rt>,
59 | ) -> Result, cxx::Exception>
60 | where
61 | 'rt: 'a,
62 | 'rt: 'ret,
63 | {
64 | let mut args_cxx = sys::create_value_vector();
65 | for arg in args {
66 | sys::push_value_vector(args_cxx.pin_mut(), arg.0);
67 | }
68 |
69 | Ok(JsiValue(
70 | sys::Function_callWithThis(
71 | self.0.as_ref().unwrap(),
72 | rt.get_inner_mut(),
73 | this.0.as_ref().unwrap(),
74 | args_cxx.as_ref().unwrap(),
75 | )?,
76 | PhantomData,
77 | ))
78 | }
79 |
80 | pub fn from_host_fn(
81 | name: &PropName,
82 | param_count: usize,
83 | mut body: Box>,
84 | rt: &mut RuntimeHandle<'rt>,
85 | ) -> Self {
86 | #[cfg(feature = "host-fn-trace")]
87 | log::trace!(
88 | "creating host fn {} with closure at {:p}",
89 | rt.display(name),
90 | body
91 | );
92 |
93 | let cb: sys::HostFunctionCallback =
94 | Box::new(move |rt: Pin<&mut sys::Runtime>, this, args| {
95 | let mut rt: RuntimeHandle =
96 | unsafe { RuntimeHandle::new_unchecked(rt.get_unchecked_mut() as *mut _) };
97 | let this = JsiValue(sys::Value_copy(this, rt.get_inner_mut()), PhantomData);
98 | let args = args
99 | .into_iter()
100 | .map(|arg| JsiValue(sys::Value_copy(arg, rt.get_inner_mut()), PhantomData))
101 | .collect();
102 |
103 | #[cfg(feature = "host-fn-trace")]
104 | log::trace!("host fn call with closure at {:p}", body);
105 |
106 | // log::trace!(
107 | // "host fn call with closure at {:p} (this = {:?}, args = {:?})",
108 | // body.as_ref(),
109 | // this,
110 | // args
111 | // );
112 |
113 | let val = body(this, args, &mut rt)?;
114 | Ok(val.0)
115 | });
116 |
117 | let cb = Box::into_raw(Box::new(cb)) as *mut _;
118 |
119 | JsiFn(
120 | unsafe {
121 | sys::Function_createFromHostFunction(
122 | rt.get_inner_mut(),
123 | name.0.as_ref().unwrap(),
124 | param_count as u32,
125 | cb,
126 | )
127 | },
128 | PhantomData,
129 | )
130 | }
131 | }
132 |
133 | unsafe impl<'rt> Send for JsiFn<'rt> {}
134 |
135 | pub fn create_promise<
136 | 'rt,
137 | F: 'rt + FnOnce(JsiFn<'rt>, JsiFn<'rt>, &mut RuntimeHandle<'rt>) -> (),
138 | >(
139 | body: F,
140 | rt: &mut RuntimeHandle<'rt>,
141 | ) -> JsiObject<'rt> {
142 | let mut inner = Some(body);
143 |
144 | let body = JsiFn::from_host_fn(
145 | &PropName::new("_", rt),
146 | 2,
147 | Box::new(move |_this, mut args, rt| {
148 | if args.len() != 2 {
149 | bail!(
150 | "promise callback called with {} args, expected 2",
151 | args.len()
152 | );
153 | }
154 |
155 | let resolve: JsiFn = args
156 | .remove(0)
157 | .try_into_js(rt)
158 | .context("promise resolver is not a function")?;
159 |
160 | let reject: JsiFn = args
161 | .remove(0)
162 | .try_into_js(rt)
163 | .context("promise rejecter is not a function")?;
164 |
165 | match inner.take() {
166 | Some(inner) => inner(resolve, reject, rt),
167 | None => anyhow::bail!("promise lambda is only supposed to be called once!"),
168 | }
169 |
170 | Ok(JsiValue::new_undefined())
171 | }),
172 | rt,
173 | );
174 |
175 | let ctor = rt.global().get(PropName::new("Promise", rt), rt);
176 | let ctor: JsiFn = ctor
177 | .try_into_js(rt)
178 | .expect("Promise constructor is not an object");
179 |
180 | let promise = ctor
181 | .call_as_constructor(vec![body.into_value(rt)], rt)
182 | .expect("Promise constructor threw an exception");
183 |
184 | promise
185 | .try_into_js(rt)
186 | .expect("Promise constructor did not return an object")
187 | }
188 |
--------------------------------------------------------------------------------
/jsi/src/host_function.rs:
--------------------------------------------------------------------------------
1 | use crate::{JsiValue, RuntimeHandle};
2 |
3 | pub type UserHostFunction<'rt> = dyn FnMut(
4 | JsiValue<'rt>, // this
5 | Vec>, // args
6 | &mut RuntimeHandle<'rt>, // runtime
7 | ) -> Result, anyhow::Error>
8 | + 'rt;
9 |
--------------------------------------------------------------------------------
/jsi/src/host_object.rs:
--------------------------------------------------------------------------------
1 | //! # Host objects
2 | //!
3 | //! Host objects are used in JSI to create special objects accessible from
4 | //! JavaScript which have behaviour that is defined in native code.
5 |
6 | use anyhow::bail;
7 | use std::{marker::PhantomData, pin::Pin};
8 |
9 | use crate::{sys, IntoValue, JsTaskCallback, JsiValue, PropName, RuntimeHandle};
10 | use sys::CallInvokerCallback;
11 |
12 | /// An owned host object
13 | pub struct OwnedJsiHostObject<'rt>(
14 | pub(crate) cxx::UniquePtr,
15 | pub(crate) PhantomData<&'rt mut ()>,
16 | );
17 |
18 | impl<'rt> OwnedJsiHostObject<'rt> {
19 | pub fn get(&mut self, prop: &PropName, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> {
20 | JsiValue(
21 | sys::HostObject_get(
22 | self.0.pin_mut(),
23 | rt.get_inner_mut(),
24 | prop.0.as_ref().unwrap(),
25 | ),
26 | PhantomData,
27 | )
28 | }
29 |
30 | pub fn set(&mut self, prop: &PropName, value: &JsiValue, rt: &mut RuntimeHandle<'rt>) {
31 | self.0.pin_mut().set(
32 | rt.get_inner_mut(),
33 | prop.0.as_ref().unwrap(),
34 | value.0.as_ref().unwrap(),
35 | )
36 | }
37 |
38 | pub fn properties(&mut self, rt: &mut RuntimeHandle<'rt>) -> Vec {
39 | let mut props = sys::HostObject_getPropertyNames(self.0.pin_mut(), rt.get_inner_mut());
40 | let mut vec = Vec::with_capacity(props.len());
41 | loop {
42 | let ptr = sys::pop_prop_name_vector(props.pin_mut());
43 | if ptr.is_null() {
44 | break;
45 | }
46 | vec.push(PropName(ptr, PhantomData));
47 | }
48 | vec
49 | }
50 | }
51 |
52 | /// A shared reference to a host object
53 | pub struct SharedJsiHostObject<'rt>(
54 | pub(crate) cxx::SharedPtr,
55 | pub(crate) PhantomData<&'rt mut ()>,
56 | );
57 |
58 | /// Helper trait for implementing a host object in Rust
59 | pub trait UserHostObject<'rt> {
60 | fn get(
61 | &mut self,
62 | name: PropName<'rt>,
63 | rt: &mut RuntimeHandle<'rt>,
64 | ) -> anyhow::Result>;
65 |
66 | fn set(
67 | &mut self,
68 | name: PropName<'rt>,
69 | value: JsiValue<'rt>,
70 | rt: &mut RuntimeHandle<'rt>,
71 | ) -> anyhow::Result<()>;
72 |
73 | fn properties(&mut self, rt: &mut RuntimeHandle<'rt>) -> Vec>;
74 | }
75 |
76 | impl<'rt, T: UserHostObject<'rt>> IntoValue<'rt> for T {
77 | fn into_value(self, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> {
78 | let host_object = OwnedJsiUserHostObject::new(self, rt);
79 | let host_object: OwnedJsiHostObject = host_object.into();
80 | host_object.into_value(rt)
81 | }
82 | }
83 |
84 | /// A generic wrapper struct is used to avoid using a triple-`Box` to contain
85 | /// user host objects.
86 | struct UserHostObjectWrapper(T);
87 |
88 | impl<'rt, T: UserHostObject<'rt>> sys::HostObjectImpl for UserHostObjectWrapper {
89 | fn get(
90 | &mut self,
91 | rt: Pin<&mut sys::Runtime>,
92 | name: &sys::PropNameID,
93 | ) -> anyhow::Result> {
94 | let mut rt = RuntimeHandle::new_unchecked(unsafe { rt.get_unchecked_mut() as *mut _ });
95 | let name = PropName(sys::PropNameID_copy(name, rt.get_inner_mut()), PhantomData);
96 | let value = UserHostObject::get(&mut self.0, name, &mut rt)?;
97 | Ok(value.0)
98 | }
99 |
100 | fn set(
101 | &mut self,
102 | rt: Pin<&mut sys::Runtime>,
103 | name: &sys::PropNameID,
104 | value: &sys::JsiValue,
105 | ) -> anyhow::Result<()> {
106 | let mut rt = RuntimeHandle::new_unchecked(unsafe { rt.get_unchecked_mut() as *mut _ });
107 | let name = PropName(sys::PropNameID_copy(name, rt.get_inner_mut()), PhantomData);
108 | let value = JsiValue(sys::Value_copy(value, rt.get_inner_mut()), PhantomData);
109 | UserHostObject::set(&mut self.0, name, value, &mut rt)
110 | }
111 |
112 | fn properties(&mut self, rt: Pin<&mut sys::Runtime>) -> Vec> {
113 | let mut rt = RuntimeHandle::new_unchecked(unsafe { rt.get_unchecked_mut() as *mut _ });
114 | let props = UserHostObject::properties(&mut self.0, &mut rt);
115 | props.into_iter().map(|p| p.0).collect()
116 | }
117 | }
118 |
119 | impl Drop for UserHostObjectWrapper {
120 | fn drop(&mut self) {
121 | #[cfg(feature = "host-object-trace")]
122 | log::trace!(
123 | "dropping host object wrapper {:p} with host object {:p}",
124 | self,
125 | &self.0
126 | );
127 | }
128 | }
129 |
130 | /// A host object that is implemented in Rust using a [`UserHostObject`] trait
131 | /// object. Start with this if you want to implement a host object.
132 | pub struct OwnedJsiUserHostObject<'rt>(
133 | cxx::UniquePtr,
134 | PhantomData<&'rt mut ()>,
135 | );
136 |
137 | impl<'rt> OwnedJsiUserHostObject<'rt> {
138 | // The methods in here have an extra lifetime parameter `'u` so that
139 | // user-defined host objects don't have to have their lifetimes limited by
140 | // `'rt`. This allows us to use `'static` objects as host objects, for
141 | // example.
142 |
143 | pub fn new<'u: 'rt, T: UserHostObject<'u>>(ho: T, _rt: &mut RuntimeHandle<'rt>) -> Self {
144 | // Box 1 b/c C++ can't hold Rust objects w/o a Box
145 | // Box 2 b/c RustHostObject can't be generic (no way for the trampoline
146 | // function to know) which function to call w/o dynamic dispatch
147 |
148 | let b = Box::new(UserHostObjectWrapper(ho));
149 |
150 | #[cfg(feature = "host-object-trace")]
151 | log::trace!(
152 | "created owned jsi user host object of {} inner box: {:p}",
153 | std::any::type_name::(),
154 | b.as_ref()
155 | );
156 |
157 | let b = Box::new(sys::RustHostObject(b));
158 |
159 | #[cfg(feature = "host-object-trace")]
160 | log::trace!(
161 | "created owned jsi user host object of {} outer box: {:p}",
162 | std::any::type_name::(),
163 | b.as_ref()
164 | );
165 |
166 | let ptr = sys::CxxHostObject_create(b);
167 |
168 | #[cfg(feature = "host-object-trace")]
169 | log::trace!(
170 | "created owned jsi user host of {} object: {:p}",
171 | std::any::type_name::(),
172 | ptr.as_ref().unwrap()
173 | );
174 |
175 | OwnedJsiUserHostObject(ptr, PhantomData)
176 | }
177 |
178 | // TODO: find some way to do a safety check at runtime w/o relying on `Any`,
179 | // because `Any` imposes `'static` which makes life hard due to how there
180 | // are lifetimes everywhere in this code
181 | pub fn get_inner_mut<'u: 'rt, T: UserHostObject<'u>>(&mut self) -> Option<&mut T> {
182 | let rho = sys::CxxHostObject_getInnerMut(self.0.as_mut().unwrap());
183 | let ho_inner = unsafe {
184 | &mut *(rho.0.as_mut() as *mut dyn sys::HostObjectImpl as *mut UserHostObjectWrapper)
185 | };
186 | Some(&mut ho_inner.0)
187 | }
188 |
189 | pub fn get_inner<'u: 'rt, T: UserHostObject<'u>>(&self) -> Option<&T> {
190 | let rho = sys::CxxHostObject_getInner(self.0.as_ref().unwrap());
191 | let ho_inner = unsafe {
192 | &*(rho.0.as_ref() as *const dyn sys::HostObjectImpl as *const UserHostObjectWrapper)
193 | };
194 | Some(&ho_inner.0)
195 | }
196 | }
197 |
198 | impl<'rt> Into> for OwnedJsiUserHostObject<'rt> {
199 | fn into(self) -> OwnedJsiHostObject<'rt> {
200 | OwnedJsiHostObject(sys::CxxHostObject_toHostObjectU(self.0), self.1)
201 | }
202 | }
203 |
204 | impl<'rt> TryInto> for OwnedJsiHostObject<'rt> {
205 | type Error = anyhow::Error;
206 |
207 | fn try_into(self) -> Result, Self::Error> {
208 | let ptr = sys::CxxHostObject_fromHostObjectU(self.0);
209 |
210 | if ptr.is_null() {
211 | bail!("this host object is not a Rust host object");
212 | } else {
213 | Ok(OwnedJsiUserHostObject(ptr, self.1))
214 | }
215 | }
216 | }
217 |
218 | /// A host object that is implemented in Rust using a [`UserHostObject`] trait object.
219 | #[derive(Clone)]
220 | pub struct SharedJsiUserHostObject<'rt>(
221 | cxx::SharedPtr,
222 | PhantomData<&'rt mut ()>,
223 | );
224 |
225 | impl<'rt> SharedJsiUserHostObject<'rt> {
226 | pub fn get_inner<'u: 'rt, T: UserHostObject<'u>>(&self) -> Option<&T> {
227 | #[cfg(feature = "host-object-trace")]
228 | log::trace!(
229 | "recovering shared inner host object as {} from {:p}",
230 | std::any::type_name::(),
231 | self.0.as_ref().unwrap()
232 | );
233 |
234 | let rho = sys::CxxHostObject_getInner(self.0.as_ref().unwrap());
235 |
236 | #[cfg(feature = "host-object-trace")]
237 | log::trace!("recovered outer box {:p}", rho);
238 |
239 | let ho_inner = unsafe {
240 | &*(rho.0.as_ref() as *const dyn sys::HostObjectImpl as *const UserHostObjectWrapper)
241 | };
242 |
243 | #[cfg(feature = "host-object-trace")]
244 | log::trace!("recovered inner box {:p}", ho_inner);
245 |
246 | #[cfg(feature = "host-object-trace")]
247 | log::trace!("recovered user object {:p}", &ho_inner.0);
248 |
249 | Some(&ho_inner.0)
250 | }
251 | }
252 |
253 | impl<'rt> Into> for SharedJsiUserHostObject<'rt> {
254 | fn into(self) -> SharedJsiHostObject<'rt> {
255 | SharedJsiHostObject(sys::CxxHostObject_toHostObjectS(self.0), self.1)
256 | }
257 | }
258 |
259 | impl<'rt> TryInto> for SharedJsiHostObject<'rt> {
260 | type Error = anyhow::Error;
261 |
262 | fn try_into(self) -> Result, Self::Error> {
263 | let ptr = sys::CxxHostObject_fromHostObjectS(self.0);
264 |
265 | if ptr.is_null() {
266 | bail!("this host object is not a Rust host object");
267 | } else {
268 | Ok(SharedJsiUserHostObject(ptr, self.1))
269 | }
270 | }
271 | }
272 |
273 | unsafe impl<'rt> Send for OwnedJsiHostObject<'rt> {}
274 | unsafe impl<'rt> Send for OwnedJsiUserHostObject<'rt> {}
275 | unsafe impl<'rt> Send for SharedJsiHostObject<'rt> {}
276 | unsafe impl<'rt> Send for SharedJsiUserHostObject<'rt> {}
277 |
278 | /// Support trait to allow implementation of async functions via attribute
279 | /// macro.
280 | pub trait AsyncUserHostObject<'rt> {
281 | fn spawn(task: JsTaskCallback);
282 |
283 | fn invoke(cb: CallInvokerCallback);
284 | }
285 |
--------------------------------------------------------------------------------
/jsi/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::{future::Future, pin::Pin};
2 |
3 | #[cfg(feature = "macros")]
4 | pub use jsi_macros::host_object;
5 | pub use jsi_sys as sys;
6 |
7 | // allows us to use the proc macros inside this crate
8 | extern crate self as jsi;
9 |
10 | mod array;
11 | mod array_buffer;
12 | mod call_invoker;
13 | #[cfg(feature = "serde")]
14 | mod convert;
15 | mod function;
16 | mod host_function;
17 | mod host_object;
18 | mod object;
19 | mod prop_name;
20 | mod runtime;
21 | mod string;
22 | mod symbol;
23 | mod value;
24 |
25 | pub use array::*;
26 | pub use array_buffer::*;
27 | pub use call_invoker::*;
28 | #[cfg(feature = "serde")]
29 | pub use convert::*;
30 | pub use function::*;
31 | pub use host_function::*;
32 | pub use host_object::*;
33 | pub use object::*;
34 | pub use prop_name::*;
35 | pub use runtime::*;
36 | pub use string::*;
37 | pub use symbol::*;
38 | pub use value::*;
39 |
40 | /// Creates a JavaScript `Error` object with the given string as the message.
41 | #[macro_export]
42 | macro_rules! js_error {
43 | ($rt: expr, $err: tt) => {{
44 | let mut rt = $rt;
45 | let error_ctor = rt.global().get(PropName::new("Error", rt), rt);
46 | let error_ctor: ::jsi::JsiFn = error_ctor.try_into_js(rt).unwrap();
47 | let error_str = format!($err);
48 | let error_str = ::jsi::JsiValue::new_string(error_str.as_str(), rt);
49 | let error = error_ctor
50 | .call_as_constructor(::std::iter::once(error_str), rt)
51 | .expect("Error constructor threw an exception");
52 | let error: ::jsi::JsiObject = error
53 | .try_into_js(rt)
54 | .expect("Error constructor returned a non-object");
55 | error
56 | }};
57 | }
58 |
59 | pub type JsTaskCallback = Box<
60 | dyn (for<'a> FnOnce(
61 | &'a mut RuntimeHandle<'a>,
62 | ) -> Pin> + 'a>>)
63 | + Send,
64 | >;
65 |
66 | pub fn init(
67 | rt: *mut sys::Runtime,
68 | call_invoker: cxx::SharedPtr,
69 | ) -> (RuntimeHandle<'static>, CallInvoker<'static>) {
70 | #[cfg(feature = "log")]
71 | log::debug!("got JSI runtime pointer: {:p}", rt);
72 | #[cfg(feature = "log")]
73 | log::debug!(
74 | "got JSI call invoker pointer: {:p}",
75 | call_invoker.as_ref().unwrap()
76 | );
77 |
78 | let runtime_handle = RuntimeHandle::new_unchecked(rt);
79 | let call_invoker = CallInvoker::new(call_invoker);
80 |
81 | (runtime_handle, call_invoker)
82 | }
83 |
--------------------------------------------------------------------------------
/jsi/src/object.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | use crate::array::JsiArray;
4 | use crate::array_buffer::JsiArrayBuffer;
5 | use crate::function::JsiFn;
6 | use crate::host_object::{OwnedJsiHostObject, SharedJsiHostObject};
7 | use crate::{
8 | sys, FromValue, JsiValue, OwnedJsiUserHostObject, PropName, RuntimeHandle,
9 | SharedJsiUserHostObject,
10 | };
11 |
12 | unsafe impl<'rt> Send for JsiObject<'rt> {}
13 |
14 | /// A JavaScript `Object`.
15 | pub struct JsiObject<'rt>(
16 | pub(crate) cxx::UniquePtr,
17 | pub(crate) PhantomData<&'rt mut ()>,
18 | );
19 |
20 | impl<'rt> JsiObject<'rt> {
21 | pub fn new(rt: &mut RuntimeHandle<'rt>) -> Self {
22 | JsiObject(sys::Object_create(rt.get_inner_mut()), PhantomData)
23 | }
24 |
25 | pub fn get(&self, prop: PropName, rt: &mut RuntimeHandle<'rt>) -> JsiValue<'rt> {
26 | JsiValue(
27 | sys::Object_getProperty(
28 | self.0.as_ref().unwrap(),
29 | rt.get_inner_mut(),
30 | prop.0.as_ref().unwrap(),
31 | ),
32 | PhantomData,
33 | )
34 | }
35 |
36 | pub fn has(&self, prop: PropName, rt: &mut RuntimeHandle<'rt>) -> bool {
37 | self.0
38 | .has_property(rt.get_inner_mut(), prop.0.as_ref().unwrap())
39 | }
40 |
41 | pub fn set(&mut self, prop: PropName, value: &JsiValue, rt: &mut RuntimeHandle<'rt>) {
42 | sys::Object_setProperty(
43 | self.0.pin_mut(),
44 | rt.get_inner_mut(),
45 | prop.0.as_ref().unwrap(),
46 | value.0.as_ref().unwrap(),
47 | )
48 | }
49 |
50 | pub fn properties(&mut self, rt: &mut RuntimeHandle<'rt>) -> JsiArray<'rt> {
51 | JsiArray(
52 | sys::Object_getPropertyNames(self.0.pin_mut(), rt.get_inner_mut()),
53 | PhantomData,
54 | )
55 | }
56 |
57 | pub fn is_array(&self, rt: &mut RuntimeHandle<'rt>) -> bool {
58 | self.0.is_array(rt.get_inner_mut())
59 | }
60 |
61 | pub fn is_array_buffer(&self, rt: &mut RuntimeHandle<'rt>) -> bool {
62 | self.0.is_array_buffer(rt.get_inner_mut())
63 | }
64 |
65 | pub fn is_fn(&self, rt: &mut RuntimeHandle<'rt>) -> bool {
66 | self.0.is_function(rt.get_inner_mut())
67 | }
68 |
69 | pub fn is_instance(&mut self, ctor: JsiFn, rt: &mut RuntimeHandle<'rt>) -> bool {
70 | self.0
71 | .pin_mut()
72 | .instance_of(rt.get_inner_mut(), ctor.0.as_ref().unwrap())
73 | }
74 | }
75 |
76 | pub trait FromObject<'rt>: Sized {
77 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option;
78 | }
79 |
80 | pub trait IntoObject<'rt> {
81 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt>;
82 | }
83 |
84 | impl<'rt> FromObject<'rt> for JsiArray<'rt> {
85 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option {
86 | sys::Object_asArray(&*obj.0, rt.get_inner_mut())
87 | .ok()
88 | .map(|raw| JsiArray(raw, PhantomData))
89 | }
90 | }
91 |
92 | impl<'rt> FromObject<'rt> for JsiArrayBuffer<'rt> {
93 | fn from_object(ojb: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option {
94 | sys::Object_asArrayBuffer(&*ojb.0, rt.get_inner_mut())
95 | .ok()
96 | .map(|raw| JsiArrayBuffer(raw, PhantomData))
97 | }
98 | }
99 |
100 | impl<'rt> FromObject<'rt> for JsiFn<'rt> {
101 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option {
102 | sys::Object_asFunction(&*obj.0, rt.get_inner_mut())
103 | .ok()
104 | .map(|raw| JsiFn(raw, PhantomData))
105 | }
106 | }
107 |
108 | impl<'rt, T: FromValue<'rt>> FromObject<'rt> for Vec {
109 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option {
110 | let arr: Option = FromObject::from_object(obj, rt);
111 | arr.and_then(|arr| {
112 | let arr: Vec<_> = arr.iter(rt).collect();
113 |
114 | arr.into_iter()
115 | .map(|it| FromValue::from_value(&it, rt))
116 | .collect()
117 | })
118 | }
119 | }
120 |
121 | impl<'rt> From> for JsiObject<'rt> {
122 | fn from(a: JsiArray<'rt>) -> Self {
123 | // JsiArray is a subclass of JsiObject, so we can just pointer cast
124 | JsiObject(
125 | unsafe { cxx::UniquePtr::<_>::from_raw(a.0.into_raw() as *mut _) },
126 | PhantomData,
127 | )
128 | }
129 | }
130 |
131 | impl<'rt> From> for JsiObject<'rt> {
132 | fn from(a: JsiArrayBuffer<'rt>) -> Self {
133 | // JsiArrayBuffer is a subclass of JsiObject, so we can just pointer cast
134 | JsiObject(
135 | unsafe { cxx::UniquePtr::<_>::from_raw(a.0.into_raw() as *mut _) },
136 | PhantomData,
137 | )
138 | }
139 | }
140 |
141 | impl<'rt> From> for JsiObject<'rt> {
142 | fn from(a: JsiFn<'rt>) -> Self {
143 | // JsiFn is a subclass of JsiObject, so we can just pointer cast
144 | JsiObject(
145 | unsafe { cxx::UniquePtr::<_>::from_raw(a.0.into_raw() as *mut _) },
146 | PhantomData,
147 | )
148 | }
149 | }
150 |
151 | impl<'rt, T: Into>> IntoObject<'rt> for T {
152 | fn into_object(self, _: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> {
153 | Into::into(self)
154 | }
155 | }
156 |
157 | impl<'rt> IntoObject<'rt> for OwnedJsiHostObject<'rt> {
158 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> {
159 | JsiObject(
160 | sys::Object_createFromHostObjectUnique(rt.get_inner_mut(), self.0),
161 | PhantomData,
162 | )
163 | }
164 | }
165 |
166 | impl<'rt> IntoObject<'rt> for OwnedJsiUserHostObject<'rt> {
167 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> {
168 | let obj: OwnedJsiHostObject = self.into();
169 | obj.into_object(rt)
170 | }
171 | }
172 |
173 | impl<'rt> IntoObject<'rt> for SharedJsiHostObject<'rt> {
174 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> {
175 | JsiObject(
176 | sys::Object_createFromHostObjectShared(rt.get_inner_mut(), self.0),
177 | PhantomData,
178 | )
179 | }
180 | }
181 |
182 | impl<'rt> IntoObject<'rt> for SharedJsiUserHostObject<'rt> {
183 | fn into_object(self, rt: &mut RuntimeHandle<'rt>) -> JsiObject<'rt> {
184 | let obj: SharedJsiHostObject = self.into();
185 | obj.into_object(rt)
186 | }
187 | }
188 |
189 | impl<'rt> FromObject<'rt> for SharedJsiHostObject<'rt> {
190 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option {
191 | sys::Object_asHostObject(&*obj.0, rt.get_inner_mut())
192 | .ok()
193 | .map(|raw| SharedJsiHostObject(raw, PhantomData))
194 | }
195 | }
196 |
197 | impl<'rt> FromObject<'rt> for SharedJsiUserHostObject<'rt> {
198 | fn from_object(obj: &JsiObject<'rt>, rt: &mut RuntimeHandle<'rt>) -> Option {
199 | let obj: Option = FromObject::from_object(obj, rt);
200 | obj.and_then(|obj| obj.try_into().ok())
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/jsi/src/prop_name.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | use crate::string::JsiString;
4 | use crate::{sys, RuntimeClone, RuntimeDisplay, RuntimeEq, RuntimeHandle};
5 |
6 | /// A `PropName`, which is used to retrieve properties from `Object`s.
7 | pub struct PropName<'rt>(
8 | pub(crate) cxx::UniquePtr,
9 | pub(crate) PhantomData<&'rt mut ()>,
10 | );
11 |
12 | impl<'rt> PropName<'rt> {
13 | pub fn new(name: &str, rt: &mut RuntimeHandle<'rt>) -> Self {
14 | PropName(
15 | sys::PropNameID_forUtf8(rt.get_inner_mut(), name),
16 | PhantomData,
17 | )
18 | }
19 |
20 | pub fn from_string(name: JsiString<'rt>, rt: &mut RuntimeHandle<'rt>) -> Self {
21 | PropName(
22 | sys::PropNameID_forString(rt.get_inner_mut(), &*name.0),
23 | PhantomData,
24 | )
25 | }
26 | }
27 |
28 | impl RuntimeClone<'_> for PropName<'_> {
29 | fn clone(&self, rt: &mut RuntimeHandle<'_>) -> Self {
30 | PropName(
31 | sys::PropNameID_copy(self.0.as_ref().unwrap(), rt.get_inner_mut()),
32 | PhantomData,
33 | )
34 | }
35 | }
36 |
37 | impl RuntimeDisplay for PropName<'_> {
38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>, rt: &mut RuntimeHandle<'_>) -> std::fmt::Result {
39 | write!(
40 | f,
41 | "{}",
42 | sys::PropNameID_toUtf8(&*self.0, rt.get_inner_mut()).to_string()
43 | )
44 | }
45 | }
46 |
47 | impl RuntimeEq for PropName<'_> {
48 | fn eq(&self, other: &Self, rt: &mut RuntimeHandle<'_>) -> bool {
49 | sys::PropNameID_compare(rt.get_inner_mut(), &*self.0, &*other.0)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/jsi/src/runtime.rs:
--------------------------------------------------------------------------------
1 | use crate::object::JsiObject;
2 | use crate::sys;
3 | use std::cell::Cell;
4 | use std::marker::PhantomData;
5 | use std::pin::Pin;
6 |
7 | #[derive(Debug)]
8 | pub struct RuntimeHandle<'rt>(pub(crate) *mut sys::Runtime, PhantomData<&'rt mut ()>);
9 |
10 | impl<'rt> RuntimeHandle<'rt> {
11 | // Creates a new RuntimeHandle; it's the caller's responsibility to make
12 | // sure that the runtime is not destroyed while objects under this runtime
13 | // are still being used
14 | pub fn new_unchecked(ptr: *mut sys::Runtime) -> Self {
15 | RuntimeHandle(ptr, PhantomData)
16 | }
17 |
18 | pub fn get_inner_mut(&mut self) -> Pin<&'rt mut sys::Runtime> {
19 | unsafe { Pin::new_unchecked(&mut *self.0) }
20 | }
21 |
22 | pub fn get_inner(&mut self) -> &'rt sys::Runtime {
23 | unsafe { &*self.0 }
24 | }
25 |
26 | pub fn global(&mut self) -> JsiObject<'rt> {
27 | JsiObject(sys::Runtime_global(self.get_inner_mut()), PhantomData)
28 | }
29 |
30 | pub fn eq(&mut self, lhs: &T, rhs: &T) -> bool {
31 | lhs.eq(rhs, self)
32 | }
33 |
34 | pub fn clone>(&mut self, it: &T) -> T {
35 | it.clone(self)
36 | }
37 |
38 | pub fn display<'a, T: RuntimeDisplay>(&'a mut self, it: &'a T) -> impl std::fmt::Display + 'a
39 | where
40 | 'rt: 'a,
41 | {
42 | // unsafe: transmute converts RuntimeHandle<'rt> to RuntimeHandle<'a>
43 | RuntimeDisplayWrapper(Cell::new(Some(unsafe { std::mem::transmute(self) })), it)
44 | }
45 |
46 | pub fn to_string<'a, T: RuntimeDisplay>(&'a mut self, it: &'a T) -> String {
47 | self.display(it).to_string()
48 | }
49 | }
50 |
51 | unsafe impl<'rt> Send for RuntimeHandle<'rt> {}
52 |
53 | pub trait RuntimeEq {
54 | fn eq(&self, other: &Self, rt: &mut RuntimeHandle<'_>) -> bool;
55 | }
56 |
57 | pub trait RuntimeClone<'rt> {
58 | fn clone(&self, rt: &mut RuntimeHandle<'rt>) -> Self;
59 | }
60 |
61 | impl<'a, T: RuntimeClone<'a>> RuntimeClone<'a> for Vec