Recommendation
The SDK should adopt a private-by-default model only if runtime enforcement is implemented to block access to unregistered symbols. Without runtime enforcement, the change is merely cosmetic. The most pragmatic approach is to introduce an opt-in build flag that triggers compile-time warnings for unregistered symbols, allowing developers to transition before a hard breaking change is enforced.
Confirmed Facts
- Titanium SDK 5.x+ provides mechanisms to flag unregistered public symbols during the build process when opt-in mode is enabled.
- The
TiModule::registerFunction method is the primary mechanism for explicitly whitelisting functions for JavaScript exposure.
- Default-public behavior automatically exposes exported symbols, which increases the risk of leaking internal helper methods to the JS bridge.
Trade-off Analysis
The primary trade-off is between developer velocity and API surface security. A default-public model allows rapid prototyping as any native method can be called immediately from JavaScript. However, this often leads to "accidental APIs" where internal utility functions are exposed and subsequently relied upon by third-party developers, making them impossible to refactor without breaking downstream apps.
An opt-in public model enforces a strict contract. While it adds a small amount of boilerplate (explicit registration), it ensures that only intended APIs are exposed, reducing the maintenance burden of the SDK's public surface.
Migration and Enforcement Strategy
To implement this without breaking existing modules, the following strategy is recommended:
- Version-Based Default: Introduce a version flag in the module manifest. Modules without this flag are treated as "Legacy" and retain default-public behavior.
- Opt-in Transition: New modules or updated modules use a
private-by-default setting. Any method intended for JS must be registered via TiModule::registerFunction.
- Runtime Enforcement: The JavaScript bridge must be modified to check the registration whitelist. If a JS call targets a native method not present in the whitelist, the bridge should return
undefined or throw a TypeError rather than attempting to invoke the native symbol.
Verification Steps
# Build with opt-in flag to identify unregistered symbols
ti build --platform=ios --opt-in
# Verify runtime isolation in JS
// Attempting to call an internal helper should fail
try {
MyModule.internalHelper();
} catch (e) {
console.log("Access denied: Method is private");
}
Missing Diagnostic Detail
Does the current native-to-JS bridge implementation use a dynamic lookup (reflection) or a static mapping generated at build time? If it uses dynamic lookup, runtime enforcement requires a lookup table check; if static, the code generator simply needs to omit the mapping for private methods.