├── .README ├── ios-bundling-webroot-folder.png ├── logo-dr-pogodin-studio.svg ├── logo-integreat.svg ├── logo-luna4.png └── sponsor.svg ├── .circleci └── config.yml ├── .editorconfig ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .nvmrc ├── .watchmanconfig ├── .yarn └── releases │ └── yarn-4.9.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE.md ├── OLD-README.md ├── README.md ├── ReactNativeStaticServer.podspec ├── android ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── AndroidManifestNew.xml │ └── java │ └── com │ ├── drpogodin │ └── reactnativestaticserver │ │ ├── Errors.kt │ │ ├── InetAddressUtils.kt │ │ ├── ReactNativeStaticServerModule.kt │ │ └── ReactNativeStaticServerPackage.kt │ └── lighttpd │ └── Server.kt ├── babel.config.js ├── eslint.config.mjs ├── example ├── .bundle │ └── config ├── .watchmanconfig ├── Gemfile ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── drpogodin │ │ │ │ └── reactnativestaticserver │ │ │ │ └── example │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── 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 │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── assets │ └── webroot │ │ ├── index.html │ │ ├── script.js │ │ ├── style.css │ │ └── version ├── babel.config.js ├── index.js ├── ios │ ├── .xcode.env │ ├── Podfile │ ├── Podfile.lock │ ├── ReactNativeStaticServerExample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── ReactNativeStaticServerExample.xcscheme │ ├── ReactNativeStaticServerExample.xcworkspace │ │ └── contents.xcworkspacedata │ └── ReactNativeStaticServerExample │ │ ├── AppDelegate.swift │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ ├── PrivacyInfo.xcprivacy │ │ └── ReactNativeStaticServerExample.entitlements ├── jest.config.js ├── metro.config.js ├── package.json ├── react-native.config.js ├── src │ └── App.tsx └── windows │ ├── .gitignore │ ├── ExperimentalFeatures.props │ ├── NuGet.Config │ ├── ReactNativeStaticServerExample.sln │ └── ReactNativeStaticServerExample │ ├── .gitignore │ ├── App.cpp │ ├── App.h │ ├── App.idl │ ├── App.xaml │ ├── Assets │ ├── LockScreenLogo.scale-200.png │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── StoreLogo.png │ └── Wide310x150Logo.scale-200.png │ ├── AutolinkedNativeModules.g.cpp │ ├── AutolinkedNativeModules.g.h │ ├── AutolinkedNativeModules.g.props │ ├── AutolinkedNativeModules.g.targets │ ├── MainPage.cpp │ ├── MainPage.h │ ├── MainPage.idl │ ├── MainPage.xaml │ ├── Package.appxmanifest │ ├── PropertySheet.props │ ├── ReactNativeStaticServerExample.vcxproj │ ├── ReactNativeStaticServerExample.vcxproj.filters │ ├── ReactPackageProvider.cpp │ ├── ReactPackageProvider.h │ ├── packages.lock.json │ ├── pch.cpp │ └── pch.h ├── glob ├── CMakeLists.txt ├── README.md ├── collate.h ├── freebsd-compat.h ├── glob.c └── glob.h ├── ios ├── Errors.h ├── Errors.mm ├── ReactNativeStaticServer.h ├── ReactNativeStaticServer.mm ├── Server.h └── Server.mm ├── package.json ├── react-native.config.js ├── src ├── NativeReactNativeStaticServer.ts ├── config.ts ├── constants.ts ├── index.tsx └── utils.ts ├── tsconfig.build.json ├── tsconfig.json ├── windows ├── .gitignore ├── ExperimentalFeatures.props ├── NuGet.Config ├── ReactNativeStaticServer.sln ├── ReactNativeStaticServer │ ├── Errors.cpp │ ├── Errors.h │ ├── PropertySheet.props │ ├── ReactNativeModule.cpp │ ├── ReactNativeModule.h │ ├── ReactNativeStaticServer.def │ ├── ReactNativeStaticServer.vcxproj │ ├── ReactNativeStaticServer.vcxproj.filters │ ├── ReactPackageProvider.cpp │ ├── ReactPackageProvider.h │ ├── ReactPackageProvider.idl │ ├── Server.cpp │ ├── Server.h │ ├── codegen │ │ ├── .clang-format │ │ └── NativeReactNativeStaticServerSpec.g.h │ ├── lighttpd │ │ ├── lemon.exe │ │ ├── libpcre2-8.dll │ │ ├── libwinpthread-1.dll │ │ ├── lighttpd.dll │ │ ├── mod_dirlisting.dll │ │ ├── mod_h2.dll │ │ └── mod_webdav.dll │ ├── packages.lock.json │ ├── pch.cpp │ └── pch.h └── mingw.sh └── yarn.lock /.README/ios-bundling-webroot-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/.README/ios-bundling-webroot-folder.png -------------------------------------------------------------------------------- /.README/logo-dr-pogodin-studio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.README/logo-luna4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/.README/logo-luna4.png -------------------------------------------------------------------------------- /.README/sponsor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | docker: 3 | - image: cimg/node:lts 4 | 5 | version: 2.1 6 | jobs: 7 | test: 8 | <<: *defaults 9 | steps: 10 | - checkout 11 | - run: git submodule update --init --recursive 12 | - restore_cache: 13 | key: node-modules-{{ checksum "yarn.lock" }} 14 | - run: yarn install 15 | - save_cache: 16 | key: node-modules-{{ checksum "yarn.lock" }} 17 | paths: 18 | - node_modules 19 | - example/node_modules 20 | - run: yarn test 21 | - persist_to_workspace: 22 | root: . 23 | paths: 24 | - lighttpd1.4 25 | - node_modules 26 | - pcre2 27 | release: 28 | <<: *defaults 29 | steps: 30 | - checkout 31 | - attach_workspace: 32 | at: . 33 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 34 | - run: npm publish --access public 35 | 36 | workflows: 37 | version: 2 38 | build: 39 | jobs: 40 | - test: 41 | filters: 42 | tags: 43 | only: /.*/ 44 | - release: 45 | filters: 46 | branches: 47 | ignore: /.*/ 48 | tags: 49 | only: /v[0-9]+(\.[0-9]+)*(-(alpha|beta)\.[0-9]+)?/ 50 | requires: 51 | - test 52 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: birdofpreyru -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | **/.xcode.env.local 32 | 33 | # Android/IJ 34 | # 35 | .classpath 36 | .cxx 37 | .gradle 38 | .idea 39 | .project 40 | .settings 41 | local.properties 42 | android.iml 43 | android/**/jniLibs/*/*.so 44 | 45 | # Cocoapods 46 | # 47 | example/ios/Pods 48 | 49 | # Ruby 50 | example/vendor/ 51 | 52 | # node.js 53 | # 54 | node_modules/ 55 | npm-debug.log 56 | yarn-debug.log 57 | yarn-error.log 58 | 59 | # BUCK 60 | buck-out/ 61 | \.buckd/ 62 | android/app/libs 63 | android/keystores/debug.keystore 64 | 65 | # Yarn 66 | .yarn/* 67 | !.yarn/patches 68 | !.yarn/plugins 69 | !.yarn/releases 70 | !.yarn/sdks 71 | !.yarn/versions 72 | 73 | # Expo 74 | .expo/ 75 | 76 | # Turborepo 77 | .turbo/ 78 | 79 | # generated by bob 80 | lib/ 81 | 82 | # React Native Codegen 83 | ios/generated 84 | android/generated 85 | 86 | # React Native Nitro Modules 87 | nitrogen/ 88 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pcre2"] 2 | path = pcre2 3 | url = https://github.com/PCRE2Project/pcre2.git 4 | [submodule "lighttpd1.4"] 5 | path = lighttpd1.4 6 | url = https://github.com/birdofpreyru/lighttpd1.4 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | **/__fixtures__ 3 | **/__mocks__ 4 | **/__tests__ 5 | android/build 6 | android/gradle 7 | android/gradlew 8 | android/gradlew.bat 9 | android/local.properties 10 | /build 11 | dr.pogodin-react-native-static-server-*.tgz 12 | /example/ 13 | ios/build 14 | node_modules 15 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.15.0 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nmHoistingLimits: workspaces 2 | 3 | nodeLinker: node-modules 4 | 5 | yarnPath: .yarn/releases/yarn-4.9.1.cjs 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | [Release Notes]: https://github.com/birdofpreyru/react-native-static-server/releases 2 | 3 | See [Release Notes] in GitHub repo. -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(rn-static-server C) 2 | cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) 3 | 4 | if (CMAKE_VERSION MATCHES "3.30.0") 5 | message(FATAL_ERROR "CMake v3.30.0 is not supported: https://github.com/birdofpreyru/react-native-static-server/issues/111") 6 | elseif (CMAKE_VERSION MATCHES "3.30.1") 7 | message(FATAL_ERROR "CMake v3.30.1 is not supported: https://github.com/birdofpreyru/react-native-static-server/issues/111") 8 | endif() 9 | 10 | # This prevents CMake from complaining that INSTALL(..) directives in 11 | # Lighttpd CMakeLists.txt miss BUNDLE DESTINATION. 12 | set(CMAKE_MACOSX_BUNDLE OFF) 13 | 14 | # Pre-build of PCRE2 for the target system from sources. 15 | 16 | if(CMAKE_CROSSCOMPILING AND CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 17 | file( 18 | COPY ${CMAKE_CURRENT_SOURCE_DIR}/windows/ReactNativeStaticServer/lighttpd/lemon.exe 19 | DESTINATION ${CMAKE_BINARY_DIR}/lighttpd1.4/build 20 | ) 21 | set(LEMON_PATH ${CMAKE_BINARY_DIR}/lighttpd1.4/build/lemon.exe) 22 | endif() 23 | 24 | # Note: Archs in CMAKE_OSX_ARCHITECTURES are separated by semi-colons, 25 | # if we don't escape semi-colons before inserting the value into other 26 | # variables, most CMake commands will treat that semi-colon as argument 27 | # separator in the list, and replace them with whitspaces, leading 28 | # to obscure errors (e.g. it may successfully complete the build, 29 | # but effectively doing it for the first of the archs only, and leave us 30 | # to wonder, why something does not link for some arch later). 31 | string(REPLACE " " "\\\;" ESCAPED_ARCHS "$ENV{ARCHS_STANDARD}") 32 | 33 | if(CMAKE_SYSTEM_NAME MATCHES "Android") 34 | set( 35 | EXTRA_BUILD_ARGS 36 | -DANDROID_ABI=${ANDROID_ABI} 37 | -DANDROID_PLATFORM=${ANDROID_PLATFORM} 38 | -DANDROID_STL=${ANDROID_STL} 39 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} 40 | -GNinja 41 | ) 42 | 43 | # TODO: This aims Mac Catalyst build, and needs a more specific condition, 44 | # but will do like this for now, as we are not doing native Mac builds, thus 45 | # Mac Catalyst build is the only scenario we have where the target system name 46 | # is Darwin. 47 | elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") 48 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target x86_64-apple-ios-macabi") 49 | set( 50 | EXTRA_BUILD_ARGS 51 | -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} 52 | -DCMAKE_OSX_ARCHITECTURES=${ESCAPED_ARCHS} 53 | ) 54 | # This allows to not sign compiled PCRE2 executables for iOS / macOS builds, 55 | # we don't really use them anyway, but we need them build for the install 56 | # command below to work. 57 | set(EXTRA_INSTALL_ARGS "--;CODE_SIGNING_ALLOWED=NO") 58 | 59 | elseif(CMAKE_SYSTEM_NAME MATCHES "iOS") 60 | set( 61 | EXTRA_BUILD_ARGS 62 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} 63 | -DCMAKE_OSX_ARCHITECTURES=${ESCAPED_ARCHS} 64 | -DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} 65 | -DCMAKE_IOS_INSTALL_COMBINED=${CMAKE_IOS_INSTALL_COMBINED} 66 | -GXcode 67 | ) 68 | # This allows to not sign compiled PCRE2 executables for iOS / macOS builds, 69 | # we don't really use them anyway, but we need them build for the install 70 | # command below to work. 71 | set(EXTRA_INSTALL_ARGS "--;CODE_SIGNING_ALLOWED=NO") 72 | endif() 73 | 74 | if(CMAKE_SYSTEM_NAME MATCHES "Android|Windows") 75 | set(EXTRA_BUILD_ARGS ${EXTRA_BUILD_ARGS} -DBUILD_SHARED_LIBS=ON) 76 | endif() 77 | 78 | execute_process( 79 | COMMAND ${CMAKE_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR}/pcre2 80 | -B ${CMAKE_BINARY_DIR}/pcre2 81 | -DCMAKE_BUILD_TYPE=Release 82 | -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/sysroot 83 | -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM} 84 | ${EXTRA_BUILD_ARGS} 85 | COMMAND_ECHO STDOUT 86 | COMMAND_ERROR_IS_FATAL ANY 87 | ) 88 | 89 | execute_process( 90 | COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/pcre2 91 | --config Release ${EXTRA_INSTALL_ARGS} 92 | ) 93 | 94 | execute_process(COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR}/pcre2) 95 | 96 | # Copies shared PCRE2 library into the folder from where Gradle automatically 97 | # will bundle it in the host app package. 98 | if(CMAKE_SYSTEM_NAME MATCHES "Android") 99 | file( 100 | COPY ${CMAKE_BINARY_DIR}/sysroot/lib/libpcre2-8.so 101 | DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/src/main/jniLibs/${ANDROID_ABI} 102 | ) 103 | endif() 104 | 105 | # Builds & places for packing the glob library, for Android-targeted builds. 106 | if(CMAKE_SYSTEM_NAME MATCHES "Android") 107 | execute_process( 108 | COMMAND ${CMAKE_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR}/glob 109 | -B ${CMAKE_BINARY_DIR}/glob 110 | -DCMAKE_BUILD_TYPE=Release 111 | -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/sysroot 112 | -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM} 113 | ${EXTRA_BUILD_ARGS} 114 | COMMAND_ECHO STDOUT 115 | COMMAND_ERROR_IS_FATAL ANY 116 | ) 117 | execute_process( 118 | COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/glob 119 | --config Release ${EXTRA_INSTALL_ARGS} 120 | ) 121 | execute_process(COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR}/glob) 122 | file( 123 | COPY ${CMAKE_BINARY_DIR}/sysroot/lib/libglob.so 124 | DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/src/main/jniLibs/${ANDROID_ABI} 125 | ) 126 | endif() 127 | 128 | if(CMAKE_SYSTEM_NAME MATCHES "Android|Windows") 129 | set(BUILD_SHARED_LIBS 1) 130 | endif() 131 | 132 | # Above we have build & installed a local version of PCRE2 library, now we want 133 | # to enforce Lighttpd build to see and use it (rather than any system-wide 134 | # installation of another PCRE2 version). On Ubuntu / macOS it can be done 135 | # by setting CMAKE_PREFIX_PATH; somehow it has no effect in Msys2 / UCRT64 136 | # builds for Windows (beyond by current understanding), but there setting 137 | # the PKG_CONFIG_PATH environment variable, which is picked up by pkg-config 138 | # does the trick. 139 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 140 | set(ENV{PKG_CONFIG_PATH} "${CMAKE_BINARY_DIR}/sysroot/lib/pkgconfig") 141 | else() 142 | set(CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}/sysroot") 143 | endif() 144 | 145 | add_subdirectory(lighttpd1.4) 146 | 147 | set(PLUGIN_STATIC 148 | PLUGIN_INIT(mod_access)\n 149 | PLUGIN_INIT(mod_alias)\n 150 | PLUGIN_INIT(mod_dirlisting)\n 151 | PLUGIN_INIT(mod_evhost)\n 152 | PLUGIN_INIT(mod_expire)\n 153 | PLUGIN_INIT(mod_fastcgi)\n 154 | PLUGIN_INIT(mod_h2)\n 155 | PLUGIN_INIT(mod_indexfile)\n 156 | PLUGIN_INIT(mod_redirect)\n 157 | PLUGIN_INIT(mod_rewrite)\n 158 | PLUGIN_INIT(mod_scgi)\n 159 | PLUGIN_INIT(mod_setenv)\n 160 | PLUGIN_INIT(mod_simple_vhost)\n 161 | PLUGIN_INIT(mod_staticfile)\n 162 | ) 163 | 164 | if(WITH_MOD_WEBDAV) 165 | set(PLUGIN_STATIC "${PLUGIN_STATIC}PLUGIN_INIT(mod_webdav)\n") 166 | endif() 167 | 168 | file(WRITE ${CMAKE_BINARY_DIR}/lighttpd1.4/build/plugin-static.h 169 | ${PLUGIN_STATIC} 170 | ) 171 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | _Copyright © 2022–2025, Dr. Sergey Pogodin_ 4 | — (https://dr.pogodin.studio) \ 5 | _Copyright © 2017, futurepress_ 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /OLD-README.md: -------------------------------------------------------------------------------- 1 | # React Native Static Server (v0.6, v0.5) 2 | 3 | _This documentation is for older library versions **v0.6** and **v0.5**. For newer versions (**v0.7**+) see the main [README.md](./README.md)_ 4 | 5 | ## Getting started 6 | - Install the package 7 | ```shell 8 | $ npm install --save @dr.pogodin/react-native-static-server 9 | ``` 10 | - If you intend to use this library in an [Expo app](https://expo.dev), 11 | it is possible, but requires additional setup efforts. 12 | See [this discussion](https://github.com/birdofpreyru/react-native-static-server/issues/8#issuecomment-1211605867) 13 | for details. 14 | 15 | ## Usage 16 | 17 | Declare the `StaticServer` with a port or use the default `0` to pick a random available port. 18 | 19 | ```jsx 20 | import StaticServer from '@dr.pogodin/react-native-static-server'; 21 | 22 | let server = new StaticServer(8080); 23 | 24 | // Start the server 25 | server.start().then((url) => { 26 | console.log("Serving at URL", url); 27 | }); 28 | 29 | // Stop the server 30 | server.stop(); 31 | 32 | // Check if native server running 33 | const isRunning = await server.isRunning() 34 | // isRunning - true/false 35 | ``` 36 | 37 | `StaticServer` serves from the document directory (default) or takes an optional absolute path to serve from. 38 | 39 | For instance, using [react-native-fs](https://github.com/johanneslumpe/react-native-fs) you can get the document directory and specify a directory from there. 40 | 41 | #### Default (document directory) 42 | 43 | ```javascript 44 | import StaticServer from '@dr.pogodin/react-native-static-server'; 45 | import RNFS from 'react-native-fs'; 46 | 47 | // create a path you want to write to 48 | let path = RNFS.DocumentDirectoryPath + '/www'; 49 | 50 | let server = new StaticServer(8080, path); 51 | ``` 52 | 53 | #### Custom folder (iOS) 54 | 55 | ##### Create the folder for static files 56 | 57 | Create a folder in your project's top-level directory (usually next to your node_modules and index.js file), and put the files you want to access over http in there. 58 | 59 | ##### Add folder (static files) to XCode 60 | 61 | This folder **must be added to XCode** so it gets bundled with the app. 62 | 63 | In XCode, `Project Navigator` right click in the folder project → `Add files to ""` → Select the static folder **and clic options (Uncheck copy items if needed, Create folder references)** so don't duplicate files → Clic Add. 64 | 65 | When the app gets bundled, this folder will be next to the compiled app, so using `MainBundlePath` property from `react-native-fs` you can access to the directory. 66 | 67 | ```javascript 68 | import StaticServer from '@dr.pogodin/react-native-static-server'; 69 | import RNFS from 'react-native-fs'; 70 | 71 | // path where files will be served from (index.html here) 72 | let path = RNFS.MainBundlePath + '/www'; 73 | 74 | let server = new StaticServer(8080, path); 75 | ``` 76 | 77 | If the server should only be accessible from within the app, set `localOnly` to `true` 78 | 79 | ```javascript 80 | import StaticServer from '@dr.pogodin/react-native-static-server'; 81 | 82 | // Just set options with defaults 83 | let server = new StaticServer({localOnly : true }); 84 | // Or also valid are: 85 | let server = new StaticServer(8080, {localOnly : true }); 86 | let server = new StaticServer(8080, path, {localOnly : true }); 87 | 88 | ``` 89 | 90 | If the server should not pause when the app is in the background, set `keepAlive` to `true` 91 | 92 | ```javascript 93 | let server = new StaticServer({keepAlive : true }); 94 | ``` 95 | 96 | Passing `0` as the port number will cause a random port to be assigned every time the server starts. 97 | It will reset to a new random port each time the server unpauses, so this should only be used with `keepAlive`. 98 | 99 | ```javascript 100 | let server = new StaticServer(0, {keepAlive : true }); 101 | ``` 102 | 103 | 104 | [Babel]: https://babeljs.io/ 105 | -------------------------------------------------------------------------------- /ReactNativeStaticServer.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | $extraCMakeArgs = "" 5 | 6 | $libToolFlags = "-llighttpd -lpcre2-8 -lmod_dirlisting -lmod_h2" 7 | 8 | $outputFiles = [ 9 | # Note: Below is the list of all build products generated from PRCE2, 10 | # Lighttpd, and this library, as of now; the commented out modules are 11 | # not currently used by our library. 12 | '${BUILT_PRODUCTS_DIR}/liblighttpd.a', 13 | # '${BUILT_PRODUCTS_DIR}/libmod_accesslog.a', 14 | # '${BUILT_PRODUCTS_DIR}/libmod_ajp13.a', 15 | # '${BUILT_PRODUCTS_DIR}/libmod_auth.a', 16 | # '${BUILT_PRODUCTS_DIR}/libmod_authn_file.a', 17 | # '${BUILT_PRODUCTS_DIR}/libmod_cgi.a', 18 | # '${BUILT_PRODUCTS_DIR}/libmod_deflate.a', 19 | '${BUILT_PRODUCTS_DIR}/libmod_dirlisting.a', 20 | # '${BUILT_PRODUCTS_DIR}/libmod_extforward.a', 21 | '${BUILT_PRODUCTS_DIR}/libmod_h2.a', 22 | # '${BUILT_PRODUCTS_DIR}/libmod_proxy.a', 23 | # '${BUILT_PRODUCTS_DIR}/libmod_rrdtool.a', 24 | # '${BUILT_PRODUCTS_DIR}/libmod_sockproxy.a', 25 | # '${BUILT_PRODUCTS_DIR}/libmod_ssi.a', 26 | # '${BUILT_PRODUCTS_DIR}/libmod_status.a', 27 | # '${BUILT_PRODUCTS_DIR}/libmod_userdir.a', 28 | # '${BUILT_PRODUCTS_DIR}/libmod_vhostdb.a', 29 | # '${BUILT_PRODUCTS_DIR}/libmod_wstunnel.a', 30 | '${BUILT_PRODUCTS_DIR}/libpcre2-8.a' 31 | ] 32 | 33 | if ENV['RN_STATIC_SERVER_WEBDAV'] == '1' then 34 | $extraCMakeArgs += " -DWITH_MOD_WEBDAV=ON" 35 | $libToolFlags += " -lmod_webdav" 36 | $outputFiles.append('${BUILT_PRODUCTS_DIR}/libmod_webdav.a') 37 | end 38 | 39 | Pod::Spec.new do |s| 40 | s.name = "ReactNativeStaticServer" 41 | s.version = package["version"] 42 | s.summary = package["description"] 43 | s.homepage = package["homepage"] 44 | s.license = package["license"] 45 | s.authors = package["author"] 46 | 47 | s.platforms = { :ios => min_ios_version_supported } 48 | s.source = { :git => "https://github.com/birdofpreyru/react-native-static-server.git", :tag => "#{s.version}" } 49 | 50 | s.preserve_paths = 'README.md', 'package.json', 'index.js' 51 | 52 | # This requires CMake on the build host, which can be installed via Homebrew (https://brew.sh) 53 | s.script_phase = { 54 | :name => 'Build native dependencies', 55 | :execution_position => :before_compile, 56 | :output_files => $outputFiles, 57 | :script => <<-CMD 58 | set -e 59 | 60 | if [[ ${CONFIGURATION} == "Debug" ]] 61 | then 62 | LIGHTTPD_CONFIG="Debug" 63 | else 64 | LIGHTTPD_CONFIG="Release" 65 | fi 66 | 67 | if [[ ${PLATFORM_FAMILY_NAME} == "iOS" ]] 68 | then 69 | EXTRA_CONFIG_ARGS="-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET} -DCMAKE_SYSTEM_NAME=iOS -GXcode" 70 | BUILD_OUTPUT_FOLDER_LIGHTTPD="/${LIGHTTPD_CONFIG}${EFFECTIVE_PLATFORM_NAME}" 71 | BUILD_OUTPUT_FOLDER_PCRE2="/Release${EFFECTIVE_PLATFORM_NAME}" 72 | else 73 | # This assumes Mac Catalyst build. 74 | EXTRA_CONFIG_ARGS="-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" 75 | fi 76 | 77 | cmake ${PODS_TARGET_SRCROOT} -B ${TARGET_TEMP_DIR} \ 78 | -DBUILD_STATIC=1 -DBUILD_LIBRARY=1 ${EXTRA_CONFIG_ARGS} #{$extraCMakeArgs} 79 | 80 | cmake --build ${TARGET_TEMP_DIR} --config ${LIGHTTPD_CONFIG} --target lighttpd 81 | 82 | cp ${TARGET_TEMP_DIR}/lighttpd1.4/build${BUILD_OUTPUT_FOLDER_LIGHTTPD}/*.a \ 83 | ${TARGET_TEMP_DIR}/pcre2${BUILD_OUTPUT_FOLDER_PCRE2}/*.a \ 84 | ${BUILT_PRODUCTS_DIR} 85 | CMD 86 | } 87 | s.source_files = "ios/**/*.{h,m,mm,cpp}" 88 | s.private_header_files = "ios/generated/**/*.h" 89 | 90 | # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. 91 | # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. 92 | if respond_to?(:install_modules_dependencies, true) 93 | install_modules_dependencies(s) 94 | else 95 | s.dependency "React-Core" 96 | end 97 | 98 | s.pod_target_xcconfig = { 99 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", 100 | "OTHER_LIBTOOLFLAGS" => $libToolFlags 101 | } 102 | end 103 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.getExtOrDefault = {name -> 3 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeStaticServer_' + name] 4 | } 5 | 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath "com.android.tools.build:gradle:8.7.2" 13 | // noinspection DifferentKotlinGradleVersion 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}" 15 | } 16 | } 17 | 18 | 19 | apply plugin: "com.android.library" 20 | apply plugin: "kotlin-android" 21 | 22 | apply plugin: "com.facebook.react" 23 | 24 | def getExtOrIntegerDefault(name) { 25 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeStaticServer_" + name]).toInteger() 26 | } 27 | 28 | def supportsNamespace() { 29 | def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') 30 | def major = parsed[0].toInteger() 31 | def minor = parsed[1].toInteger() 32 | 33 | // Namespace support was added in 7.3.0 34 | return (major == 7 && minor >= 3) || major >= 8 35 | } 36 | 37 | android { 38 | if (supportsNamespace()) { 39 | namespace "com.drpogodin.reactnativestaticserver" 40 | 41 | sourceSets { 42 | main { 43 | manifest.srcFile "src/main/AndroidManifestNew.xml" 44 | } 45 | } 46 | } 47 | 48 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") 49 | 50 | defaultConfig { 51 | minSdkVersion getExtOrIntegerDefault("minSdkVersion") 52 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") 53 | externalNativeBuild { 54 | cmake { 55 | arguments "-DWITH_ANDROID_NDK_SYSLOG_INTERCEPT=ON", "-DWITH_JAVA_NATIVE_INTERFACE=ON", "-DBUILD_STATIC=ON", 56 | // TODO: I guess, there should be a more elegant way to append 57 | // arguments here based on flags set in project Gradle properties, 58 | // but for now this will do. 59 | (project.properties["ReactNativeStaticServer_webdav"] ? "-DWITH_MOD_WEBDAV=ON" : "-DWITH_MOD_WEBDAV=OFF") 60 | targets "lighttpd" 61 | } 62 | } 63 | } 64 | 65 | externalNativeBuild { 66 | cmake { 67 | path "../CMakeLists.txt" 68 | } 69 | } 70 | 71 | buildFeatures { 72 | buildConfig true 73 | } 74 | 75 | buildTypes { 76 | release { 77 | minifyEnabled false 78 | consumerProguardFiles "proguard-rules.pro" 79 | } 80 | } 81 | 82 | lintOptions { 83 | disable "GradleCompatible" 84 | } 85 | 86 | compileOptions { 87 | sourceCompatibility JavaVersion.VERSION_1_8 88 | targetCompatibility JavaVersion.VERSION_1_8 89 | } 90 | 91 | sourceSets { 92 | main { 93 | java.srcDirs += [ 94 | "generated/java", 95 | "generated/jni" 96 | ] 97 | } 98 | } 99 | } 100 | 101 | repositories { 102 | mavenCentral() 103 | google() 104 | } 105 | 106 | def kotlin_version = getExtOrDefault("kotlinVersion") 107 | 108 | dependencies { 109 | implementation "com.facebook.react:react-android" 110 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 111 | } 112 | 113 | react { 114 | jsRootDir = file("../src/") 115 | libraryName = "ReactNativeStaticServer" 116 | codegenJavaPackageName = "com.drpogodin.reactnativestaticserver" 117 | } 118 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | ReactNativeStaticServer_kotlinVersion=2.0.21 2 | ReactNativeStaticServer_minSdkVersion=24 3 | ReactNativeStaticServer_targetSdkVersion=34 4 | ReactNativeStaticServer_compileSdkVersion=35 5 | ReactNativeStaticServer_ndkVersion=27.1.12297006 6 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep public class com.lighttpd.Server { 2 | void onLaunchedCallback(); 3 | } 4 | 5 | -keepclasseswithmembernames,includedescriptorclasses class * { 6 | native ; 7 | } -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifestNew.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/com/drpogodin/reactnativestaticserver/Errors.kt: -------------------------------------------------------------------------------- 1 | package com.drpogodin.reactnativestaticserver 2 | 3 | import android.util.Log 4 | import com.facebook.react.bridge.Promise 5 | 6 | class Errors(val name: String, val message: String) { 7 | val error: Error 8 | get() = Error(message) 9 | val exception: Exception 10 | get() = Exception(message) 11 | 12 | fun log(): Errors { 13 | Log.e(LOG_TAG, message) 14 | return this 15 | } 16 | 17 | fun log(e: Exception): Errors { 18 | Log.e(LOG_TAG, e.toString()) 19 | return this.log() 20 | } 21 | 22 | fun reject(promise: Promise?) { 23 | promise?.reject(this.toString(), message, error) 24 | } 25 | 26 | fun reject(promise: Promise?, details: String?) { 27 | if (promise != null) { 28 | var message = message 29 | if (details != null) message += ": $details" 30 | promise.reject(this.toString(), message, error) 31 | } 32 | } 33 | 34 | override fun toString(): String { 35 | return "$LOG_TAG:$name" 36 | } 37 | 38 | companion object { 39 | const val LOG_TAG = "RN_STATIC_SERVER" 40 | 41 | fun anotherInstanceIsActive( 42 | activeServerId: Double, 43 | failedToLaunchServerId: Double 44 | ): Errors { 45 | return Errors( 46 | "ANOTHER_INSTANCE_IS_ACTIVE", 47 | "Failed to launch server #$failedToLaunchServerId, another server instance (#$activeServerId) is active.") 48 | } 49 | 50 | fun failGetLocalIpAddress(): Errors { 51 | return Errors( 52 | "FAIL_GET_LOCAL_IP_ADDRESS", 53 | "Failed to get local IP address" 54 | ) 55 | } 56 | 57 | fun failGetOpenPort(): Errors { 58 | return Errors( 59 | "FAIL_GET_OPEN_PORT", 60 | "Failed to get an open port" 61 | ) 62 | } 63 | 64 | fun internalError(serverId: Double): Errors { 65 | return Errors("INTERNAL_ERROR", "Internal error (server #$serverId)") 66 | } 67 | 68 | fun serverCrashed(serverId: Double): Errors { 69 | return Errors("SERVER_CRASHED", "Server #$serverId crashed") 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/src/main/java/com/drpogodin/reactnativestaticserver/InetAddressUtils.kt: -------------------------------------------------------------------------------- 1 | package com.drpogodin.reactnativestaticserver 2 | 3 | import java.util.regex.Pattern 4 | 5 | /* 6 | * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/util/InetAddressUtils.java $ 7 | * $Revision: 652020 $ 8 | * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ 9 | * 10 | * ==================================================================== 11 | * Licensed to the Apache Software Foundation (ASF) under one 12 | * or more contributor license agreements. See the NOTICE file 13 | * distributed with this work for additional information 14 | * regarding copyright ownership. The ASF licenses this file 15 | * to you under the Apache License, Version 2.0 (the 16 | * "License"); you may not use this file except in compliance 17 | * with the License. You may obtain a copy of the License at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * Unless required by applicable law or agreed to in writing, 22 | * software distributed under the License is distributed on an 23 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 24 | * KIND, either express or implied. See the License for the 25 | * specific language governing permissions and limitations 26 | * under the License. 27 | * ==================================================================== 28 | * 29 | * This software consists of voluntary contributions made by many 30 | * individuals on behalf of the Apache Software Foundation. For more 31 | * information on the Apache Software Foundation, please see 32 | * . 33 | * 34 | */ 35 | object InetAddressUtils { 36 | private val IPV4_PATTERN = Pattern.compile( 37 | "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$") 38 | 39 | fun isIPv4Address(input: String): Boolean { 40 | return IPV4_PATTERN.matcher(input).matches() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /android/src/main/java/com/drpogodin/reactnativestaticserver/ReactNativeStaticServerModule.kt: -------------------------------------------------------------------------------- 1 | package com.drpogodin.reactnativestaticserver 2 | 3 | import android.util.Log 4 | import com.drpogodin.reactnativestaticserver.InetAddressUtils.isIPv4Address 5 | import com.facebook.react.bridge.Arguments 6 | import com.facebook.react.bridge.LifecycleEventListener 7 | import com.facebook.react.bridge.Promise 8 | import com.facebook.react.bridge.ReactApplicationContext 9 | import com.facebook.react.bridge.ReactMethod 10 | import com.facebook.react.module.annotations.ReactModule 11 | import com.facebook.react.modules.core.DeviceEventManagerModule 12 | import com.lighttpd.Server 13 | import java.net.InetAddress 14 | import java.net.NetworkInterface 15 | import java.net.ServerSocket 16 | import java.util.concurrent.Semaphore 17 | 18 | @ReactModule(name = ReactNativeStaticServerModule.NAME) 19 | class ReactNativeStaticServerModule(reactContext: ReactApplicationContext) : 20 | NativeReactNativeStaticServerSpec(reactContext), LifecycleEventListener { 21 | // The currently active server instance. We assume only single server instance 22 | // can be active at any time, thus a simple field should be enough for now. 23 | // If we arrive to having possibility of multiple servers running in 24 | // parallel, then this will become a hash map [ID <-> Server], and we will 25 | // use it for communication from JS to this module to the target Server 26 | // instance. 27 | private var server: Server? = null 28 | private var pendingPromise: Promise? = null 29 | 30 | override fun getTypedExportedConstants(): Map { 31 | val constants: MutableMap = HashMap() 32 | constants["CRASHED"] = Server.CRASHED 33 | constants["IS_MAC_CATALYST"] = false 34 | constants["LAUNCHED"] = Server.LAUNCHED 35 | constants["TERMINATED"] = Server.TERMINATED 36 | return constants 37 | } 38 | 39 | @ReactMethod 40 | override fun getActiveServerId(promise: Promise) { 41 | promise.resolve(server?.id) 42 | } 43 | 44 | @ReactMethod 45 | override fun getLocalIpAddress(promise: Promise) { 46 | try { 47 | val en = NetworkInterface.getNetworkInterfaces() 48 | while (en.hasMoreElements()) { 49 | val `interface` = en.nextElement() 50 | val enumIpAddress = `interface`.inetAddresses 51 | while (enumIpAddress.hasMoreElements()) { 52 | val inetAddress = enumIpAddress.nextElement() 53 | if (!inetAddress.isLoopbackAddress) { 54 | val ip = inetAddress.hostAddress 55 | if (ip != null && isIPv4Address(ip)) { 56 | promise.resolve(ip) 57 | return 58 | } 59 | } 60 | } 61 | } 62 | promise.resolve("127.0.0.1") 63 | } catch (e: Exception) { 64 | Errors.failGetLocalIpAddress().reject(promise) 65 | } 66 | } 67 | 68 | override fun getName(): String { 69 | return NAME 70 | } 71 | 72 | @ReactMethod 73 | override fun start( 74 | id: Double, // Server ID for backward communication with JS layer. 75 | configPath: String, 76 | errlogPath: String, 77 | promise: Promise 78 | ) { 79 | Log.i(LOG_TAG, "Starting...") 80 | try { 81 | sem.acquire() 82 | } catch (e: Exception) { 83 | Errors.internalError(id).log(e) 84 | .reject(promise, "Failed to acquire a semaphore") 85 | return 86 | } 87 | 88 | val activeServerId = server?.id 89 | if (activeServerId != null) { 90 | Errors.anotherInstanceIsActive(activeServerId, id).log().reject(promise) 91 | sem.release() 92 | return 93 | } 94 | if (pendingPromise != null) { 95 | Errors.internalError(id).log().reject(promise, "Unexpected pending promise") 96 | sem.release() 97 | return 98 | } 99 | pendingPromise = promise 100 | val emitter: DeviceEventManagerModule.RCTDeviceEventEmitter = reactApplicationContext 101 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) 102 | 103 | server = Server(id, configPath, errlogPath) { signal, details -> 104 | if (signal !== Server.LAUNCHED) server = null 105 | if (pendingPromise == null) { 106 | val event = Arguments.createMap() 107 | event.putDouble("serverId", id) 108 | event.putString("event", signal) 109 | event.putString("details", details) 110 | emitter.emit("RNStaticServer", event) 111 | } else { 112 | if (signal === Server.CRASHED) { 113 | Errors.serverCrashed(id).reject(pendingPromise, details) 114 | } else pendingPromise!!.resolve(details) 115 | pendingPromise = null 116 | sem.release() 117 | } 118 | } 119 | server!!.start() 120 | } 121 | 122 | @ReactMethod 123 | override fun getOpenPort(address: String, promise: Promise) { 124 | try { 125 | val socket = ServerSocket( 126 | 0, 0, InetAddress.getByName(address)) 127 | val port = socket.localPort 128 | socket.close() 129 | promise.resolve(port) 130 | } catch (e: Exception) { 131 | Errors.failGetOpenPort().log(e).reject(promise) 132 | } 133 | } 134 | 135 | @ReactMethod 136 | override fun stop(promise: Promise?) { 137 | Log.i(LOG_TAG, "stop() triggered") 138 | try { 139 | sem.acquire() 140 | } catch (e: Exception) { 141 | Errors.internalError(server!!.id).log(e) 142 | .reject(promise, "Failed to acquire a semaphore") 143 | return 144 | } 145 | if (pendingPromise != null) { 146 | Errors.internalError(server!!.id) 147 | .reject(pendingPromise, "Unexpected pending promise") 148 | sem.release() 149 | return 150 | } 151 | pendingPromise = promise 152 | server!!.interrupt() 153 | } 154 | 155 | @ReactMethod 156 | override fun addListener(eventName: String?) { 157 | // NOOP 158 | } 159 | 160 | @ReactMethod 161 | override fun removeListeners(count: Double) { 162 | // NOOP 163 | } 164 | 165 | // NOTE: Pause/resume operations, if opted, are managed in JS layer. 166 | override fun onHostResume() {} 167 | override fun onHostPause() {} 168 | override fun onHostDestroy() { 169 | stop(null) 170 | } 171 | 172 | companion object { 173 | // This semaphore is used to atomize server start-up and shut-down operations. 174 | // It is acquired in the very beginning of start() and stop() methods; and it 175 | // is normally released on the first subsequent signal from the server, at 176 | // the same moment the pendingPromise for those start() and stop() is resolved. 177 | // In edge cases, when start() or stop() is aborted due to failed runtime 178 | // invariant checks, this semaphore is released at those abort points, which 179 | // are in all cases prior to assigning the pendingPromise value. 180 | private val sem = Semaphore(1, true) 181 | const val NAME = "ReactNativeStaticServer" 182 | const val LOG_TAG = Errors.LOG_TAG + " (Module)" 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /android/src/main/java/com/drpogodin/reactnativestaticserver/ReactNativeStaticServerPackage.kt: -------------------------------------------------------------------------------- 1 | package com.drpogodin.reactnativestaticserver 2 | 3 | import com.facebook.react.BaseReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.module.model.ReactModuleInfo 7 | import com.facebook.react.module.model.ReactModuleInfoProvider 8 | import java.util.HashMap 9 | 10 | class ReactNativeStaticServerPackage : BaseReactPackage() { 11 | override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { 12 | return if (name == ReactNativeStaticServerModule.NAME) { 13 | ReactNativeStaticServerModule(reactContext) 14 | } else { 15 | null 16 | } 17 | } 18 | 19 | override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { 20 | return ReactModuleInfoProvider { 21 | val moduleInfos: MutableMap = HashMap() 22 | moduleInfos[ReactNativeStaticServerModule.NAME] = ReactModuleInfo( 23 | ReactNativeStaticServerModule.NAME, 24 | ReactNativeStaticServerModule.NAME, 25 | canOverrideExistingModule = false, // canOverrideExistingModule 26 | needsEagerInit = false, // needsEagerInit 27 | isCxxModule = false, // isCxxModule 28 | isTurboModule = true // isTurboModule 29 | ) 30 | moduleInfos 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/src/main/java/com/lighttpd/Server.kt: -------------------------------------------------------------------------------- 1 | package com.lighttpd 2 | 3 | import android.util.Log 4 | import com.drpogodin.reactnativestaticserver.Errors 5 | 6 | /** 7 | * Java interface for native Lighttpd server running in a dedicated Thread. 8 | * Use Thread methods to operate the server: 9 | * .start() - To launch it; 10 | * .isActive() - To check its current status; 11 | * .interrupt() - To gracefully terminate it. 12 | * Also, `signalConsumer` callback provided to Server instance upon construction 13 | * will provide you with server state change Signals. 14 | * 15 | * As Java Thread instances may be executed only once, to restart the server 16 | * you should create and launch a new instance of Server object. 17 | * 18 | * BEWARE: With the current Lighttpd implementation, 19 | * and the way it is integrated into this library, it is not safe to run 20 | * multiple server instances in parallel! Be sure the previous server instance, 21 | * if any, has terminated or crashed before launching a new one! 22 | */ 23 | class Server( 24 | val id: Double, 25 | var configPath: String, 26 | private var errorLogPath: String, 27 | private val signalConsumer: (signal: String, message: String?) -> Unit 28 | ) : Thread() { 29 | override fun interrupt() { 30 | Log.i(LOG_TAG, "Server.interrupt() triggered") 31 | gracefulShutdown() 32 | // No need to call super.interrupt() here, the native this.shutdown() 33 | // method will set within the native layer necessary flags that will 34 | // cause graceful termination of the thread. 35 | } 36 | 37 | private external fun gracefulShutdown() 38 | external fun launch(configPath: String, errorLogPath: String): Int 39 | override fun run() { 40 | Log.i(LOG_TAG, "Server.run() triggered") 41 | if (activeServer != null) { 42 | val msg = "Another Server instance is active" 43 | Log.e(LOG_TAG, msg) 44 | signalConsumer(CRASHED, msg) 45 | return 46 | } 47 | try { 48 | activeServer = this 49 | val res = launch(configPath, errorLogPath) 50 | if (res != 0) { 51 | throw Exception("Native server exited with status $res") 52 | } 53 | 54 | // NOTE: It MUST BE set "null" prior to sending out TERMINATED or CRASHED 55 | // signals. 56 | activeServer = null 57 | Log.i(LOG_TAG, "Server terminated gracefully") 58 | signalConsumer(TERMINATED, null) 59 | } catch (error: Exception) { 60 | activeServer = null 61 | Log.e(LOG_TAG, "Server crashed", error) 62 | signalConsumer(CRASHED, error.message) 63 | } 64 | } 65 | 66 | companion object { 67 | init { 68 | System.loadLibrary("lighttpd") 69 | } 70 | 71 | // NOTE: Tried to use enum, but was not able to make it work with JNI. 72 | const val CRASHED = "CRASHED" 73 | const val LAUNCHED = "LAUNCHED" 74 | const val TERMINATED = "TERMINATED" 75 | private var activeServer: Server? = null 76 | private const val LOG_TAG = Errors.LOG_TAG 77 | 78 | // NOTE: @JvmStatic annotation is needed to make this function 79 | // visible via JNI in C code. 80 | @JvmStatic fun onLaunchedCallback() { 81 | activeServer!!.signalConsumer(LAUNCHED, null) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:react-native-builder-bob/babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { fixupConfigRules } from '@eslint/compat'; 2 | import { FlatCompat } from '@eslint/eslintrc'; 3 | import js from '@eslint/js'; 4 | import { defineConfig } from 'eslint/config'; 5 | import path from 'node:path'; 6 | import { fileURLToPath } from 'node:url'; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const compat = new FlatCompat({ 11 | baseDirectory: __dirname, 12 | recommendedConfig: js.configs.recommended, 13 | allConfig: js.configs.all, 14 | }); 15 | 16 | export default defineConfig([ 17 | { 18 | extends: fixupConfigRules(compat.extends('@react-native')), 19 | rules: { 20 | curly: ['error', 'multi-line'], 21 | 'react/react-in-jsx-scope': 'off', 22 | }, 23 | }, 24 | { 25 | ignores: [ 26 | 'node_modules/', 27 | 'lib/' 28 | ], 29 | }, 30 | ]); 31 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /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 | # Exclude problematic versions of cocoapods and activesupport that causes build failures. 7 | gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' 8 | gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' 9 | gem 'xcodeproj', '< 1.26.0' 10 | gem 'concurrent-ruby', '< 1.3.4' 11 | 12 | # Ruby 3.4.0 has removed some libraries from the standard library. 13 | gem 'bigdecimal' 14 | gem 'logger' 15 | gem 'benchmark' 16 | gem 'mutex_m' 17 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # React Native Static Server Example 2 | 3 | This example React Native application showcases the basic use of 4 | [@dr.pogodin/react-native-static-server] to serve a simple static website which 5 | is rendered inside the app using [react-native-webview] component. It also 6 | demonstrates a few optional features relevant to such example, like messaging 7 | between the native app and a web app inside [react-native-webview], and opening 8 | selected web app links separately in the system browser app. 9 | 10 | **BEWARE:** _To facilitate library development needs, this example is set up 11 | differently from a real project — instead of consuming library code from 12 | a node module installed from NPM, this example consumes the library from its 13 | parent folder. Also dev setup works only with [Yarn]._ 14 | 15 | To install dependencies and run the example do in the library code base root: 16 | ` 17 | ```shell 18 | # Installs node modules, both in the root, and in the example folder. 19 | yarn install 20 | 21 | # This clones and checks out the source code for PCRE2 and Lighttpd, 22 | # which would be already packed into the library package from NPM. 23 | git submodule update --init --recursive 24 | 25 | # Launches the development server. 26 | yarn example start 27 | ``` 28 | 29 | On **Android**: 30 | - `yarn example android` (in the library codebase root) — 31 | builds & deploys the example app. 32 | 33 | On **iOS**: 34 | - Install pods: 35 | ```sh 36 | cd example/ios 37 | RCT_NEW_ARCH_ENABLED=1 pod install 38 | ``` 39 | Here `RCT_NEW_ARCH_ENABLED=1` is optional, omit it to build for the old RN 40 | architecture. 41 | 42 | - Then open, build, and run the example project in XCode. 43 | 44 | [@dr.pogodin/react-native-static-server]: https://www.npmjs.com/package/@dr.pogodin/react-native-static-server 45 | [react-native-webview]: https://www.npmjs.com/package/react-native-webview 46 | [Yarn]: https://yarnpkg.com/ 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | /** 6 | * This is the configuration block to customize your React Native Android app. 7 | * By default you don't need to apply any configuration, just uncomment the lines you need. 8 | */ 9 | react { 10 | /* Folders */ 11 | // The root of your project, i.e. where "package.json" lives. Default is '../..' 12 | // root = file("../../") 13 | // The folder where the react-native NPM package is. Default is ../../node_modules/react-native 14 | // reactNativeDir = file("../../node_modules/react-native") 15 | // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen 16 | // codegenDir = file("../../node_modules/@react-native/codegen") 17 | // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js 18 | // cliFile = file("../../node_modules/react-native/cli.js") 19 | 20 | /* Variants */ 21 | // The list of variants to that are debuggable. For those we're going to 22 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 23 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 24 | // debuggableVariants = ["liteDebug", "prodDebug"] 25 | 26 | /* Bundling */ 27 | // A list containing the node command and its flags. Default is just 'node'. 28 | // nodeExecutableAndArgs = ["node"] 29 | // 30 | // The command to run when bundling. By default is 'bundle' 31 | // bundleCommand = "ram-bundle" 32 | // 33 | // The path to the CLI configuration file. Default is empty. 34 | // bundleConfig = file(../rn-cli.config.js) 35 | // 36 | // The name of the generated asset file containing your JS bundle 37 | // bundleAssetName = "MyApplication.android.bundle" 38 | // 39 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 40 | // entryFile = file("../js/MyApplication.android.js") 41 | // 42 | // A list of extra flags to pass to the 'bundle' commands. 43 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 44 | // extraPackagerArgs = [] 45 | 46 | /* Hermes Commands */ 47 | // The hermes compiler command to run. By default it is 'hermesc' 48 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 49 | // 50 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 51 | // hermesFlags = ["-O", "-output-source-map"] 52 | 53 | /* Autolinking */ 54 | autolinkLibrariesWithApp() 55 | } 56 | 57 | /** 58 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 59 | */ 60 | def enableProguardInReleaseBuilds = true 61 | 62 | /** 63 | * The preferred build flavor of JavaScriptCore (JSC) 64 | * 65 | * For example, to use the international variant, you can use: 66 | * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` 67 | * 68 | * The international variant includes ICU i18n library and necessary data 69 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 70 | * give correct results when using with locales other than en-US. Note that 71 | * this variant is about 6MiB larger per architecture than default. 72 | */ 73 | def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' 74 | 75 | android { 76 | ndkVersion rootProject.ext.ndkVersion 77 | buildToolsVersion rootProject.ext.buildToolsVersion 78 | compileSdk rootProject.ext.compileSdkVersion 79 | 80 | namespace "drpogodin.reactnativestaticserver.example" 81 | defaultConfig { 82 | applicationId "drpogodin.reactnativestaticserver.example" 83 | minSdkVersion rootProject.ext.minSdkVersion 84 | targetSdkVersion rootProject.ext.targetSdkVersion 85 | versionCode 1 86 | versionName "1.0" 87 | } 88 | 89 | sourceSets { 90 | main { 91 | assets.srcDirs = ['../../assets'] 92 | } 93 | } 94 | signingConfigs { 95 | debug { 96 | storeFile file('debug.keystore') 97 | storePassword 'android' 98 | keyAlias 'androiddebugkey' 99 | keyPassword 'android' 100 | } 101 | } 102 | buildTypes { 103 | debug { 104 | signingConfig signingConfigs.debug 105 | } 106 | release { 107 | // Caution! In production, you need to generate your own keystore file. 108 | // see https://reactnative.dev/docs/signed-apk-android. 109 | signingConfig signingConfigs.debug 110 | minifyEnabled enableProguardInReleaseBuilds 111 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 112 | } 113 | } 114 | } 115 | 116 | dependencies { 117 | // The version of react-native is set by the React Native Gradle Plugin 118 | implementation("com.facebook.react:react-android") 119 | 120 | if (hermesEnabled.toBoolean()) { 121 | implementation("com.facebook.react:hermes-android") 122 | } else { 123 | implementation jscFlavor 124 | } 125 | } 126 | 127 | // Run Codegen during development for the example app. 128 | tasks.register('invokeLibraryCodegen', Exec) { 129 | workingDir "$rootDir/../../" 130 | def isWindows = System.getProperty('os.name').toLowerCase().contains('windows') 131 | 132 | if (isWindows) { 133 | commandLine 'cmd', '/c', 'npx bob build --target codegen' 134 | } else { 135 | commandLine 'sh', '-c', 'npx bob build --target codegen' 136 | } 137 | } 138 | 139 | preBuild.dependsOn invokeLibraryCodegen 140 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/drpogodin/reactnativestaticserver/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package drpogodin.reactnativestaticserver.example 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : 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 fun getMainComponentName(): String = "ReactNativeStaticServerExample" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/drpogodin/reactnativestaticserver/example/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package drpogodin.reactnativestaticserver.example 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List = 20 | PackageList(this).packages.apply { 21 | // Packages that cannot be autolinked yet can be added manually here, for example: 22 | // add(MyReactNativePackage()) 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, OpenSourceMergedSoMapping) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ReactNativeStaticServerExample 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | buildToolsVersion = "35.0.0" 4 | minSdkVersion = 24 5 | compileSdkVersion = 35 6 | targetSdkVersion = 35 7 | ndkVersion = "27.1.12297006" 8 | kotlinVersion = "2.0.21" 9 | } 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath("com.android.tools.build:gradle") 16 | classpath("com.facebook.react:react-native-gradle-plugin") 17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 18 | } 19 | } 20 | 21 | apply plugin: "com.facebook.react.rootproject" 22 | -------------------------------------------------------------------------------- /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 | 25 | # Use this property to specify which architecture you want to build. 26 | # You can also override it from the CLI using 27 | # ./gradlew -PreactNativeArchitectures=x86_64 28 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 29 | 30 | # Use this property to enable support to the new architecture. 31 | # This will allow you to use TurboModules and the Fabric render in 32 | # your application. You should enable this flag either if you want 33 | # to write custom TurboModules/Fabric components OR use libraries that 34 | # are providing them. 35 | newArchEnabled=true 36 | 37 | # Use this property to enable or disable the Hermes JS engine. 38 | # If set to false, you will be using JSC instead. 39 | hermesEnabled=true 40 | 41 | ReactNativeStaticServer_webdav = true 42 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/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.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } 2 | plugins { id("com.facebook.react.settings") } 3 | extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } 4 | rootProject.name = 'drpogodin.reactnativestaticserver.example' 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNativeStaticServerExample", 3 | "displayName": "ReactNativeStaticServerExample" 4 | } 5 | -------------------------------------------------------------------------------- /example/assets/webroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Hello from React Native Static Server!

10 |

11 | 12 | @dr.pogodin/react-native-static-server 13 | — Embed HTTP server for React Native applications. 14 |

15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/assets/webroot/script.js: -------------------------------------------------------------------------------- 1 | // This will handle incoming messages from the React Native layer, 2 | // the actual .onNativeMessage name can be any name we decide to use 3 | // in our message envelope on the native side. 4 | window.onNativeMessage = function (message) { 5 | // eslint-disable-next-line no-alert 6 | alert(`Got message from React Native layer: ${message}`); 7 | }; 8 | 9 | document.getElementById('message-to-rn').addEventListener('click', function () { 10 | // .ReactNativeWebView is automatically attached to the `window` by the host 11 | // WebView component, and it has .postMessage() method allowing to send text 12 | // messages to the React Native layer. 13 | if (window.ReactNativeWebView) { 14 | const message = 'Hello from the WebView content!'; 15 | window.ReactNativeWebView.postMessage(message); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /example/assets/webroot/style.css: -------------------------------------------------------------------------------- 1 | .header { 2 | background: linear-gradient(to right, red, orange, yellow, green, skyblue, blue, purple); 3 | background-clip: text; 4 | color: transparent; 5 | display: inline-block; 6 | font-weight: 900; 7 | text-decoration: underline red; 8 | -webkit-background-clip: text; 9 | } 10 | -------------------------------------------------------------------------------- /example/assets/webroot/version: -------------------------------------------------------------------------------- 1 | 1.0.0 -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getConfig } = require('react-native-builder-bob/babel-config'); 3 | const pkg = require('../package.json'); 4 | 5 | const root = path.resolve(__dirname, '..'); 6 | 7 | module.exports = getConfig( 8 | { 9 | presets: ['module:@react-native/babel-preset'], 10 | }, 11 | { root, pkg } 12 | ); 13 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src/App'; 3 | import { name as appName } from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /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 | 12 | # To allow finding Node installed via NVM. 13 | export NVM_DIR="$HOME/.nvm" 14 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 15 | 16 | export NODE_BINARY=$(command -v node) 17 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | ENV['RCT_NEW_ARCH_ENABLED'] = '1' 2 | 3 | # Resolve react_native_pods.rb with node to allow for hoisting 4 | require Pod::Executable.execute_command('node', ['-p', 5 | 'require.resolve( 6 | "react-native/scripts/react_native_pods.rb", 7 | {paths: [process.argv[1]]}, 8 | )', __dir__]).strip 9 | 10 | platform :ios, min_ios_version_supported 11 | prepare_react_native_project! 12 | 13 | linkage = ENV['USE_FRAMEWORKS'] 14 | if linkage != nil 15 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 16 | use_frameworks! :linkage => linkage.to_sym 17 | end 18 | 19 | target 'ReactNativeStaticServerExample' do 20 | config = use_native_modules! 21 | 22 | use_react_native!( 23 | :path => config[:reactNativePath], 24 | # An absolute path to your application root. 25 | :app_path => "#{Pod::Config.instance.installation_root}/.." 26 | ) 27 | 28 | 29 | # Run Codegen during development for the example app. 30 | pre_install do |installer| 31 | system("cd ../../ && npx bob build --target codegen") 32 | end 33 | 34 | post_install do |installer| 35 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 36 | react_native_post_install( 37 | installer, 38 | config[:reactNativePath], 39 | :mac_catalyst_enabled => ENV['MAC_CATALYST'] == '1' 40 | # :ccache_enabled => true 41 | ) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /example/ios/ReactNativeStaticServerExample.xcodeproj/xcshareddata/xcschemes/ReactNativeStaticServerExample.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/ReactNativeStaticServerExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/ReactNativeStaticServerExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import React 3 | import React_RCTAppDelegate 4 | import ReactAppDependencyProvider 5 | 6 | @main 7 | class AppDelegate: UIResponder, UIApplicationDelegate { 8 | var window: UIWindow? 9 | 10 | var reactNativeDelegate: ReactNativeDelegate? 11 | var reactNativeFactory: RCTReactNativeFactory? 12 | 13 | func application( 14 | _ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 16 | ) -> Bool { 17 | let delegate = ReactNativeDelegate() 18 | let factory = RCTReactNativeFactory(delegate: delegate) 19 | delegate.dependencyProvider = RCTAppDependencyProvider() 20 | 21 | reactNativeDelegate = delegate 22 | reactNativeFactory = factory 23 | 24 | window = UIWindow(frame: UIScreen.main.bounds) 25 | 26 | factory.startReactNative( 27 | withModuleName: "ReactNativeStaticServerExample", 28 | in: window, 29 | launchOptions: launchOptions 30 | ) 31 | 32 | return true 33 | } 34 | } 35 | 36 | class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { 37 | override func sourceURL(for bridge: RCTBridge) -> URL? { 38 | self.bundleURL() 39 | } 40 | 41 | override func bundleURL() -> URL? { 42 | #if DEBUG 43 | RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") 44 | #else 45 | Bundle.main.url(forResource: "main", withExtension: "jsbundle") 46 | #endif 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/ios/ReactNativeStaticServerExample/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/ReactNativeStaticServerExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/ReactNativeStaticServerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ReactNativeStaticServerExample 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 | 30 | NSAllowsArbitraryLoads 31 | 32 | NSAllowsLocalNetworking 33 | 34 | 35 | NSLocationWhenInUseUsageDescription 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIRequiredDeviceCapabilities 40 | 41 | arm64 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/ios/ReactNativeStaticServerExample/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/ReactNativeStaticServerExample/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 0A2A.1 14 | 15 | 16 | 17 | NSPrivacyAccessedAPIType 18 | NSPrivacyAccessedAPICategoryUserDefaults 19 | NSPrivacyAccessedAPITypeReasons 20 | 21 | CA92.1 22 | 23 | 24 | 25 | NSPrivacyAccessedAPIType 26 | NSPrivacyAccessedAPICategoryDiskSpace 27 | NSPrivacyAccessedAPITypeReasons 28 | 29 | 85F4.1 30 | E174.1 31 | 32 | 33 | 34 | NSPrivacyAccessedAPIType 35 | NSPrivacyAccessedAPICategorySystemBootTime 36 | NSPrivacyAccessedAPITypeReasons 37 | 38 | 35F9.1 39 | 40 | 41 | 42 | NSPrivacyCollectedDataTypes 43 | 44 | NSPrivacyTracking 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/ReactNativeStaticServerExample/ReactNativeStaticServerExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.personal-information.location 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { getDefaultConfig } = require('@react-native/metro-config'); 3 | const { getConfig } = require('react-native-builder-bob/metro-config'); 4 | const pkg = require('../package.json'); 5 | 6 | const root = path.resolve(__dirname, '..'); 7 | 8 | /** 9 | * Metro configuration 10 | * https://facebook.github.io/metro/docs/configuration 11 | * 12 | * @type {import('metro-config').MetroConfig} 13 | */ 14 | module.exports = getConfig(getDefaultConfig(__dirname), { 15 | root, 16 | pkg, 17 | project: __dirname, 18 | }); 19 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dr.pogodin/react-native-static-server-example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "windows:autolink": "./node_modules/.bin/rnc-cli autolink-windows --sln \"windows\\ReactNativeStaticServerExample.sln\" --proj \"windows\\ReactNativeStaticServerExample\\ReactNativeStaticServerExample.vcxproj\"", 10 | "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", 11 | "build:ios": "react-native build-ios --scheme ReactNativeStaticServerExample --mode Debug --extra-params \"-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO\"" 12 | }, 13 | "dependencies": { 14 | "@dr.pogodin/react-native-fs": "^2.33.1", 15 | "@dr.pogodin/react-native-webview": "^13.15.8", 16 | "react": "19.0.0", 17 | "react-native": "0.79.1", 18 | "react-native-windows": "0.78.5" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.26.10", 22 | "@babel/preset-env": "^7.26.9", 23 | "@babel/runtime": "^7.27.0", 24 | "@react-native-community/cli": "18.0.0", 25 | "@react-native-community/cli-platform-android": "18.0.0", 26 | "@react-native-community/cli-platform-ios": "18.0.0", 27 | "@react-native/babel-preset": "0.79.1", 28 | "@react-native/metro-config": "0.79.1", 29 | "@react-native/typescript-config": "0.79.1", 30 | "@types/react": "^19.0.0", 31 | "react-native-builder-bob": "^0.40.6" 32 | }, 33 | "engines": { 34 | "node": ">=18" 35 | }, 36 | "react-native-windows": { 37 | "init-windows": { 38 | "name": "ReactNativeStaticServerExample", 39 | "namespace": "ReactNativeStaticServerExample", 40 | "template": "old/uwp-cpp-app" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/react-native.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pkg = require('../package.json'); 3 | 4 | module.exports = { 5 | project: { 6 | ios: { 7 | automaticPodsInstallation: true, 8 | }, 9 | }, 10 | dependencies: { 11 | [pkg.name]: { 12 | root: path.join(__dirname, '..'), 13 | platforms: { 14 | // Codegen script incorrectly fails without this 15 | // So we explicitly specify the platforms with empty object 16 | ios: {}, 17 | android: {}, 18 | }, 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | *AppPackages* 2 | *BundleArtifacts* 3 | 4 | #OS junk files 5 | [Tt]humbs.db 6 | *.DS_Store 7 | 8 | #Visual Studio files 9 | *.[Oo]bj 10 | *.user 11 | *.aps 12 | *.pch 13 | *.vspscc 14 | *.vssscc 15 | *_i.c 16 | *_p.c 17 | *.ncb 18 | *.suo 19 | *.tlb 20 | *.tlh 21 | *.bak 22 | *.[Cc]ache 23 | *.ilk 24 | *.log 25 | *.lib 26 | *.sbr 27 | *.sdf 28 | *.opensdf 29 | *.opendb 30 | *.unsuccessfulbuild 31 | ipch/ 32 | [Oo]bj/ 33 | [Bb]in 34 | [Dd]ebug*/ 35 | [Rr]elease*/ 36 | Ankh.NoLoad 37 | 38 | # Visual C++ cache files 39 | ipch/ 40 | *.aps 41 | *.ncb 42 | *.opendb 43 | *.opensdf 44 | *.sdf 45 | *.cachefile 46 | *.VC.db 47 | *.VC.VC.opendb 48 | 49 | #MonoDevelop 50 | *.pidb 51 | *.userprefs 52 | 53 | #Tooling 54 | _ReSharper*/ 55 | *.resharper 56 | [Tt]est[Rr]esult* 57 | *.sass-cache 58 | 59 | #Project files 60 | [Bb]uild/ 61 | 62 | #Subversion files 63 | .svn 64 | 65 | # Office Temp Files 66 | ~$* 67 | 68 | # vim Temp Files 69 | *~ 70 | 71 | #NuGet 72 | packages/ 73 | *.nupkg 74 | 75 | #ncrunch 76 | *ncrunch* 77 | *crunch*.local.xml 78 | 79 | # visual studio database projects 80 | *.dbmdl 81 | 82 | #Test files 83 | *.testsettings 84 | 85 | #Other files 86 | *.DotSettings 87 | .vs/ 88 | *project.lock.json 89 | 90 | #Files generated by the VS build 91 | **/Generated Files/** 92 | 93 | -------------------------------------------------------------------------------- /example/windows/ExperimentalFeatures.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 17 | true 18 | 19 | 26 | false 27 | 28 | true 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/windows/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/.gitignore: -------------------------------------------------------------------------------- 1 | /Bundle 2 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/App.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | // #pragma warning(disable:4996) 4 | 5 | #include "App.h" 6 | 7 | #include "AutolinkedNativeModules.g.h" 8 | #include "ReactPackageProvider.h" 9 | 10 | using namespace winrt; 11 | using namespace xaml; 12 | using namespace xaml::Controls; 13 | using namespace xaml::Navigation; 14 | 15 | using namespace Windows::ApplicationModel; 16 | namespace winrt::ReactNativeStaticServerExample::implementation 17 | { 18 | /// 19 | /// Initializes the singleton application object. This is the first line of 20 | /// authored code executed, and as such is the logical equivalent of main() or 21 | /// WinMain(). 22 | /// 23 | App::App() noexcept 24 | { 25 | /* 26 | AllocConsole(); 27 | freopen("CONOUT$", "w", stdout); 28 | freopen("CONOUT$", "w", stderr); 29 | */ 30 | #if BUNDLE 31 | JavaScriptBundleFile(L"index.windows"); 32 | InstanceSettings().UseFastRefresh(false); 33 | #else 34 | JavaScriptBundleFile(L"index"); 35 | InstanceSettings().UseFastRefresh(true); 36 | #endif 37 | 38 | #if _DEBUG 39 | InstanceSettings().UseDirectDebugger(true); 40 | InstanceSettings().UseDeveloperSupport(true); 41 | #else 42 | InstanceSettings().UseDirectDebugger(false); 43 | InstanceSettings().UseDeveloperSupport(false); 44 | #endif 45 | 46 | RegisterAutolinkedNativeModulePackages(PackageProviders()); // Includes any autolinked modules 47 | 48 | PackageProviders().Append(make()); // Includes all modules in this project 49 | 50 | InitializeComponent(); 51 | } 52 | 53 | /// 54 | /// Invoked when the application is launched normally by the end user. Other entry points 55 | /// will be used such as when the application is launched to open a specific file. 56 | /// 57 | /// Details about the launch request and process. 58 | void App::OnLaunched(activation::LaunchActivatedEventArgs const& e) 59 | { 60 | super::OnLaunched(e); 61 | 62 | Frame rootFrame = Window::Current().Content().as(); 63 | rootFrame.Navigate(xaml_typename(), box_value(e.Arguments())); 64 | } 65 | 66 | /// 67 | /// Invoked when the application is activated by some means other than normal launching. 68 | /// 69 | void App::OnActivated(Activation::IActivatedEventArgs const &e) { 70 | auto preActivationContent = Window::Current().Content(); 71 | super::OnActivated(e); 72 | if (!preActivationContent && Window::Current()) { 73 | Frame rootFrame = Window::Current().Content().as(); 74 | rootFrame.Navigate(xaml_typename(), nullptr); 75 | } 76 | } 77 | 78 | /// 79 | /// Invoked when application execution is being suspended. Application state is saved 80 | /// without knowing whether the application will be terminated or resumed with the contents 81 | /// of memory still intact. 82 | /// 83 | /// The source of the suspend request. 84 | /// Details about the suspend request. 85 | void App::OnSuspending([[maybe_unused]] IInspectable const& sender, [[maybe_unused]] SuspendingEventArgs const& e) 86 | { 87 | // Save application state and stop any background activity 88 | } 89 | 90 | /// 91 | /// Invoked when Navigation to a certain page fails 92 | /// 93 | /// The Frame which failed navigation 94 | /// Details about the navigation failure 95 | void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e) 96 | { 97 | throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name); 98 | } 99 | 100 | } // namespace winrt::ReactNativeStaticServerExample::implementation 101 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/App.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App.xaml.g.h" 4 | 5 | #include 6 | 7 | namespace activation = winrt::Windows::ApplicationModel::Activation; 8 | 9 | namespace winrt::ReactNativeStaticServerExample::implementation 10 | { 11 | struct App : AppT 12 | { 13 | App() noexcept; 14 | void OnLaunched(activation::LaunchActivatedEventArgs const&); 15 | void OnActivated(Windows::ApplicationModel::Activation::IActivatedEventArgs const &e); 16 | void OnSuspending(IInspectable const&, Windows::ApplicationModel::SuspendingEventArgs const&); 17 | void OnNavigationFailed(IInspectable const&, xaml::Navigation::NavigationFailedEventArgs const&); 18 | private: 19 | using super = AppT; 20 | }; 21 | } // namespace winrt::ReactNativeStaticServerExample::implementation 22 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/App.idl: -------------------------------------------------------------------------------- 1 | namespace ReactNativeStaticServerExample 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/windows/ReactNativeStaticServerExample/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/windows/ReactNativeStaticServerExample/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/windows/ReactNativeStaticServerExample/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/windows/ReactNativeStaticServerExample/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/windows/ReactNativeStaticServerExample/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/windows/ReactNativeStaticServerExample/Assets/StoreLogo.png -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/example/windows/ReactNativeStaticServerExample/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/AutolinkedNativeModules.g.cpp: -------------------------------------------------------------------------------- 1 | // AutolinkedNativeModules.g.cpp contents generated by "npx @react-native-community/cli autolink-windows" 2 | // clang-format off 3 | #include "pch.h" 4 | #include "AutolinkedNativeModules.g.h" 5 | 6 | // Includes from @dr.pogodin/react-native-static-server 7 | #include 8 | 9 | // Includes from @dr.pogodin/react-native-fs 10 | #include 11 | 12 | // Includes from @dr.pogodin/react-native-webview 13 | #include 14 | 15 | namespace winrt::Microsoft::ReactNative 16 | { 17 | 18 | void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) 19 | { 20 | // IReactPackageProviders from @dr.pogodin/react-native-static-server 21 | packageProviders.Append(winrt::ReactNativeStaticServer::ReactPackageProvider()); 22 | // IReactPackageProviders from @dr.pogodin/react-native-fs 23 | packageProviders.Append(winrt::ReactNativeFs::ReactPackageProvider()); 24 | // IReactPackageProviders from @dr.pogodin/react-native-webview 25 | packageProviders.Append(winrt::ReactNativeWebView::ReactPackageProvider()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/AutolinkedNativeModules.g.h: -------------------------------------------------------------------------------- 1 | // AutolinkedNativeModules.g.h contents generated by "npx @react-native-community/cli autolink-windows" 2 | // clang-format off 3 | #pragma once 4 | 5 | namespace winrt::Microsoft::ReactNative 6 | { 7 | 8 | void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/AutolinkedNativeModules.g.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/AutolinkedNativeModules.g.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {16da6f7d-0d78-4550-bcf3-a340a17c7fd9} 8 | 9 | 10 | 11 | {53640a4d-6b5f-4963-872b-27e5aa4f96ee} 12 | 13 | 14 | 15 | {00AA3765-C6A0-4713-B3F9-BFE47B9C83F5} 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/MainPage.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "MainPage.h" 3 | #if __has_include("MainPage.g.cpp") 4 | #include "MainPage.g.cpp" 5 | #endif 6 | 7 | #include "App.h" 8 | 9 | using namespace winrt; 10 | using namespace xaml; 11 | 12 | namespace winrt::ReactNativeStaticServerExample::implementation 13 | { 14 | MainPage::MainPage() 15 | { 16 | InitializeComponent(); 17 | auto app = Application::Current().as(); 18 | ReactRootView().ReactNativeHost(app->Host()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/MainPage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "MainPage.g.h" 3 | #include 4 | 5 | namespace winrt::ReactNativeStaticServerExample::implementation 6 | { 7 | struct MainPage : MainPageT 8 | { 9 | MainPage(); 10 | }; 11 | } 12 | 13 | namespace winrt::ReactNativeStaticServerExample::factory_implementation 14 | { 15 | struct MainPage : MainPageT 16 | { 17 | }; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/MainPage.idl: -------------------------------------------------------------------------------- 1 | #include "NamespaceRedirect.h" 2 | 3 | namespace ReactNativeStaticServerExample 4 | { 5 | [default_interface] 6 | runtimeclass MainPage : XAML_NAMESPACE.Controls.Page 7 | { 8 | MainPage(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/MainPage.xaml: -------------------------------------------------------------------------------- 1 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | ReactNativeStaticServerExample 18 | drpog 19 | Assets\StoreLogo.png 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/PropertySheet.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | 20 | 21 | <_CustomResource Include="..\..\assets\webroot\**\*"> 22 | webroot\%(RecursiveDir)%(FileName)%(Extension) 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/ReactNativeStaticServerExample.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Assets 25 | 26 | 27 | Assets 28 | 29 | 30 | Assets 31 | 32 | 33 | Assets 34 | 35 | 36 | Assets 37 | 38 | 39 | Assets 40 | 41 | 42 | Assets 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {e48dc53e-40b1-40cb-970a-f89935452892} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/ReactPackageProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ReactPackageProvider.h" 3 | #include "NativeModules.h" 4 | 5 | using namespace winrt::Microsoft::ReactNative; 6 | 7 | namespace winrt::ReactNativeStaticServerExample::implementation 8 | { 9 | 10 | void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept 11 | { 12 | AddAttributedModules(packageBuilder, true); 13 | } 14 | 15 | } // namespace winrt::ReactNativeStaticServerExample::implementation 16 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/ReactPackageProvider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "winrt/Microsoft.ReactNative.h" 4 | 5 | namespace winrt::ReactNativeStaticServerExample::implementation 6 | { 7 | struct ReactPackageProvider : winrt::implements 8 | { 9 | public: // IReactPackageProvider 10 | void CreatePackage(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept; 11 | }; 12 | } // namespace winrt::ReactNativeStaticServerExample::implementation 13 | 14 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "native,Version=v0.0": { 5 | "Microsoft.JavaScript.Hermes": { 6 | "type": "Direct", 7 | "requested": "[0.1.23, )", 8 | "resolved": "0.1.23", 9 | "contentHash": "cA9t1GjY4Yo0JD1AfA//e1lOwk48hLANfuX6GXrikmEBNZVr2TIX5ONJt5tqCnpZyLz6xGiPDgTfFNKbSfb21g==" 10 | }, 11 | "Microsoft.UI.Xaml": { 12 | "type": "Direct", 13 | "requested": "[2.8.0, )", 14 | "resolved": "2.8.0", 15 | "contentHash": "vxdHxTr63s5KVtNddMFpgvjBjUH50z7seq/5jLWmmSuf8poxg+sXrywkofUdE8ZstbpO9y3FL/IXXUcPYbeesA==", 16 | "dependencies": { 17 | "Microsoft.Web.WebView2": "1.0.1264.42" 18 | } 19 | }, 20 | "Microsoft.Windows.CppWinRT": { 21 | "type": "Direct", 22 | "requested": "[2.0.230706.1, )", 23 | "resolved": "2.0.230706.1", 24 | "contentHash": "l0D7oCw/5X+xIKHqZTi62TtV+1qeSz7KVluNFdrJ9hXsst4ghvqQ/Yhura7JqRdZWBXAuDS0G0KwALptdoxweQ==" 25 | }, 26 | "boost": { 27 | "type": "Transitive", 28 | "resolved": "1.83.0", 29 | "contentHash": "cy53VNMzysEMvhBixDe8ujPk67Fcj3v6FPHQnH91NYJNLHpc6jxa2xq9ruCaaJjE4M3YrGSHDi4uUSTGBWw6EQ==" 30 | }, 31 | "Microsoft.Web.WebView2": { 32 | "type": "Transitive", 33 | "resolved": "1.0.1264.42", 34 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 35 | }, 36 | "common": { 37 | "type": "Project", 38 | "dependencies": { 39 | "boost": "[1.83.0, )" 40 | } 41 | }, 42 | "fmt": { 43 | "type": "Project" 44 | }, 45 | "folly": { 46 | "type": "Project", 47 | "dependencies": { 48 | "Fmt": "[1.0.0, )", 49 | "boost": "[1.83.0, )" 50 | } 51 | }, 52 | "microsoft.reactnative": { 53 | "type": "Project", 54 | "dependencies": { 55 | "Common": "[1.0.0, )", 56 | "Folly": "[1.0.0, )", 57 | "Microsoft.JavaScript.Hermes": "[0.1.23, )", 58 | "Microsoft.UI.Xaml": "[2.8.0, )", 59 | "ReactCommon": "[1.0.0, )", 60 | "boost": "[1.83.0, )" 61 | } 62 | }, 63 | "reactcommon": { 64 | "type": "Project", 65 | "dependencies": { 66 | "Folly": "[1.0.0, )", 67 | "boost": "[1.83.0, )" 68 | } 69 | }, 70 | "reactnativefs": { 71 | "type": "Project", 72 | "dependencies": { 73 | "Microsoft.ReactNative": "[1.0.0, )", 74 | "Microsoft.UI.Xaml": "[2.8.0, )" 75 | } 76 | }, 77 | "reactnativestaticserver": { 78 | "type": "Project", 79 | "dependencies": { 80 | "Microsoft.ReactNative": "[1.0.0, )", 81 | "Microsoft.UI.Xaml": "[2.8.0, )" 82 | } 83 | }, 84 | "reactnativewebview": { 85 | "type": "Project", 86 | "dependencies": { 87 | "Microsoft.ReactNative": "[1.0.0, )", 88 | "Microsoft.UI.Xaml": "[2.8.0, )" 89 | } 90 | } 91 | }, 92 | "native,Version=v0.0/win10-arm": { 93 | "Microsoft.Web.WebView2": { 94 | "type": "Transitive", 95 | "resolved": "1.0.1264.42", 96 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 97 | } 98 | }, 99 | "native,Version=v0.0/win10-arm-aot": { 100 | "Microsoft.Web.WebView2": { 101 | "type": "Transitive", 102 | "resolved": "1.0.1264.42", 103 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 104 | } 105 | }, 106 | "native,Version=v0.0/win10-arm64-aot": { 107 | "Microsoft.Web.WebView2": { 108 | "type": "Transitive", 109 | "resolved": "1.0.1264.42", 110 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 111 | } 112 | }, 113 | "native,Version=v0.0/win10-x64": { 114 | "Microsoft.Web.WebView2": { 115 | "type": "Transitive", 116 | "resolved": "1.0.1264.42", 117 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 118 | } 119 | }, 120 | "native,Version=v0.0/win10-x64-aot": { 121 | "Microsoft.Web.WebView2": { 122 | "type": "Transitive", 123 | "resolved": "1.0.1264.42", 124 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 125 | } 126 | }, 127 | "native,Version=v0.0/win10-x86": { 128 | "Microsoft.Web.WebView2": { 129 | "type": "Transitive", 130 | "resolved": "1.0.1264.42", 131 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 132 | } 133 | }, 134 | "native,Version=v0.0/win10-x86-aot": { 135 | "Microsoft.Web.WebView2": { 136 | "type": "Transitive", 137 | "resolved": "1.0.1264.42", 138 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 139 | } 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /example/windows/ReactNativeStaticServerExample/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NOMINMAX 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | using namespace winrt::Windows::Foundation; 25 | -------------------------------------------------------------------------------- /glob/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5 FATAL_ERROR) 2 | project(GLOB C) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | set(CMAKE_C_STANDARD_REQUIRED TRUE) 6 | 7 | set(HEADERS collate.h freebsd-compat.h glob.h) 8 | set(SOURCES glob.c) 9 | 10 | add_compile_definitions(__USE_BSD) 11 | add_library(glob ${HEADERS} ${SOURCES}) 12 | 13 | install(TARGETS glob) 14 | install(FILES ${HEADERS} DESTINATION include) 15 | -------------------------------------------------------------------------------- /glob/README.md: -------------------------------------------------------------------------------- 1 | The Glob library is a part of Android SDK starting from SDK 28; thus, to support 2 | older SDKs we compile it from sources. These source files are taken from 3 | the https://android.googlesource.com/platform/bionic repository 4 | ([bionic](https://en.wikipedia.org/wiki/Bionic_%28software%29) 5 | is Android's C library, math library, and dynamic linker). 6 | -------------------------------------------------------------------------------- /glob/collate.h: -------------------------------------------------------------------------------- 1 | #include "freebsd-compat.h" 2 | 3 | /** 4 | * [reallocarray(3)](https://man7.org/linux/man-pages/man3/realloc.3.html) resizes 5 | * allocated memory on the heap. 6 | * 7 | * Equivalent to `realloc(__ptr, __item_count * __item_size)` but fails if the 8 | * multiplication overflows. 9 | * 10 | * Returns a pointer (which may be different from `__ptr`) to the resized 11 | * memory on success and returns a null pointer and sets `errno` on failure 12 | * (but see the notes for malloc()). 13 | */ 14 | #if __ANDROID_API__ >= 29 15 | void* _Nullable reallocarray(void* _Nullable __ptr, size_t __item_count, size_t __item_size) __BIONIC_ALLOC_SIZE(2, 3) __INTRODUCED_IN(29); 16 | #else 17 | #include 18 | static __inline void* _Nullable reallocarray(void* _Nullable __ptr, size_t __item_count, size_t __item_size) { 19 | size_t __new_size; 20 | if (__builtin_mul_overflow(__item_count, __item_size, &__new_size)) { 21 | errno = ENOMEM; 22 | return NULL; 23 | } 24 | return realloc(__ptr, __new_size); 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /glob/freebsd-compat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #define _BSD_SOURCE 20 | 21 | #define REPLACE_GETOPT 22 | 23 | /* FreeBSD has this, but we can't really implement it correctly on Linux. */ 24 | #define issetugid() 0 25 | 26 | #define __compiler_membar() __asm __volatile(" " : : : "memory") 27 | -------------------------------------------------------------------------------- /glob/glob.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1989, 1993 3 | * The Regents of the University of California. All rights reserved. 4 | * 5 | * This code is derived from software contributed to Berkeley by 6 | * Guido van Rossum. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. Neither the name of the University nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | * 32 | * @(#)glob.h 8.1 (Berkeley) 6/2/93 33 | * $FreeBSD$ 34 | */ 35 | 36 | #ifndef _GLOB_H_ 37 | #define _GLOB_H_ 38 | 39 | #include 40 | #include 41 | 42 | struct dirent; 43 | struct stat; 44 | 45 | typedef struct { 46 | size_t gl_pathc; /* Count of total paths so far. */ 47 | size_t gl_matchc; /* Count of paths matching pattern. */ 48 | size_t gl_offs; /* Reserved at beginning of gl_pathv. */ 49 | int gl_flags; /* Copy of flags parameter to glob. */ 50 | 51 | /** List of paths matching pattern. */ 52 | char* _Nullable * _Nullable gl_pathv; 53 | 54 | /** Copy of `__error_callback` parameter to glob. */ 55 | int (* _Nullable gl_errfunc)(const char* _Nonnull __failure_path, int __failure_errno); 56 | 57 | /** Called instead of closedir() when GLOB_ALTDIRFUNC flag is specified. */ 58 | void (* _Nullable gl_closedir)(void* _Nonnull); 59 | /** Called instead of readdir() when GLOB_ALTDIRFUNC flag is specified. */ 60 | struct dirent* _Nullable (* _Nonnull gl_readdir)(void* _Nonnull); 61 | /** Called instead of opendir() when GLOB_ALTDIRFUNC flag is specified. */ 62 | void* _Nullable (* _Nonnull gl_opendir)(const char* _Nonnull); 63 | /** Called instead of lstat() when GLOB_ALTDIRFUNC flag is specified. */ 64 | int (* _Nullable gl_lstat)(const char* _Nonnull, struct stat* _Nonnull); 65 | /** Called instead of stat() when GLOB_ALTDIRFUNC flag is specified. */ 66 | int (* _Nullable gl_stat)(const char* _Nonnull, struct stat* _Nonnull); 67 | } glob_t; 68 | 69 | /* Believed to have been introduced in 1003.2-1992 */ 70 | #define GLOB_APPEND 0x0001 /* Append to output from previous call. */ 71 | #define GLOB_DOOFFS 0x0002 /* Prepend `gl_offs` null pointers (leaving space for exec, say). */ 72 | #define GLOB_ERR 0x0004 /* Return on error. */ 73 | #define GLOB_MARK 0x0008 /* Append "/" to the names of returned directories. */ 74 | #define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ 75 | #define GLOB_NOSORT 0x0020 /* Don't sort. */ 76 | #define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */ 77 | 78 | /* Error values returned by glob(3) */ 79 | #define GLOB_NOSPACE (-1) /* Malloc call failed. */ 80 | #define GLOB_ABORTED (-2) /* Unignored error. */ 81 | #define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */ 82 | 83 | #if __USE_BSD 84 | #define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ 85 | #define GLOB_BRACE 0x0080 /* Expand braces like csh. */ 86 | #define GLOB_MAGCHAR 0x0100 /* Set in `gl_flags` if the pattern had globbing characters. */ 87 | #define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */ 88 | #define GLOB_QUOTE 0x0400 /* Quote special chars with \. */ 89 | #define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ 90 | #define GLOB_LIMIT 0x1000 /* limit number of returned paths */ 91 | #endif 92 | 93 | __BEGIN_DECLS 94 | 95 | int glob(const char* _Nonnull __pattern, int __flags, int (* _Nullable __error_callback)(const char* _Nonnull __failure_path, int __failure_errno), glob_t* _Nonnull __result_ptr); 96 | void globfree(glob_t* _Nonnull __result_ptr); 97 | 98 | __END_DECLS 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /ios/Errors.h: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.h 3 | // ReactNativeStaticServer 4 | // 5 | // Created by Sergey Pogodin on 10/4/23. 6 | // 7 | 8 | #ifndef Errors_h 9 | #define Errors_h 10 | 11 | #import 12 | 13 | @interface RNSSException : NSException 14 | - (id) initWithName: (NSString*)name details: (NSString*)details; 15 | - (NSError*) error; 16 | - (RNSSException*) log; 17 | - (void) reject:(RCTPromiseRejectBlock)reject; 18 | + (RNSSException*) from: (NSException*)exception; 19 | + (RNSSException*) name: (NSString*)name; 20 | + (RNSSException*) name: (NSString*)name details: (NSString*)details; 21 | 22 | @property(readonly) NSInteger code; 23 | @end 24 | 25 | #endif /* Errors_h */ 26 | -------------------------------------------------------------------------------- /ios/Errors.mm: -------------------------------------------------------------------------------- 1 | #import "Errors.h" 2 | 3 | static NSString * const ERROR_DOMAIN = @"RNStaticServer"; 4 | 5 | @implementation RNSSException; 6 | 7 | - (id) initWithName:(NSString*)name details:(NSString*)details 8 | { 9 | self = [super initWithName:name reason:details userInfo:nil]; 10 | return self; 11 | } 12 | 13 | /** 14 | * Creates a new NSError object based on this RNSSException 15 | */ 16 | - (NSError*) error 17 | { 18 | return [NSError 19 | errorWithDomain: ERROR_DOMAIN 20 | code: self.code 21 | userInfo: self.userInfo 22 | ]; 23 | } 24 | 25 | - (RNSSException*) log 26 | { 27 | NSLog(@"%@: %@", self.name, self.reason); 28 | return self; 29 | } 30 | 31 | - (void) reject: (RCTPromiseRejectBlock)reject 32 | { 33 | reject(self.name, self.reason, [self error]); 34 | } 35 | 36 | + (RNSSException*) from: (NSException*)exception 37 | { 38 | return [[RNSSException alloc] 39 | initWithName: exception.name 40 | reason: exception.reason 41 | userInfo: exception.userInfo 42 | ]; 43 | } 44 | 45 | + (RNSSException*) name: (NSString*)name 46 | { 47 | return [[RNSSException alloc] initWithName:name details:nil]; 48 | } 49 | 50 | + (RNSSException*) name: (NSString*)name details:(NSString*)details 51 | { 52 | return [[RNSSException alloc] initWithName:name details:details]; 53 | } 54 | 55 | @end; 56 | -------------------------------------------------------------------------------- /ios/ReactNativeStaticServer.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "generated/RNReactNativeStaticServerSpec/RNReactNativeStaticServerSpec.h" 4 | 5 | @interface ReactNativeStaticServer : RCTEventEmitter 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/ReactNativeStaticServer.mm: -------------------------------------------------------------------------------- 1 | #import "ReactNativeStaticServer.h" 2 | #import "Server.h" 3 | #import "Errors.h" 4 | #import 5 | #import 6 | #include 7 | 8 | static NSString * const EVENT_NAME = @"RNStaticServer"; 9 | static dispatch_semaphore_t sem = dispatch_semaphore_create(1); 10 | 11 | @implementation ReactNativeStaticServer { 12 | Server *server; 13 | } 14 | 15 | RCT_EXPORT_MODULE(); 16 | 17 | - (instancetype)init { 18 | return [super init]; 19 | } 20 | 21 | - (void)invalidate 22 | { 23 | [super invalidate]; 24 | if (self->server) { 25 | [self stop:^void(id){} 26 | reject:^void(NSString *a,NSString *b, NSError *c){}]; 27 | } 28 | } 29 | 30 | - (NSDictionary*) constantsToExport { 31 | return @{ 32 | @"CRASHED": CRASHED, 33 | @"IS_MAC_CATALYST": @(TARGET_OS_MACCATALYST), 34 | @"LAUNCHED": LAUNCHED, 35 | @"TERMINATED": TERMINATED 36 | }; 37 | } 38 | 39 | - (NSDictionary*) getConstants { 40 | return [self constantsToExport]; 41 | } 42 | 43 | RCT_REMAP_METHOD(getActiveServerId, 44 | getActiveServerId:(RCTPromiseResolveBlock) resolve 45 | reject:(RCTPromiseRejectBlock)reject 46 | ) { 47 | resolve(self->server ? self->server.serverId : [NSNull null]); 48 | } 49 | 50 | RCT_REMAP_METHOD(getLocalIpAddress, 51 | getLocalIpAddress:(RCTPromiseResolveBlock)resolve 52 | reject:(RCTPromiseRejectBlock)reject 53 | ) { 54 | struct ifaddrs *interfaces = NULL; // a linked list of network interfaces 55 | @try { 56 | struct ifaddrs *temp_addr = NULL; 57 | int success = getifaddrs(&interfaces); // get the list of network interfaces 58 | if (success == 0) { 59 | NSLog(@"Found network interfaces, iterating."); 60 | temp_addr = interfaces; 61 | while(temp_addr != NULL) { 62 | // Check if the current interface is of type AF_INET (IPv4) 63 | // and not the loopback interface (lo0) 64 | if(temp_addr->ifa_addr->sa_family == AF_INET) { 65 | if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { 66 | NSLog(@"Found IPv4 address of the local wifi connection. Returning address."); 67 | NSString *ip = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; 68 | resolve(ip); 69 | return; 70 | } 71 | } 72 | temp_addr = temp_addr->ifa_next; 73 | } 74 | } 75 | NSLog(@"Could not find IP address, falling back to '127.0.0.1'."); 76 | resolve(@"127.0.0.1"); 77 | } 78 | @catch (NSException *e) { 79 | [[RNSSException from:e] reject:reject]; 80 | } 81 | @finally { 82 | freeifaddrs(interfaces); 83 | } 84 | } 85 | 86 | RCTPromiseResolveBlock pendingResolve = nil; 87 | RCTPromiseRejectBlock pendingReject = nil; 88 | 89 | RCT_REMAP_METHOD(start, 90 | start:(double)_serverId 91 | configPath:(NSString*)configPath 92 | errlogPath:(NSString*)errlogPath 93 | resolve:(RCTPromiseResolveBlock)resolve 94 | reject:(RCTPromiseRejectBlock)reject 95 | ) { 96 | NSLog(@"Starting the server..."); 97 | 98 | NSNumber *serverId = [NSNumber numberWithDouble:_serverId]; 99 | 100 | dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 101 | 102 | if (self->server) { 103 | NSString *name = [NSString stringWithFormat:@"Failed to launch server #%@, another server instance (#%@) is active", serverId, self->server.serverId]; 104 | auto e = [[RNSSException name:name] log]; 105 | [e reject:reject]; 106 | dispatch_semaphore_signal(sem); 107 | return; 108 | } 109 | 110 | if (pendingResolve != nil || pendingReject != nil) { 111 | NSString *name = [NSString stringWithFormat:@"Internal error (server #%@)", serverId]; 112 | auto e = [[RNSSException name:name details:@"Non-expected pending promise"] log]; 113 | [e reject:reject]; 114 | dispatch_semaphore_signal(sem); 115 | return; 116 | } 117 | 118 | pendingResolve = resolve; 119 | pendingReject = reject; 120 | 121 | SignalConsumer signalConsumer = ^void(NSString * const signal, 122 | NSString * const details) 123 | { 124 | if (signal != LAUNCHED) self->server = nil; 125 | if (pendingResolve == nil && pendingReject == nil) { 126 | [self sendEventWithName:EVENT_NAME 127 | body: @{ 128 | @"serverId": serverId, 129 | @"event": signal, 130 | @"details": details == nil ? @"" : details 131 | } 132 | ]; 133 | } else { 134 | if (signal == CRASHED) { 135 | NSString *name = [NSString stringWithFormat:@"Server #%@ crashed", serverId]; 136 | [[RNSSException name:name details:details] 137 | reject:pendingReject]; 138 | } else pendingResolve(details); 139 | pendingResolve = nil; 140 | pendingReject = nil; 141 | dispatch_semaphore_signal(sem); 142 | } 143 | }; 144 | 145 | self->server = [Server 146 | serverWithId:serverId 147 | configPath:configPath 148 | errlogPath:errlogPath 149 | signalConsumer:signalConsumer 150 | ]; 151 | 152 | [self->server start]; 153 | } 154 | 155 | - (NSArray *)supportedEvents { 156 | return @[EVENT_NAME]; 157 | } 158 | 159 | RCT_REMAP_METHOD(stop, 160 | stop:(RCTPromiseResolveBlock)resolve 161 | reject:(RCTPromiseRejectBlock)reject 162 | ) { 163 | try { 164 | if (self->server) { 165 | NSLog(@"Stopping..."); 166 | 167 | dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 168 | 169 | if (pendingResolve != nil || pendingReject != nil) { 170 | auto e = [[RNSSException name:@"Internal error" 171 | details:@"Unexpected pending promise"] log]; 172 | [e reject:reject]; 173 | dispatch_semaphore_signal(sem); 174 | return; 175 | } 176 | 177 | pendingResolve = resolve; 178 | pendingReject = reject; 179 | [self->server cancel]; 180 | } 181 | } catch (NSException *e) { 182 | [[RNSSException from:e] reject:reject]; 183 | } 184 | } 185 | 186 | RCT_REMAP_METHOD(getOpenPort, 187 | getOpenPort:(NSString*) address 188 | resolve:(RCTPromiseResolveBlock)resolve 189 | reject:(RCTPromiseRejectBlock)reject 190 | ) { 191 | @try { 192 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 193 | if (sockfd < 0) { 194 | [[RNSSException name:@"Error creating socket"] reject:reject]; 195 | return; 196 | } 197 | 198 | struct sockaddr_in serv_addr; 199 | memset(&serv_addr, 0, sizeof(serv_addr)); 200 | serv_addr.sin_family = AF_INET; 201 | serv_addr.sin_port = 0; 202 | if (!inet_aton([address cStringUsingEncoding:NSUTF8StringEncoding], &(serv_addr.sin_addr))) { 203 | [[RNSSException name:@"Invalid address format"] reject:reject]; 204 | return; 205 | } 206 | 207 | if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { 208 | [[RNSSException name:@"Error binding socket"] reject:reject]; 209 | return; 210 | } 211 | 212 | socklen_t len = sizeof(serv_addr); 213 | if (getsockname(sockfd, (struct sockaddr *) &serv_addr, &len) < 0) { 214 | [[RNSSException name:@"Error getting socket name"] reject:reject]; 215 | return; 216 | } 217 | int port = ntohs(serv_addr.sin_port); 218 | 219 | close(sockfd); 220 | resolve(@(port)); 221 | } 222 | @catch (NSException *e) { 223 | [[RNSSException from:e] reject:reject]; 224 | } 225 | } 226 | 227 | - (void) startObserving { 228 | // NOOP: Triggered when the first listener from JS side is added. 229 | } 230 | 231 | - (void) stopObserving { 232 | // NOOP: Triggered when the last listener from JS side is removed. 233 | } 234 | 235 | + (BOOL)requiresMainQueueSetup 236 | { 237 | return NO; 238 | } 239 | 240 | - (std::shared_ptr)getTurboModule: 241 | (const facebook::react::ObjCTurboModule::InitParams &)params 242 | { 243 | return std::make_shared(params); 244 | } 245 | 246 | @end 247 | -------------------------------------------------------------------------------- /ios/Server.h: -------------------------------------------------------------------------------- 1 | static NSString * const CRASHED = @"CRASHED"; 2 | static NSString * const LAUNCHED = @"LAUNCHED"; 3 | static NSString * const TERMINATED = @"TERMINATED"; 4 | 5 | typedef void (^SignalConsumer)(NSString * const signal, NSString * const details); 6 | 7 | @interface Server : NSThread 8 | - (void) cancel; 9 | - (void) main; 10 | 11 | + (Server*) serverWithId:(NSNumber*)serverId 12 | configPath:(NSString*)configPath 13 | errlogPath:(NSString*)errlogPath 14 | signalConsumer:(SignalConsumer)signalConsumer; 15 | 16 | @property (readonly) NSNumber *serverId; 17 | @property SignalConsumer signalConsumer; 18 | @end 19 | -------------------------------------------------------------------------------- /ios/Server.mm: -------------------------------------------------------------------------------- 1 | #import "Server.h" 2 | 3 | Server *activeServer; 4 | 5 | void onLaunchedCallback() { 6 | activeServer.signalConsumer(LAUNCHED, nil); 7 | } 8 | 9 | extern "C" { 10 | int lighttpd_launch( 11 | const char * config_path, 12 | const char * module_path, 13 | const char * errlog_path, 14 | void (*cb)() 15 | ); 16 | 17 | void lighttpd_graceful_shutdown(); 18 | } 19 | 20 | @implementation Server { 21 | NSString *configPath; 22 | NSString *errlogPath; 23 | } 24 | 25 | - (id) initWithServerId:(NSNumber*)serverId 26 | configPath:(NSString*)configPath 27 | errlogPath:(NSString*)errlogPath 28 | signalConsumer:(SignalConsumer)signalConsumer 29 | { 30 | self = [super init]; 31 | self->_serverId = serverId; 32 | self->configPath = configPath; 33 | self->errlogPath = errlogPath; 34 | self.signalConsumer = signalConsumer; 35 | return self; 36 | } 37 | 38 | - (void) cancel { 39 | NSLog(@"Server.cancel() triggered"); 40 | lighttpd_graceful_shutdown(); 41 | [super cancel]; 42 | } 43 | 44 | - (void) main { 45 | NSLog(@"Server.main() triggered"); 46 | 47 | if (activeServer) { 48 | NSString *msg = @"Another Server instance is active"; 49 | NSLog(@"%@", msg); 50 | self.signalConsumer(CRASHED, msg); 51 | return; 52 | } 53 | 54 | @try { 55 | activeServer = self; 56 | int res = lighttpd_launch( 57 | [self->configPath cStringUsingEncoding:NSASCIIStringEncoding], 58 | nil, 59 | [self->errlogPath cStringUsingEncoding:NSASCIIStringEncoding], 60 | onLaunchedCallback 61 | ); 62 | if (res) [NSException raise:@"Server exited with error" format:@"%d", res]; 63 | 64 | activeServer = NULL; 65 | 66 | NSLog(@"Server terminated gracefully"); 67 | self.signalConsumer(TERMINATED, nil); 68 | } 69 | @catch (NSException *error) { 70 | activeServer = NULL; 71 | NSLog(@"Server crashed %@", error.name); 72 | self.signalConsumer(CRASHED, error.name); 73 | } 74 | } 75 | 76 | + (Server*) serverWithId:(NSNumber*)serverId 77 | configPath:(NSString*)configPath 78 | errlogPath:(NSString*)errlogPath 79 | signalConsumer:(SignalConsumer)signalConsumer 80 | { 81 | return [[Server alloc] 82 | initWithServerId:serverId 83 | configPath:configPath 84 | errlogPath:errlogPath 85 | signalConsumer:signalConsumer]; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dr.pogodin/react-native-static-server", 3 | "version": "0.21.0", 4 | "description": "Embedded HTTP server for React Native", 5 | "source": "./src/index.tsx", 6 | "main": "./lib/module/index.js", 7 | "types": "./lib/typescript/src/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "types": "./lib/typescript/src/index.d.ts", 11 | "default": "./lib/module/index.js" 12 | }, 13 | "./package.json": "./package.json" 14 | }, 15 | "scripts": { 16 | "codegen-windows": "./node_modules/.bin/rnc-cli codegen-windows", 17 | "example": "yarn workspace @dr.pogodin/react-native-static-server-example", 18 | "test": "yarn lint && yarn typecheck", 19 | "typecheck": "tsc", 20 | "lint": "eslint \"**/*.{js,ts,tsx}\"", 21 | "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", 22 | "prepare": "bob build" 23 | }, 24 | "keywords": [ 25 | "react-native", 26 | "ios", 27 | "android", 28 | "lighttpd", 29 | "windows", 30 | "embed", 31 | "server", 32 | "http", 33 | "https", 34 | "react" 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/birdofpreyru/react-native-static-server.git" 39 | }, 40 | "author": "Dr. Sergey Pogodin (https://dr.pogodin.studio)", 41 | "license": "MIT", 42 | "licenseFilename": "LICENSE.md", 43 | "readmeFilename": "README.md", 44 | "bugs": { 45 | "url": "https://github.com/birdofpreyru/react-native-static-server/issues" 46 | }, 47 | "homepage": "https://dr.pogodin.studio/docs/react-native-static-server", 48 | "title": "React Native Static Server", 49 | "funding": { 50 | "type": "github", 51 | "url": "https://github.com/sponsors/birdofpreyru" 52 | }, 53 | "publishConfig": { 54 | "registry": "https://registry.npmjs.org/" 55 | }, 56 | "devDependencies": { 57 | "@dr.pogodin/react-native-fs": "^2.33.1", 58 | "@eslint/compat": "^1.2.8", 59 | "@eslint/eslintrc": "^3.3.1", 60 | "@eslint/js": "^9.25.1", 61 | "@react-native-community/cli": "18.0.0", 62 | "@react-native/eslint-config": "^0.79.1", 63 | "@types/jest": "^29.5.14", 64 | "@types/react": "^19.0.12", 65 | "del-cli": "^6.0.0", 66 | "eslint": "^9.25.1", 67 | "jest": "^29.7.0", 68 | "react": "19.0.0", 69 | "react-native": "0.79.1", 70 | "react-native-builder-bob": "^0.40.6", 71 | "react-native-windows": "^0.78.5", 72 | "typescript": "^5.8.3" 73 | }, 74 | "peerDependencies": { 75 | "@dr.pogodin/react-native-fs": ">= 2.22.0", 76 | "react": "*", 77 | "react-native": "*", 78 | "react-native-windows": "*" 79 | }, 80 | "workspaces": [ 81 | "example" 82 | ], 83 | "packageManager": "yarn@4.9.1", 84 | "jest": { 85 | "preset": "react-native", 86 | "modulePathIgnorePatterns": [ 87 | "/example/node_modules", 88 | "/lib/" 89 | ] 90 | }, 91 | "react-native-builder-bob": { 92 | "source": "src", 93 | "output": "lib", 94 | "targets": [ 95 | "codegen", 96 | [ 97 | "module", 98 | { 99 | "esm": true 100 | } 101 | ], 102 | [ 103 | "typescript", 104 | { 105 | "project": "tsconfig.build.json" 106 | } 107 | ] 108 | ] 109 | }, 110 | "codegenConfig": { 111 | "name": "RNReactNativeStaticServerSpec", 112 | "type": "modules", 113 | "jsSrcsDir": "src", 114 | "windows": { 115 | "namespace": "winrt::ReactNativeStaticServer", 116 | "outputDirectory": "windows/ReactNativeStaticServer/codegen" 117 | }, 118 | "outputDir": { 119 | "ios": "ios/generated", 120 | "android": "android/generated" 121 | }, 122 | "android": { 123 | "javaPackageName": "com.drpogodin.reactnativestaticserver" 124 | }, 125 | "includesGeneratedCode": true 126 | }, 127 | "dependencies": { 128 | "@dr.pogodin/js-utils": "^0.0.17" 129 | }, 130 | "create-react-native-library": { 131 | "type": "turbo-module", 132 | "languages": "kotlin-objc", 133 | "version": "0.49.8" 134 | }, 135 | "react-native-windows": { 136 | "init-windows": { 137 | "name": "ReactNativeStaticServer", 138 | "namespace": "ReactNativeStaticServer", 139 | "template": "old/uwp-cpp-lib" 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('@react-native-community/cli-types').UserDependencyConfig} 3 | */ 4 | module.exports = { 5 | dependency: { 6 | platforms: { 7 | android: { 8 | cmakeListsPath: 'generated/jni/CMakeLists.txt', 9 | }, 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/NativeReactNativeStaticServer.ts: -------------------------------------------------------------------------------- 1 | import type { TurboModule } from 'react-native'; 2 | import { TurboModuleRegistry } from 'react-native'; 3 | 4 | export interface Spec extends TurboModule { 5 | readonly getConstants: () => { 6 | CRASHED: string; 7 | IS_MAC_CATALYST: boolean; 8 | LAUNCHED: string; 9 | TERMINATED: string; 10 | }; 11 | 12 | addListener(eventName: string): void; 13 | 14 | getActiveServerId(): Promise; 15 | 16 | removeListeners(count: number): void; 17 | 18 | start(id: number, configPath: string, errlogPath: string): Promise; 19 | 20 | // TODO: Instead of implementing these methods in native code ourselves, 21 | // we probably can use `@react-native-community/netinfo` library to retrieve 22 | // local IP address and a random open port (thus a bit less native code 23 | // to maintain ourselves in this library). 24 | getLocalIpAddress(): Promise; 25 | 26 | getOpenPort(address: string): Promise; 27 | stop(): Promise; 28 | } 29 | 30 | export default TurboModuleRegistry.getEnforcing('ReactNativeStaticServer'); 31 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | // Encapsulates the standard Lighttpd configuration for the library. 2 | 3 | import { 4 | mkdir, 5 | TemporaryDirectoryPath, 6 | writeFile, 7 | } from '@dr.pogodin/react-native-fs'; 8 | 9 | /** 10 | * Filesystem location where the library will keep its working files (configs, 11 | * logs, uploads) for the app. 12 | */ 13 | export const WORK_DIR = `${TemporaryDirectoryPath}/__rn-static-server__`; 14 | 15 | /** 16 | * Filesystem location for the error log file. 17 | */ 18 | export const ERROR_LOG_FILE = `${WORK_DIR}/errorlog.txt`; 19 | 20 | /** 21 | * Filesystem locations where the library will keep uploads for the app. 22 | */ 23 | export const UPLOADS_DIR = `${WORK_DIR}/uploads`; 24 | 25 | /** 26 | * Options for error log, they mirror debug options of Lighttpd config: 27 | * https://redmine.lighttpd.net/projects/lighttpd/wiki/DebugVariables 28 | */ 29 | export type ErrorLogOptions = { 30 | conditionHandling?: boolean; 31 | fileNotFound?: boolean; 32 | requestHandling?: boolean; 33 | requestHeader?: boolean; 34 | requestHeaderOnError?: boolean; 35 | responseHeader?: boolean; 36 | 37 | // "debug.log-ssl-noise" is recognized by Lighttpd only when the Lighttpd TLS 38 | // module is loaded, otherwise it is reported as an unknown config key. As we 39 | // don't use TLS module currently, let's remove this option, at least for now. 40 | // See: https://github.com/birdofpreyru/react-native-static-server/issues/59#issuecomment-1646752111 41 | // sslNoise?: boolean; 42 | 43 | timeouts?: boolean; 44 | }; 45 | 46 | /** 47 | * Options for the standard Lighttpd configuration for the library. 48 | */ 49 | export type StandardConfigOptions = { 50 | errorLog?: ErrorLogOptions; 51 | extraConfig: string; 52 | fileDir: string; 53 | hostname: string; 54 | port: number; 55 | webdav?: string[]; // DEPRECATED 56 | }; 57 | 58 | /** 59 | * Generates a fragment of Lighttpd config related to error and debug logging. 60 | * @param errorLogOptions 61 | * @returns 62 | */ 63 | function errorLogConfig(errorLogOptions?: ErrorLogOptions): string { 64 | const res = []; 65 | 66 | if (errorLogOptions) { 67 | const ops: ErrorLogOptions = errorLogOptions; 68 | const enable = (op: string) => { 69 | res.push(`debug.log-${op} = "enable"`); 70 | }; 71 | if (ops.conditionHandling) enable('condition-handling'); 72 | if (ops.fileNotFound) enable('file-not-found'); 73 | if (ops.requestHandling) enable('request-handling'); 74 | if (ops.requestHeader) enable('request-header'); 75 | if (ops.requestHeaderOnError) enable('request-header-on-error'); 76 | if (ops.responseHeader) enable('response-header'); 77 | 78 | // Not a valid option, without TLS module (see more details in a comment 79 | // earlier in the file). 80 | // if (ops.sslNoise) enable('ssl-noise'); 81 | 82 | if (ops.timeouts) enable('timeouts'); 83 | } else res.push('server.errorlog-use-syslog = "enable"'); 84 | 85 | return res.join('\n'); 86 | } 87 | 88 | /** 89 | * Generates the standard Lighttpd config. 90 | * @param param0 91 | * @returns 92 | */ 93 | function standardConfig({ 94 | errorLog, 95 | extraConfig, 96 | fileDir, 97 | hostname, 98 | port, 99 | webdav, // DEPRECATED 100 | }: StandardConfigOptions) { 101 | let webdavConfig = ''; 102 | if (webdav) { 103 | webdavConfig += 'server.modules += ("mod_webdav")'; 104 | for (let i = 0; i < webdav.length; ++i) { 105 | webdavConfig += `$HTTP["url"] =~ "${webdav[i]}" { webdav.activate = "enable" }`; 106 | } 107 | } 108 | 109 | return `server.document-root = "${fileDir}" 110 | server.bind = "${hostname}" 111 | server.upload-dirs = ( "${UPLOADS_DIR}" ) 112 | server.port = ${port} 113 | ${errorLogConfig(errorLog)} 114 | index-file.names += ("index.xhtml", "index.html", "index.htm", "default.htm", "index.php") 115 | 116 | ${webdavConfig} 117 | ${extraConfig}`; 118 | } 119 | 120 | /** 121 | * Creates a new file with the standard Lighttpd configuration in the WORK_DIR, 122 | * and returns resolves to its path. 123 | * @param fileDir 124 | * @param hostname 125 | * @param port 126 | * @return {Promise} Resolves to the name of the created config file. 127 | */ 128 | export async function newStandardConfigFile( 129 | options: StandardConfigOptions, 130 | ): Promise { 131 | // NOTE: Lighttpd exits with error right away if the specified uploads 132 | // directory does not exist. 133 | await mkdir(UPLOADS_DIR); 134 | 135 | const configFile = `${WORK_DIR}/config-${Date.now()}.txt`; 136 | await writeFile(configFile, standardConfig(options), 'utf8'); 137 | return configFile; 138 | } 139 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // Imports internal constants defined within the native layer, 2 | // and exports user-facing constants for server states. 3 | 4 | import ReactNativeStaticServer from './NativeReactNativeStaticServer'; 5 | 6 | const CONSTANTS = ReactNativeStaticServer.getConstants(); 7 | 8 | export const IS_MAC_CATALYST = CONSTANTS.IS_MAC_CATALYST; 9 | 10 | export const SIGNALS = { 11 | CRASHED: CONSTANTS.CRASHED, 12 | LAUNCHED: CONSTANTS.LAUNCHED, 13 | TERMINATED: CONSTANTS.TERMINATED, 14 | }; 15 | 16 | export enum STATES { 17 | ACTIVE, 18 | CRASHED, 19 | INACTIVE, 20 | STARTING, 21 | STOPPING, 22 | } 23 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | import { 4 | DocumentDirectoryPath, 5 | MainBundlePath, 6 | } from '@dr.pogodin/react-native-fs'; 7 | 8 | import { IS_MAC_CATALYST } from './constants'; 9 | 10 | type PLATFORM = 'ANDROID' | 'IOS' | 'MACOS' | 'WINDOWS'; 11 | 12 | function getPlatform(): PLATFORM { 13 | switch (Platform.OS) { 14 | case 'android': 15 | return 'ANDROID'; 16 | case 'ios': 17 | return IS_MAC_CATALYST ? 'MACOS' : 'IOS'; 18 | case 'windows': 19 | return 'WINDOWS'; 20 | default: 21 | throw Error(`Unsupported platform ${Platform.OS}`); 22 | } 23 | } 24 | 25 | const PLATFORM: PLATFORM = getPlatform(); 26 | 27 | const BASE_ASSET_DIRS: { [key in typeof PLATFORM]: string } = { 28 | ANDROID: DocumentDirectoryPath, 29 | IOS: MainBundlePath || '', 30 | MACOS: `${MainBundlePath}/Contents/Resources`, 31 | WINDOWS: MainBundlePath || '', 32 | }; 33 | 34 | const BASE_ASSET_DIR = BASE_ASSET_DIRS[PLATFORM]; 35 | 36 | const SEP = PLATFORM === 'WINDOWS' ? '\\' : '/'; 37 | 38 | /** 39 | * Returns `true` if given path is absolute, `false` otherwise. 40 | * @param {string} path 41 | * @return {boolean} 42 | */ 43 | function isAbsolutePath(path: string): boolean { 44 | if (!path) return false; 45 | 46 | if (Platform.OS === 'windows') { 47 | return !!path.match(/^[a-zA-Z]:\\/); 48 | } 49 | 50 | // This should do for Android and iOS. 51 | return path.startsWith('/') || path.startsWith('file:///'); 52 | } 53 | 54 | /** 55 | * If given `path` is relative, it returns the corresponding absolute path, 56 | * resolved relative to the platform-specific base location for bundled assets; 57 | * otherwise, it just returns given absolute path as is. 58 | * @param path Absolute or relative path. 59 | * @return Absolute path. 60 | */ 61 | export function resolveAssetsPath(path: string): string { 62 | return isAbsolutePath(path) ? path : `${BASE_ASSET_DIR}${SEP}${path}`; 63 | } 64 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "exclude": ["example", "lib"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "paths": { 5 | "@dr.pogodin/react-native-static-server": ["./src/index"] 6 | }, 7 | "allowUnreachableCode": false, 8 | "allowUnusedLabels": false, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "jsx": "react-jsx", 12 | "lib": ["ESNext"], 13 | "module": "ESNext", 14 | "moduleResolution": "bundler", 15 | "noEmit": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitReturns": true, 18 | "noImplicitUseStrict": false, 19 | "noStrictGenericChecks": false, 20 | "noUncheckedIndexedAccess": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true, 26 | "target": "ESNext", 27 | "verbatimModuleSyntax": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | *AppPackages* 2 | *BundleArtifacts* 3 | 4 | #OS junk files 5 | [Tt]humbs.db 6 | *.DS_Store 7 | 8 | #Visual Studio files 9 | *.[Oo]bj 10 | *.user 11 | *.aps 12 | *.pch 13 | *.vspscc 14 | *.vssscc 15 | *_i.c 16 | *_p.c 17 | *.ncb 18 | *.suo 19 | *.tlb 20 | *.tlh 21 | *.bak 22 | *.[Cc]ache 23 | *.ilk 24 | *.log 25 | *.lib 26 | *.sbr 27 | *.sdf 28 | *.opensdf 29 | *.opendb 30 | *.unsuccessfulbuild 31 | ipch/ 32 | [Oo]bj/ 33 | [Bb]in 34 | [Dd]ebug*/ 35 | [Rr]elease*/ 36 | Ankh.NoLoad 37 | 38 | # Visual C++ cache files 39 | ipch/ 40 | *.aps 41 | *.ncb 42 | *.opendb 43 | *.opensdf 44 | *.sdf 45 | *.cachefile 46 | *.VC.db 47 | *.VC.VC.opendb 48 | 49 | #MonoDevelop 50 | *.pidb 51 | *.userprefs 52 | 53 | #Tooling 54 | _ReSharper*/ 55 | *.resharper 56 | [Tt]est[Rr]esult* 57 | *.sass-cache 58 | 59 | #Project files 60 | [Bb]uild/ 61 | 62 | #Subversion files 63 | .svn 64 | 65 | # Office Temp Files 66 | ~$* 67 | 68 | # vim Temp Files 69 | *~ 70 | 71 | #NuGet 72 | packages/ 73 | *.nupkg 74 | 75 | #ncrunch 76 | *ncrunch* 77 | *crunch*.local.xml 78 | 79 | # visual studio database projects 80 | *.dbmdl 81 | 82 | #Test files 83 | *.testsettings 84 | 85 | #Other files 86 | *.DotSettings 87 | .vs/ 88 | *project.lock.json 89 | 90 | #Files generated by the VS build 91 | **/Generated Files/** 92 | 93 | -------------------------------------------------------------------------------- /windows/ExperimentalFeatures.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 17 | true 18 | 19 | 26 | false 27 | 28 | true 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /windows/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/Errors.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "Errors.h" 4 | 5 | RNException::RNException(std::string && message) { 6 | this->Message = std::move(message); 7 | } 8 | 9 | const char* RNException::what() { 10 | return this->Message.c_str(); 11 | } 12 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/Errors.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "NativeModules.h" 5 | 6 | using namespace winrt::Microsoft::ReactNative; 7 | 8 | class RNException : public std::exception, public ReactError { 9 | public: 10 | // TODO: ReactError also has Code (string) and UserInfo (JSValueObject) 11 | // fields, we may accept here, and use. For now, just getting message is ok. 12 | RNException(std::string && message); 13 | 14 | virtual const char* what(); 15 | 16 | template 17 | void reject(ReactPromise& promise); 18 | }; 19 | 20 | template 21 | void RNException::reject(ReactPromise& promise) { 22 | promise.Reject(*this); 23 | } 24 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/PropertySheet.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/ReactNativeModule.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ReactNativeModule.h" 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "Errors.h" 9 | #include "Server.h" 10 | 11 | using namespace std::chrono_literals; 12 | using namespace winrt::ReactNativeStaticServer; 13 | using namespace winrt::Windows::Networking::Connectivity; 14 | 15 | ReactNativeModule* mod; 16 | React::ReactPromise* pendingResult; 17 | Server *server; 18 | 19 | // There is no semaphore in C++ STL prior to C++20, 20 | // thus we have to make it ourselves. 21 | boolean sem = true; 22 | std::mutex sem_guard; 23 | std::condition_variable sem_cv; 24 | 25 | void lock_sem() { 26 | std::unique_lock lk(sem_guard); 27 | sem_cv.wait(lk, [] { return sem; }); 28 | sem = false; 29 | } 30 | 31 | void unlock_sem() { 32 | std::lock_guard lk(sem_guard); 33 | if (sem) throw std::exception("Internal synchronization error"); 34 | sem = true; 35 | sem_cv.notify_one(); 36 | } 37 | 38 | void OnSignal(std::string signal, std::string details) { 39 | // BEWARE: .sendEvent() depends on the server object to get its ID, 40 | // thus MUST BE called before the object is dropped below, if it is. 41 | if (!pendingResult) mod->sendEvent(signal, details); 42 | 43 | double id = server->id(); 44 | if (signal == CRASHED || signal == TERMINATED) { 45 | delete server; 46 | server = NULL; 47 | } 48 | 49 | if (pendingResult) { 50 | if (signal == CRASHED) { 51 | RNException("Server #" + std::to_string(id) + " crashed").reject(*pendingResult); 52 | } 53 | else pendingResult->Resolve(details); 54 | delete pendingResult; 55 | pendingResult = NULL; 56 | unlock_sem(); 57 | } 58 | } 59 | 60 | ReactNativeStaticServerSpec_Constants ReactNativeModule::GetConstants() noexcept { 61 | ReactNativeStaticServerSpec_Constants res; 62 | res.CRASHED = CRASHED; 63 | res.IS_MAC_CATALYST = false; 64 | res.LAUNCHED = LAUNCHED; 65 | res.TERMINATED = TERMINATED; 66 | return res; 67 | } 68 | 69 | void ReactNativeModule::getActiveServerId(React::ReactPromise>&& result) noexcept { 70 | result.Resolve(server ? std::optional(server->id()) : std::nullopt); 71 | } 72 | 73 | void ReactNativeModule::getLocalIpAddress(React::ReactPromise&& result) noexcept { 74 | try { 75 | auto hosts = NetworkInformation::GetHostNames(); 76 | for (winrt::Windows::Networking::HostName host: hosts) { 77 | if (host.Type() == winrt::Windows::Networking::HostNameType::Ipv4) { 78 | auto network = host.IPInformation().NetworkAdapter(); 79 | int32_t netType = network.IanaInterfaceType(); 80 | // TODO: This needs second thoughts, and a lot of testing. 81 | // The current values 6 & 72 mean "either Ethernet network, 82 | // or IEEE 802.11 wireless network interface", see: 83 | // https://learn.microsoft.com/en-us/uwp/api/windows.networking.connectivity.networkadapter.ianainterfacetype?view=winrt-22621#property-value 84 | // For now we just pick up the first IP for such connected 85 | // network, but we probably should give library consumer 86 | // control over what network is selected, and stuff (there 87 | // are related tickets for other os). 88 | if (netType == 6 || netType == 71) { 89 | // TODO: Here we can use network.GetConnectedProfileAsync() 90 | // to get more info about the current connection status, 91 | // but for now just let return the first IP we found. 92 | return result.Resolve(winrt::to_string(host.CanonicalName())); 93 | } 94 | } 95 | } 96 | } 97 | catch (...) { 98 | // NOOP 99 | } 100 | RNException("Failed to get a non-local IP address").reject(result); 101 | } 102 | 103 | void ReactNativeModule::getOpenPort( 104 | std::string address, 105 | React::ReactPromise&& result 106 | ) noexcept { 107 | try { 108 | auto socket = winrt::Windows::Networking::Sockets::StreamSocketListener(); 109 | // TODO: This will fail if nor InternetClientServer neither PrivateNetworkClientServer 110 | // capability is granted to the app. The error messaging should be improved, to make it 111 | // clear to the library consumer why the failure happened. 112 | winrt::Windows::Networking::HostName hostname(winrt::to_hstring(address)); 113 | if (socket.BindEndpointAsync(hostname, L"").wait_for(5s) != AsyncStatus::Completed) { 114 | return RNException("Binding time out").reject(result); 115 | } 116 | double port = std::stod(winrt::to_string(socket.Information().LocalPort())); 117 | socket.Close(); 118 | return result.Resolve(port); 119 | } 120 | catch (...) { 121 | // NOOP 122 | } 123 | RNException("Failed to get an open port").reject(result); 124 | } 125 | 126 | void ReactNativeModule::sendEvent(std::string signal, std::string details) { 127 | JSValueObject obj = JSValueObject{ 128 | {"serverId", server->id()}, 129 | {"event", signal}, 130 | {"details", details} 131 | }; 132 | this->EmitEvent(std::move(obj)); 133 | } 134 | 135 | void ReactNativeModule::start( 136 | double id, 137 | std::string configPath, 138 | std::string errlogPath, 139 | React::ReactPromise&& result 140 | ) noexcept { 141 | lock_sem(); 142 | 143 | if (server) { 144 | RNException( 145 | "Failed to launch server #" + std::to_string(id) + 146 | ", another server instance (#" + server->id_str() + ") is active").reject(result); 147 | unlock_sem(); 148 | return; 149 | }; 150 | 151 | if (pendingResult) { 152 | RNException("Internal error").reject(result); 153 | unlock_sem(); 154 | return; 155 | } 156 | 157 | mod = this; 158 | pendingResult = new React::ReactPromise(result); 159 | server = new Server(id, configPath, errlogPath, OnSignal); 160 | server->launch(); 161 | } 162 | 163 | void ReactNativeModule::stop(React::ReactPromise&& result) noexcept { 164 | try { 165 | lock_sem(); 166 | 167 | // The synchronization in JS layer is supposed to ensure this native 168 | // .stop() is never called before any previous pendingResult is settled 169 | // and cleaned up. 170 | if (pendingResult) { 171 | unlock_sem(); 172 | RNException("Internal error").reject(result); 173 | return; 174 | } 175 | 176 | // This means either the server has crashed at the same time we were 177 | // about to ask it to gracefully shutdown, or there is some error in 178 | // JS layer, which is not supposed to call this native .stop() unless 179 | // an active server instance exists. 180 | if (!server) { 181 | RNException("No active server").reject(result); 182 | unlock_sem(); 183 | return; 184 | } 185 | 186 | pendingResult = new React::ReactPromise(result); 187 | server->shutdown(); 188 | 189 | // The OnSignal() handler will dispose the server once TERMINATED, 190 | // or CRASHED signal is received, and it will settle and clean up 191 | // pendingPromise; anything else going wrong, the try/catch block 192 | // will catch it and report to JS layer in RN way. 193 | } 194 | catch (...) { 195 | RNException("Failed to gracefully shutdown the server #" + server->id_str()).reject(result); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/ReactNativeModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "codegen/NativeReactNativeStaticServerSpec.g.h" 4 | 5 | #include "JSValue.h" 6 | #include "NativeModules.h" 7 | 8 | using namespace winrt::Microsoft::ReactNative; 9 | 10 | namespace winrt::ReactNativeStaticServer 11 | { 12 | 13 | REACT_MODULE(ReactNativeModule, L"ReactNativeStaticServer") 14 | struct ReactNativeModule 15 | { 16 | using ModuleSpec = ReactNativeStaticServerSpec; 17 | 18 | REACT_GET_CONSTANTS(GetConstants) 19 | ReactNativeStaticServerSpec_Constants GetConstants() noexcept; 20 | 21 | REACT_METHOD(addListener) 22 | void addListener(std::string eventName) noexcept { 23 | // NOOP 24 | } 25 | 26 | REACT_METHOD(removeListeners) 27 | void removeListeners(double count) noexcept { 28 | // NOOP 29 | } 30 | 31 | REACT_EVENT(EmitEvent, L"RNStaticServer"); 32 | std::function EmitEvent; 33 | 34 | void sendEvent(std::string signal, std::string details); 35 | 36 | REACT_METHOD(getActiveServerId) 37 | void getActiveServerId(React::ReactPromise>&& result) noexcept; 38 | 39 | REACT_METHOD(getLocalIpAddress) 40 | void getLocalIpAddress(React::ReactPromise&& result) noexcept; 41 | 42 | REACT_METHOD(getOpenPort) 43 | void getOpenPort(std::string address, React::ReactPromise&& result) noexcept; 44 | 45 | REACT_METHOD(start) 46 | void start(double id, 47 | std::string configPath, 48 | std::string errlogPath, 49 | React::ReactPromise&& result) noexcept; 50 | 51 | REACT_METHOD(stop) 52 | void stop(React::ReactPromise&& result) noexcept; 53 | }; 54 | 55 | } // namespace winrt::ReactNativeStaticServer 56 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/ReactNativeStaticServer.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE 3 | DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE 4 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/ReactNativeStaticServer.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {ac89e086-a7ef-46ed-8566-8e8bbcc1eab4} 28 | 29 | 30 | 31 | 32 | Lighttpd 33 | 34 | 35 | Lighttpd 36 | 37 | 38 | Lighttpd 39 | 40 | 41 | Lighttpd 42 | 43 | 44 | Lighttpd 45 | 46 | 47 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/ReactPackageProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ReactPackageProvider.h" 3 | #if __has_include("ReactPackageProvider.g.cpp") 4 | #include "ReactPackageProvider.g.cpp" 5 | #endif 6 | 7 | #include "ReactNativeModule.h" 8 | 9 | using namespace winrt::Microsoft::ReactNative; 10 | 11 | namespace winrt::ReactNativeStaticServer::implementation 12 | { 13 | 14 | void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept 15 | { 16 | AddAttributedModules(packageBuilder, true); 17 | } 18 | 19 | } // namespace winrt::ReactNativeStaticServer::implementation 20 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/ReactPackageProvider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ReactPackageProvider.g.h" 3 | 4 | using namespace winrt::Microsoft::ReactNative; 5 | 6 | namespace winrt::ReactNativeStaticServer::implementation 7 | { 8 | struct ReactPackageProvider : ReactPackageProviderT 9 | { 10 | ReactPackageProvider() = default; 11 | 12 | void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept; 13 | }; 14 | } // namespace winrt::ReactNativeStaticServer::implementation 15 | 16 | namespace winrt::ReactNativeStaticServer::factory_implementation 17 | { 18 | 19 | struct ReactPackageProvider : ReactPackageProviderT {}; 20 | 21 | } // namespace winrt::ReactNativeStaticServer::factory_implementation 22 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/ReactPackageProvider.idl: -------------------------------------------------------------------------------- 1 | namespace ReactNativeStaticServer 2 | { 3 | [webhosthidden] 4 | [default_interface] 5 | runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider 6 | { 7 | ReactPackageProvider(); 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/Server.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Server.h" 3 | #include 4 | 5 | using namespace winrt::ReactNativeStaticServer; 6 | using namespace winrt::Windows::ApplicationModel; 7 | using namespace winrt::Windows::Storage; 8 | 9 | typedef void (*CallbackT)(); 10 | 11 | typedef int (*LighttpdLaunchT)( 12 | const char *configPath, 13 | const char *modulesPath, 14 | const char *errlogPath, 15 | CallbackT 16 | ); 17 | 18 | typedef void (*LighttpdShutdownT)(); 19 | 20 | LighttpdLaunchT LighttpdLaunch; 21 | LighttpdShutdownT LighttpdShutdown; 22 | 23 | Server* Server::activeServer; 24 | 25 | void LoadLighttpdDll() { 26 | LoadPackagedLibrary(L"ReactNativeStaticServer\\libpcre2-8.dll", 0); 27 | LoadPackagedLibrary(L"ReactNativeStaticServer\\libwinpthread-1.dll", 0); 28 | HMODULE dll = LoadPackagedLibrary(L"ReactNativeStaticServer\\lighttpd.dll", 0); 29 | if (dll) { 30 | LighttpdLaunch = (LighttpdLaunchT)GetProcAddress(dll, "lighttpd_launch"); 31 | LighttpdShutdown = (LighttpdShutdownT)GetProcAddress(dll, "lighttpd_graceful_shutdown"); 32 | } 33 | else { 34 | // The DLLs we tried to import above are pre-build and bundled into 35 | // an UWP app by this library, so failure to locate and load them is 36 | // a fatal crash, meaning there is something wrong with our library, 37 | // and we terminate the app in such case. 38 | // 39 | // NOTE: The next few lines retrieve the failure cause, but don't forward it anywhere, 40 | // for now they can be seen in debugger. 41 | DWORD errcode = GetLastError(); 42 | LPSTR errmsg = nullptr; 43 | FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 44 | NULL, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)& errmsg, 0, NULL); 45 | 46 | terminate(); 47 | } 48 | } 49 | 50 | void Server::OnLaunchedCallback() { 51 | Server::activeServer->_signalConsumer(LAUNCHED, ""); 52 | } 53 | 54 | Server::Server( 55 | double id, 56 | std::string configPath, 57 | std::string errlogPath, 58 | SignalConsumer signalConsumer 59 | ): 60 | _id(id), 61 | _configPath(configPath), 62 | _errlogPath(errlogPath), 63 | _signalConsumer(signalConsumer) 64 | { 65 | if (!LighttpdLaunch) LoadLighttpdDll(); 66 | } 67 | 68 | void Server::launch() { 69 | concurrency::task task = concurrency::create_task( 70 | [this] { 71 | if (Server::activeServer) { 72 | // Bail out with error if another server instance is running. 73 | this->_signalConsumer(CRASHED, "Another Server instance is active"); 74 | return; 75 | } 76 | winrt::hstring appPath = Package::Current().InstalledLocation().Path(); 77 | std::string modulesPath(appPath.begin(), appPath.end()); 78 | modulesPath += "\\ReactNativeStaticServer"; 79 | try { 80 | Server::activeServer = this; 81 | int res = LighttpdLaunch( 82 | this->_configPath.c_str(), 83 | modulesPath.c_str(), 84 | this->_errlogPath.c_str(), 85 | Server::OnLaunchedCallback 86 | ); 87 | if (res) { 88 | throw new std::exception("Ligttpd exited with status " + res); 89 | } 90 | Server::activeServer = NULL; 91 | this->_signalConsumer(TERMINATED, ""); 92 | } 93 | catch (...) { 94 | Server::activeServer = NULL; 95 | this->_signalConsumer(CRASHED, ""); 96 | } 97 | } 98 | ); 99 | } 100 | 101 | void Server::shutdown() { 102 | LighttpdShutdown(); 103 | } 104 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/Server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace winrt::ReactNativeStaticServer { 6 | 7 | static const std::string CRASHED = "CRASHED"; 8 | static const std::string LAUNCHED = "LAUNCHED"; 9 | static const std::string TERMINATED = "TERMINATED"; 10 | 11 | typedef void (*SignalConsumer)(std::string signal, std::string details); 12 | 13 | class Server { 14 | public: 15 | Server( 16 | double id, 17 | std::string configPath, 18 | std::string errlogPath, 19 | SignalConsumer signalConsumer); 20 | 21 | inline double id() { return _id; } 22 | inline std::string id_str() { return std::to_string(_id); } 23 | 24 | void launch(); 25 | void shutdown(); 26 | private: 27 | double _id; 28 | std::string _configPath; 29 | std::string _errlogPath; 30 | SignalConsumer _signalConsumer; 31 | 32 | static Server* activeServer; 33 | 34 | static void OnLaunchedCallback(); 35 | }; 36 | 37 | } // namespace winrt::ReactNativeStaticServer 38 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/codegen/.clang-format: -------------------------------------------------------------------------------- 1 | DisableFormat: true 2 | SortIncludes: false -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/codegen/NativeReactNativeStaticServerSpec.g.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * This file is auto-generated from a NativeModule spec file in js. 4 | * 5 | * This is a C++ Spec class that should be used with MakeTurboModuleProvider to register native modules 6 | * in a way that also verifies at compile time that the native module matches the interface required 7 | * by the TurboModule JS spec. 8 | */ 9 | #pragma once 10 | // clang-format off 11 | 12 | #include 13 | #include 14 | 15 | namespace winrt::ReactNativeStaticServer { 16 | 17 | struct ReactNativeStaticServerSpec_Constants { 18 | std::string CRASHED; 19 | bool IS_MAC_CATALYST; 20 | std::string LAUNCHED; 21 | std::string TERMINATED; 22 | }; 23 | 24 | 25 | inline winrt::Microsoft::ReactNative::FieldMap GetStructInfo(ReactNativeStaticServerSpec_Constants*) noexcept { 26 | winrt::Microsoft::ReactNative::FieldMap fieldMap { 27 | {L"CRASHED", &ReactNativeStaticServerSpec_Constants::CRASHED}, 28 | {L"IS_MAC_CATALYST", &ReactNativeStaticServerSpec_Constants::IS_MAC_CATALYST}, 29 | {L"LAUNCHED", &ReactNativeStaticServerSpec_Constants::LAUNCHED}, 30 | {L"TERMINATED", &ReactNativeStaticServerSpec_Constants::TERMINATED}, 31 | }; 32 | return fieldMap; 33 | } 34 | 35 | struct ReactNativeStaticServerSpec : winrt::Microsoft::ReactNative::TurboModuleSpec { 36 | static constexpr auto constants = std::tuple{ 37 | TypedConstant{0}, 38 | }; 39 | static constexpr auto methods = std::tuple{ 40 | Method{0, L"addListener"}, 41 | Method>) noexcept>{1, L"getActiveServerId"}, 42 | Method{2, L"removeListeners"}, 43 | Method) noexcept>{3, L"start"}, 44 | Method) noexcept>{4, L"getLocalIpAddress"}, 45 | Method) noexcept>{5, L"getOpenPort"}, 46 | Method) noexcept>{6, L"stop"}, 47 | }; 48 | 49 | template 50 | static constexpr void ValidateModule() noexcept { 51 | constexpr auto constantCheckResults = CheckConstants(); 52 | constexpr auto methodCheckResults = CheckMethods(); 53 | 54 | REACT_SHOW_CONSTANT_SPEC_ERRORS( 55 | 0, 56 | "ReactNativeStaticServerSpec_Constants", 57 | " REACT_GET_CONSTANTS(GetConstants) ReactNativeStaticServerSpec_Constants GetConstants() noexcept {/*implementation*/}\n" 58 | " REACT_GET_CONSTANTS(GetConstants) static ReactNativeStaticServerSpec_Constants GetConstants() noexcept {/*implementation*/}\n"); 59 | 60 | REACT_SHOW_METHOD_SPEC_ERRORS( 61 | 0, 62 | "addListener", 63 | " REACT_METHOD(addListener) void addListener(std::string eventName) noexcept { /* implementation */ }\n" 64 | " REACT_METHOD(addListener) static void addListener(std::string eventName) noexcept { /* implementation */ }\n"); 65 | REACT_SHOW_METHOD_SPEC_ERRORS( 66 | 1, 67 | "getActiveServerId", 68 | " REACT_METHOD(getActiveServerId) void getActiveServerId(::React::ReactPromise> &&result) noexcept { /* implementation */ }\n" 69 | " REACT_METHOD(getActiveServerId) static void getActiveServerId(::React::ReactPromise> &&result) noexcept { /* implementation */ }\n"); 70 | REACT_SHOW_METHOD_SPEC_ERRORS( 71 | 2, 72 | "removeListeners", 73 | " REACT_METHOD(removeListeners) void removeListeners(double count) noexcept { /* implementation */ }\n" 74 | " REACT_METHOD(removeListeners) static void removeListeners(double count) noexcept { /* implementation */ }\n"); 75 | REACT_SHOW_METHOD_SPEC_ERRORS( 76 | 3, 77 | "start", 78 | " REACT_METHOD(start) void start(double id, std::string configPath, std::string errlogPath, ::React::ReactPromise &&result) noexcept { /* implementation */ }\n" 79 | " REACT_METHOD(start) static void start(double id, std::string configPath, std::string errlogPath, ::React::ReactPromise &&result) noexcept { /* implementation */ }\n"); 80 | REACT_SHOW_METHOD_SPEC_ERRORS( 81 | 4, 82 | "getLocalIpAddress", 83 | " REACT_METHOD(getLocalIpAddress) void getLocalIpAddress(::React::ReactPromise &&result) noexcept { /* implementation */ }\n" 84 | " REACT_METHOD(getLocalIpAddress) static void getLocalIpAddress(::React::ReactPromise &&result) noexcept { /* implementation */ }\n"); 85 | REACT_SHOW_METHOD_SPEC_ERRORS( 86 | 5, 87 | "getOpenPort", 88 | " REACT_METHOD(getOpenPort) void getOpenPort(std::string address, ::React::ReactPromise &&result) noexcept { /* implementation */ }\n" 89 | " REACT_METHOD(getOpenPort) static void getOpenPort(std::string address, ::React::ReactPromise &&result) noexcept { /* implementation */ }\n"); 90 | REACT_SHOW_METHOD_SPEC_ERRORS( 91 | 6, 92 | "stop", 93 | " REACT_METHOD(stop) void stop(::React::ReactPromise &&result) noexcept { /* implementation */ }\n" 94 | " REACT_METHOD(stop) static void stop(::React::ReactPromise &&result) noexcept { /* implementation */ }\n"); 95 | } 96 | }; 97 | 98 | } // namespace winrt::ReactNativeStaticServer 99 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/lighttpd/lemon.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/windows/ReactNativeStaticServer/lighttpd/lemon.exe -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/lighttpd/libpcre2-8.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/windows/ReactNativeStaticServer/lighttpd/libpcre2-8.dll -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/lighttpd/libwinpthread-1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/windows/ReactNativeStaticServer/lighttpd/libwinpthread-1.dll -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/lighttpd/lighttpd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/windows/ReactNativeStaticServer/lighttpd/lighttpd.dll -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/lighttpd/mod_dirlisting.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/windows/ReactNativeStaticServer/lighttpd/mod_dirlisting.dll -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/lighttpd/mod_h2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/windows/ReactNativeStaticServer/lighttpd/mod_h2.dll -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/lighttpd/mod_webdav.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/birdofpreyru/react-native-static-server/bb30302f6de715d0e951d8fdaf7dd4b1240d09e5/windows/ReactNativeStaticServer/lighttpd/mod_webdav.dll -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "native,Version=v0.0": { 5 | "Microsoft.UI.Xaml": { 6 | "type": "Direct", 7 | "requested": "[2.8.0, )", 8 | "resolved": "2.8.0", 9 | "contentHash": "vxdHxTr63s5KVtNddMFpgvjBjUH50z7seq/5jLWmmSuf8poxg+sXrywkofUdE8ZstbpO9y3FL/IXXUcPYbeesA==", 10 | "dependencies": { 11 | "Microsoft.Web.WebView2": "1.0.1264.42" 12 | } 13 | }, 14 | "Microsoft.Windows.CppWinRT": { 15 | "type": "Direct", 16 | "requested": "[2.0.230706.1, )", 17 | "resolved": "2.0.230706.1", 18 | "contentHash": "l0D7oCw/5X+xIKHqZTi62TtV+1qeSz7KVluNFdrJ9hXsst4ghvqQ/Yhura7JqRdZWBXAuDS0G0KwALptdoxweQ==" 19 | }, 20 | "boost": { 21 | "type": "Transitive", 22 | "resolved": "1.83.0", 23 | "contentHash": "cy53VNMzysEMvhBixDe8ujPk67Fcj3v6FPHQnH91NYJNLHpc6jxa2xq9ruCaaJjE4M3YrGSHDi4uUSTGBWw6EQ==" 24 | }, 25 | "Microsoft.JavaScript.Hermes": { 26 | "type": "Transitive", 27 | "resolved": "0.1.23", 28 | "contentHash": "cA9t1GjY4Yo0JD1AfA//e1lOwk48hLANfuX6GXrikmEBNZVr2TIX5ONJt5tqCnpZyLz6xGiPDgTfFNKbSfb21g==" 29 | }, 30 | "Microsoft.Web.WebView2": { 31 | "type": "Transitive", 32 | "resolved": "1.0.1264.42", 33 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 34 | }, 35 | "common": { 36 | "type": "Project", 37 | "dependencies": { 38 | "boost": "[1.83.0, )" 39 | } 40 | }, 41 | "fmt": { 42 | "type": "Project" 43 | }, 44 | "folly": { 45 | "type": "Project", 46 | "dependencies": { 47 | "boost": "[1.83.0, )", 48 | "fmt": "[1.0.0, )" 49 | } 50 | }, 51 | "microsoft.reactnative": { 52 | "type": "Project", 53 | "dependencies": { 54 | "Common": "[1.0.0, )", 55 | "Folly": "[1.0.0, )", 56 | "Microsoft.JavaScript.Hermes": "[0.1.23, )", 57 | "Microsoft.UI.Xaml": "[2.8.0, )", 58 | "ReactCommon": "[1.0.0, )", 59 | "boost": "[1.83.0, )" 60 | } 61 | }, 62 | "reactcommon": { 63 | "type": "Project", 64 | "dependencies": { 65 | "Folly": "[1.0.0, )", 66 | "boost": "[1.83.0, )" 67 | } 68 | } 69 | }, 70 | "native,Version=v0.0/win10-arm": { 71 | "Microsoft.Web.WebView2": { 72 | "type": "Transitive", 73 | "resolved": "1.0.1264.42", 74 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 75 | } 76 | }, 77 | "native,Version=v0.0/win10-arm-aot": { 78 | "Microsoft.Web.WebView2": { 79 | "type": "Transitive", 80 | "resolved": "1.0.1264.42", 81 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 82 | } 83 | }, 84 | "native,Version=v0.0/win10-arm64-aot": { 85 | "Microsoft.Web.WebView2": { 86 | "type": "Transitive", 87 | "resolved": "1.0.1264.42", 88 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 89 | } 90 | }, 91 | "native,Version=v0.0/win10-x64": { 92 | "Microsoft.Web.WebView2": { 93 | "type": "Transitive", 94 | "resolved": "1.0.1264.42", 95 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 96 | } 97 | }, 98 | "native,Version=v0.0/win10-x64-aot": { 99 | "Microsoft.Web.WebView2": { 100 | "type": "Transitive", 101 | "resolved": "1.0.1264.42", 102 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 103 | } 104 | }, 105 | "native,Version=v0.0/win10-x86": { 106 | "Microsoft.Web.WebView2": { 107 | "type": "Transitive", 108 | "resolved": "1.0.1264.42", 109 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 110 | } 111 | }, 112 | "native,Version=v0.0/win10-x86-aot": { 113 | "Microsoft.Web.WebView2": { 114 | "type": "Transitive", 115 | "resolved": "1.0.1264.42", 116 | "contentHash": "7OBUTkzQ5VI/3gb0ufi5U4zjuCowAJwQg2li0zXXzqkM+S1kmOlivTy1R4jAW+gY5Vyg510M+qMAESCQUjrfgA==" 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /windows/ReactNativeStaticServer/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NOMINMAX 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #if __has_include() 11 | #include 12 | #endif 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | using namespace winrt::Windows::Foundation; 28 | -------------------------------------------------------------------------------- /windows/mingw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # This script pre-builds Lighttpd into a static Windows library. 5 | # To use it: 6 | # - Install MSYS2 (https://www.msys2.org). 7 | # - Run the default MSYS2 UCRT64 console, and execute the following command 8 | # to install necessary dependencies: 9 | # $ pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake \ 10 | # mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-pcre2 11 | # - Check-out the correct code version in the /lighttpd1.4 folder (the master 12 | # branch of Lighttpd repo does not support MinGW build yet, you need some 13 | # commit from the win32-exp branch). 14 | # - Then run this script from within the same MSYS2 UCRT64 console. 15 | 16 | SCRIPT_FOLDER=$(dirname $(realpath $0)) 17 | ROOT_FOLDER=$(dirname $SCRIPT_FOLDER) 18 | BUILD_FOLDER="$SCRIPT_FOLDER/build" 19 | OUTPUT_FOLDER="$SCRIPT_FOLDER/ReactNativeStaticServer/lighttpd" 20 | MSYS2_PATH="/ucrt64" 21 | 22 | cmake $ROOT_FOLDER -B $BUILD_FOLDER -G Ninja \ 23 | -DBUILD_LIBRARY=ON -DCMAKE_BUILD_TYPE=Release 24 | cmake --build $BUILD_FOLDER --target mod_dirlisting mod_h2 mod_webdav lighttpd 25 | 26 | mkdir -p $OUTPUT_FOLDER 27 | cd $BUILD_FOLDER/lighttpd1.4/build 28 | cp *.dll $OUTPUT_FOLDER 29 | 30 | # NOTE: The Lemon binary created here is used by Android builds on Windows. 31 | cp lemon.exe $OUTPUT_FOLDER 32 | 33 | cd $BUILD_FOLDER/sysroot/bin 34 | cp libpcre2-8.dll $OUTPUT_FOLDER 35 | 36 | cd $MSYS2_PATH/bin 37 | cp libwinpthread-1.dll $OUTPUT_FOLDER 38 | --------------------------------------------------------------------------------