Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/demo-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_modules/
ios/Pods/
ios/build/
ios/.xcode.env.local
ios/DevelopmentTeam.xcconfig
*.xcworkspace/xcuserdata/
*.xcodeproj/xcuserdata/

Expand Down
1 change: 1 addition & 0 deletions examples/demo-app/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ gem 'bigdecimal'
gem 'logger'
gem 'benchmark'
gem 'mutex_m'
gem 'nkf' # provides kconv, needed by CFPropertyList on Ruby 3.4+
23 changes: 20 additions & 3 deletions examples/demo-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,24 @@ This example ships both an `ios/` and an `android/` project.
`DEVELOPMENT_TEAM` is intentionally blank in the committed Xcode project. Open
`ios/OfflineDemo.xcodeproj`, select the target, and set your own team under
**Signing & Capabilities** before running on a physical device. The Simulator
needs no team.
needs no team. The repo also ships `ios/DevelopmentTeam.xcconfig.example` as an
optional template for a **local** xcconfig (copy to `ios/DevelopmentTeam.xcconfig`,
gitignored). The Xcode target does **not** include that file; copying it alone
does not configure signing — set your team in **Signing & Capabilities**, or wire
the xcconfig into the project yourself if you prefer that workflow.

## iOS development (simulator vs physical device)

The app uses **UIScene** lifecycle (required on iOS 26+). In **Debug**:

- **Simulator** loads JavaScript from **Metro** (`npm start` in this directory).
Use `npx react-native run-ios` as usual.
- **Physical device** loads the **embedded** `main.jsbundle` that Xcode produces
during the build. Metro is not required to open the app, but **after you change
any JS/TS you must rebuild and reinstall** (run `npx react-native run-ios
--device "Your iPhone"` again) to pick up those changes.

Release builds always use the embedded bundle.

## How to test peer-to-peer

Expand All @@ -65,5 +82,5 @@ emulator have no working Bluetooth stack.
4. Send a connection request, accept it on the other device
5. Open **Chats** and send a message — it is MLS-encrypted automatically

Grant Bluetooth and (on Android) nearby-devices/location permissions when
prompted, or discovery silently returns nothing.
For Android, grant location and nearby-device permissions when prompted so BLE
discovery can run.
4 changes: 4 additions & 0 deletions examples/demo-app/ios/DevelopmentTeam.xcconfig.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Optional template only — not referenced by the OfflineDemo Xcode target.
// Copy to DevelopmentTeam.xcconfig (gitignored) if you wire xcconfig into the
// project yourself; otherwise set Team under Signing & Capabilities in Xcode.
DEVELOPMENT_TEAM = YOUR_TEAM_ID_HERE
8 changes: 8 additions & 0 deletions examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
0C80B921A6F3F58F76C31292 /* libPods-OfflineDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-OfflineDemo.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
7E8F9A0B1C2D3E4F5061728A /* ReactNativeSceneLaunchOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8F9A0B1C2D3E4F50617289 /* ReactNativeSceneLaunchOptions.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
8A2F3B4C5D6E7F8091A2B3C5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */; };
A6527776531B6CBC53134632 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
/* End PBXBuildFile section */

Expand All @@ -23,7 +25,9 @@
5709B34CF0A7D63546082F79 /* Pods-OfflineDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OfflineDemo.release.xcconfig"; path = "Target Support Files/Pods-OfflineDemo/Pods-OfflineDemo.release.xcconfig"; sourceTree = "<group>"; };
5DCACB8F33CDC322A6C60F78 /* libPods-OfflineDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OfflineDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = OfflineDemo/AppDelegate.swift; sourceTree = "<group>"; };
7E8F9A0B1C2D3E4F50617289 /* ReactNativeSceneLaunchOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReactNativeSceneLaunchOptions.swift; path = ../../shared/ios/ReactNativeSceneLaunchOptions.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = OfflineDemo/LaunchScreen.storyboard; sourceTree = "<group>"; };
8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OfflineDemo/SceneDelegate.swift; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

Expand All @@ -44,6 +48,8 @@
children = (
13B07FB51A68108700A75B9A /* Images.xcassets */,
761780EC2CA45674006654EE /* AppDelegate.swift */,
8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */,
7E8F9A0B1C2D3E4F50617289 /* ReactNativeSceneLaunchOptions.swift */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
Expand Down Expand Up @@ -247,6 +253,8 @@
buildActionMask = 2147483647;
files = (
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,
8A2F3B4C5D6E7F8091A2B3C5 /* SceneDelegate.swift in Sources */,
7E8F9A0B1C2D3E4F5061728A /* ReactNativeSceneLaunchOptions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
36 changes: 26 additions & 10 deletions examples/demo-app/ios/OfflineDemo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import ReactAppDependencyProvider

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
/// React Native (`RCTDeviceInfo`, orientation notifications) still reads
/// `UIApplication.shared.delegate?.window` even when using UIScene lifecycle.
var window: UIWindow?

var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
/// Stashed from `application(_:didFinishLaunchingWithOptions:)` for the scene.
private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]?

func application(
_ application: UIApplication,
Expand All @@ -20,16 +23,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

reactNativeDelegate = delegate
reactNativeFactory = factory
self.launchOptions = launchOptions

window = UIWindow(frame: UIScreen.main.bounds)
return true
}

factory.startReactNative(
withModuleName: "OfflineDemo",
in: window,
launchOptions: launchOptions
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)

return true
configuration.delegateClass = SceneDelegate.self
return configuration
}
}

Expand All @@ -40,9 +49,16 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {

override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#if targetEnvironment(simulator)
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
if let embedded = Bundle.main.url(forResource: "main", withExtension: "jsbundle") {
return embedded
}
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#endif
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
17 changes: 17 additions & 0 deletions examples/demo-app/ios/OfflineDemo/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@
<string>Location is used for Bluetooth peer discovery</string>
<key>RCTNewArchEnabled</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
Expand Down
33 changes: 33 additions & 0 deletions examples/demo-app/ios/OfflineDemo/SceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import UIKit
import React

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?

func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene,
let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let factory = appDelegate.reactNativeFactory
else {
assertionFailure("SceneDelegate: AppDelegate or React Native factory is not ready")
return
}

let window = UIWindow(windowScene: windowScene)
self.window = window
appDelegate.window = window

factory.startReactNative(
withModuleName: "OfflineDemo",
in: window,
launchOptions: ReactNativeSceneLaunchOptions.merging(
appDelegate.launchOptions,
sceneConnectionOptions: connectionOptions
)
)
}
}
24 changes: 5 additions & 19 deletions examples/demo-app/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ require Pod::Executable.execute_command('node', ['-p',
{paths: [process.argv[1]]},
)', __dir__]).strip

require File.expand_path('../../shared/ios/pod_fmt_consteval_patch.rb', __dir__)

platform :ios, min_ios_version_supported
prepare_react_native_project!

Expand All @@ -31,28 +33,12 @@ target 'OfflineDemo' do
# :ccache_enabled => true
)

# fmt 11.0.2 (hard-pinned by RCT-Folly -> React Native 0.82) does not compile
# under Xcode 26.5 / clang 21: clang defines __cpp_consteval, so fmt's base.h
# selects FMT_USE_CONSTEVAL=1 and its consteval format-string check fails with
# "call to consteval function ... is not a constant expression". base.h sets
# FMT_USE_CONSTEVAL via a raw #if/#elif chain with NO #ifndef guard, so a
# -DFMT_USE_CONSTEVAL=0 compile flag is silently overridden — the value must be
# forced in the header. Rewrite the two `#define FMT_USE_CONSTEVAL 1` lines to
# 0. Idempotent; re-applied on every `pod install`. Demo-app only — the SDK
# sources are untouched.
fmt_base = File.join(installer.sandbox.root, 'fmt', 'include', 'fmt', 'base.h')
if File.exist?(fmt_base)
original = File.read(fmt_base)
patched = original.gsub('# define FMT_USE_CONSTEVAL 1',
'# define FMT_USE_CONSTEVAL 0')
if patched != original
File.write(fmt_base, patched)
Pod::UI.puts 'Patched fmt/base.h: forced FMT_USE_CONSTEVAL=0 (Xcode 26.5 / clang 21)'.yellow
end
end
apply_fmt_consteval_patch_for_xcode26(installer)

installer.pods_project.targets.each do |target|
target.build_configurations.each do |build_config|
# MeshSdk.podspec still declares 13.0; current Xcode only accepts 15.0+.
build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1'
build_config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
other_cflags = build_config.build_settings['OTHER_CFLAGS'] || '$(inherited)'
other_cxxflags = build_config.build_settings['OTHER_CPLUSPLUSFLAGS'] || '$(inherited)'
Expand Down
4 changes: 2 additions & 2 deletions examples/demo-app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2727,6 +2727,6 @@ SPEC CHECKSUMS:
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb

PODFILE CHECKSUM: 0cad87f465583f2697b61325dad22f801c58afe9
PODFILE CHECKSUM: 5526e8df1b28bf6d6fac655e3c3ed2e763814a03

COCOAPODS: 1.16.2
COCOAPODS: 1.15.2
8 changes: 8 additions & 0 deletions examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
0C80B921A6F3F58F76C31292 /* libPods-MeshWiki.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-MeshWiki.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
9D3E4F5011228394A5B6C7D9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */; };
7E8F9A0B1C2D3E4F50617290 /* ReactNativeSceneLaunchOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8F9A0B1C2D3E4F5061728F /* ReactNativeSceneLaunchOptions.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
D376D8FE1677B6FD73C3EF9F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
/* End PBXBuildFile section */
Expand All @@ -23,6 +25,8 @@
5709B34CF0A7D63546082F79 /* Pods-MeshWiki.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MeshWiki.release.xcconfig"; path = "Target Support Files/Pods-MeshWiki/Pods-MeshWiki.release.xcconfig"; sourceTree = "<group>"; };
5DCACB8F33CDC322A6C60F78 /* libPods-MeshWiki.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MeshWiki.a"; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = MeshWiki/AppDelegate.swift; sourceTree = "<group>"; };
9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = MeshWiki/SceneDelegate.swift; sourceTree = "<group>"; };
7E8F9A0B1C2D3E4F5061728F /* ReactNativeSceneLaunchOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReactNativeSceneLaunchOptions.swift; path = ../../shared/ios/ReactNativeSceneLaunchOptions.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = MeshWiki/LaunchScreen.storyboard; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
Expand All @@ -44,6 +48,8 @@
children = (
13B07FB51A68108700A75B9A /* Images.xcassets */,
761780EC2CA45674006654EE /* AppDelegate.swift */,
9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */,
7E8F9A0B1C2D3E4F5061728F /* ReactNativeSceneLaunchOptions.swift */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
Expand Down Expand Up @@ -247,6 +253,8 @@
buildActionMask = 2147483647;
files = (
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,
9D3E4F5011228394A5B6C7D9 /* SceneDelegate.swift in Sources */,
7E8F9A0B1C2D3E4F50617290 /* ReactNativeSceneLaunchOptions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
33 changes: 23 additions & 10 deletions examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import ReactAppDependencyProvider
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]?

func application(
_ application: UIApplication,
Expand All @@ -20,16 +20,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

reactNativeDelegate = delegate
reactNativeFactory = factory
self.launchOptions = launchOptions

window = UIWindow(frame: UIScreen.main.bounds)
return true
}

factory.startReactNative(
withModuleName: "MeshWiki",
in: window,
launchOptions: launchOptions
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)

return true
configuration.delegateClass = SceneDelegate.self
return configuration
}
}

Expand All @@ -40,9 +46,16 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {

override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#if targetEnvironment(simulator)
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
if let embedded = Bundle.main.url(forResource: "main", withExtension: "jsbundle") {
return embedded
}
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#endif
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
17 changes: 17 additions & 0 deletions examples/mesh-wiki/ios/MeshWiki/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@
<string></string>
<key>RCTNewArchEnabled</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
Expand Down
Loading
Loading