Writing patches
API: Hermes
For React Native apps that ship their JavaScript as Hermes bytecode. The guide page is Hermes JavaScript. Everything here is in app.reseam.patch.
The bundle
HermesScope
The hermes of a running patch: the base APK's assets/index.android.bundle. It opens the first time a patch uses it, and is written back when the run finishes. A missing bundle, a file that isn't Hermes bytecode, or an unsupported bytecode version fails the patch. Version 98 is supported.
| Member | Description |
|---|---|
version: Int |
Opens the bundle and returns its bytecode version. |
modules(block: AppModules.() -> Unit) |
Declares which app modules your extensions' imports mean, for the rest of the run. Patches whose extensions import app modules must depend on the patch that calls this. |
AppModules
The receiver of hermes.modules { }. An extension imports app modules with bare specifiers, such as react or app:screens/Home.tsx. Each specifier resolves to the Metro module declared here.
| Member | Description |
|---|---|
module(specifier: String, module: MetroModule) |
The module this exact specifier means. |
prefix(prefix: String, module: (path: String) -> MetroModule) |
Resolves every specifier that starts with prefix and has no module declaration. The function gets the rest of the specifier. |
Finding functions
function
function(debugName: String? = null, block: HermesFunctionQuery.() -> Unit): FunctionTarget is the one app function matching every constraint. No match, or several, fails the patch. Your extensions' functions are never matched.
HermesFunctionQuery
| Member | Matches functions that |
|---|---|
name(value: String) |
have exactly this name, case included. |
strings(vararg values: String) |
reference each of these strings in an instruction, property names included. |
paramCount(count: Int) |
declare this many parameters, not counting this. |
FunctionTarget
A function in the app's bundle. Hooks attach to it; see Hooks.
| Member | Description |
|---|---|
module: MetroModule |
The Metro module whose factory creates this function, or the function itself when it is a factory. A function created outside a module, or in several, fails the patch. |
MetroModule
One of the app's source modules, as Metro, React Native's bundler, registers it. You get one from FunctionTarget.module and pass it to hermes.modules { }.
Hooks
| Function | Description |
|---|---|
FunctionTarget.before(block: JsCodeScope.() -> Unit) |
Runs at the start. Falling off the end calls the function with its arguments, as the block left them. returnValue returns early instead. |
FunctionTarget.after(block: JsCodeScope.() -> Unit) |
Runs after the function returns. result is its result, which falling off the end returns. |
FunctionTarget.replace(block: JsCodeScope.() -> Unit) |
Runs instead of the function. Falling off the end returns undefined. Hooks added before it no longer run. |
FunctionTarget.alwaysReturn() |
Replaces the body with return undefined. |
FunctionTarget.alwaysReturn(value: Boolean), (value: Int), (value: String) |
Replaces the body with return value. |
FunctionTarget.alwaysReturnNull() |
Replaces the body with return null. |
Hooks compose in the order patches add them: a later hook runs around the function as earlier hooks left it. Targets still match the original app functions, so hooking a function doesn't change what a later query finds.
Some functions can't be hooked, and fail the patch: generators, async functions, class constructors, functions that use new.target or direct eval, and functions whose surrounding scopes Reseam can't work out. A hooked function can't be called with new afterwards, and may lose its debug information.
JsCodeScope
The receiver of a Hermes hook. Like a CodeScope, each call adds code to the app. Branches test as JavaScript does: truthiness, == null, and strict equality.
| Member | Description |
|---|---|
thisObject: JsValue |
The this the function was called with. |
param(index: Int): JsValue |
An argument, as assigned so far. undefined past the last one. |
result: JsValue |
The returned value, in after { }. |
int(value: Int): JsValue, bool(value: Boolean): JsValue, string(value: String): JsValue |
Constants. |
nullValue: JsValue, undefinedValue: JsValue |
null and undefined. |
call(export: JsExport, vararg args: JsValue): JsValue |
Calls an extension's export, with this undefined. |
JsExport.invoke(vararg args: JsValue): JsValue |
The same as call, written as a call: Features.withFeature(result, string("search")). |
whenTrue(value, block), whenFalse(value, block) |
Runs the block when value is truthy, or falsy. |
whenNull(value, block), whenNotNull(value, block) |
Runs the block when value is null or undefined, or neither. |
whenEqual(left, right, block), whenNotEqual(left, right, block) |
Runs the block when left === right, or !==. |
returnValue(value: JsValue) |
Returns value. |
returnUndefined(), returnNull(), returnTrue(), returnFalse() |
Returns that constant. |
Each when… returns a JsOtherwise. Settings add whenEnabled and valueOf.
JsValue
A value in a Hermes code block. assign(value: JsValue) overwrites it in place: an argument, the result, or an earlier call's result. Constants can't be assigned.
JsOtherwise
What a when… function returns. otherwise(block: JsCodeScope.() -> Unit) adds the other branch.
Extensions
ExtJsModule
A JavaScript extension of your bundle. The Gradle plugin generates one per extension, from its entry module's exports, as app.reseam.<folder>.bindings.<Extension>. For apps/example/extensions/features, that is app.reseam.example.bindings.Features.
| Member | Description |
|---|---|
ExtJsModule(name: String) |
The extension, by its bundle name, such as example-features. |
name: String |
Its name. |
export(name: String? = null) |
Declares an exported function, named after the property unless name is given. |
The extension's code first runs when a hook first calls one of its exports. Its exports stay private, and add nothing to the app's global object.
JsExport
A function an extension exports.
| Member | Description |
|---|---|
module: ExtJsModule |
Its extension. |
name: String |
Its exported name. |