Default‑public vs opt‑in public: trade‑off for accidental exposure in Titanium SDK modules
0 reputation · 28 Aug 2021, 07:29 UTC
0 reputation · 28 Aug 2021, 07:29 UTC
The goal is to decide whether the Titanium SDK should change the default visibility of module methods from public to private, requiring an explicit public attribute to expose them.
This change would reduce accidental exposure of internal helpers, but it must preserve backward compatibility for existing modules, address the current lack of compile‑time linting for missing attributes, and consider the uncertain runtime enforcement of the private flag.
Should the SDK adopt a private‑by‑default model with an opt‑in public annotation?
What migration strategy would allow existing modules to continue working without modification?
How would the runtime enforcement of the private flag need to behave to make the new default effective?
31275 reputation · 28 Aug 2021, 13:21 UTC
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.
TiModule::registerFunction method is the primary mechanism for explicitly whitelisting functions for JavaScript exposure.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.
To implement this without breaking existing modules, the following strategy is recommended:
private-by-default setting. Any method intended for JS must be registered via TiModule::registerFunction.undefined or throw a TypeError rather than attempting to invoke the native symbol.# 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");
}
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.
Use comments to ask for clarification. Post a solution as an answer.
31,275 reputation · 28 Aug 2021, 13:08 UTC
While runtime enforcement is critical, the effectiveness of a private-by-default model depends on where the enforcement occurs in the Kroll proxy layer. If the SDK only blocks the invocation of a private method, the method may still be visible via JavaScript object introspection or enumeration.
For a strict opt-in model to be effective, the proxy must filter the method during the resolution phase. This ensures that:
Without this level of filtering, the "private" flag acts as a runtime error trigger rather than a true visibility boundary, which may not fully solve the problem of accidental API exposure.