Vega Headless Tasks and Services
Headless JavaScript tasks and services enable the execution of JavaScript code in the background, independent of the user interface (UI). This means that the code can run without the need for a UI, hence the name "headless." Both Headless JavaScript tasks and services are suitable for long-running tasks. The distinction lies in their intended use: Headless JavaScript tasks are designed for fire-and-forget tasks, while Headless JavaScript services are intended to be interacted with while they run. For instance, you can utilize Headless JavaScript tasks to fetch channels and their scheduling information from your backend and publish it to Fire TV's Electronic Programming Guide (EPG) without launching the app's UI. In such cases, no interaction with the headless JavaScript task is expected. On the other hand, you can use Headless JavaScript services for media playback, freeing up the main JavaScript runtime for UI and utilizing the secondary JavaScript runtime for downloading and parsing HLS manifests to feed to Vega's media pipeline using W3C APIs for Media. In this scenario, the UI interacts with the Headless JavaScript service to control the media (for example, play, pause, etc.).
JS runtime for headless JS tasks and services
Headless JS tasks and services utilize Vega App Components in the background. They add a secondary JavaScript runtime (powered by Hermes) on top to enable JavaScript code execution and allow you to listen to lifecycle events from your JavaScript code. Each instance of a Headless JS task and service has its own JavaScript runtime instance.
Supported modules in headless JS context
This JavaScript runtime is a simplified version of what is available to the user interface and supports only the modules listed in the following table. This optimization helps maintain a small memory footprint for JavaScript runtimes, which is crucial for low-memory devices.
Module | Note |
---|---|
Logging | console.log |
Networking | fetch |
Timers | |
Promise | |
Platform | Use '@amazon-devices/react-native-kepler/Libraries/Utilities/Platform' to import . import Platform from '@amazon-devices/react-native-kepler/Libraries/Utilities/Platform'; |
Performance | global.performance.now |
ComponentInstanceManager |
Use '@amazon-devices/react-native-kepler/Libraries/ComponentInstance/ComponentInstanceManager' to import. import { ComponentType, type IComponentInstance } from '@amazon-devices/react-native-kepler/Libraries/ComponentInstance/ComponentInstanceManager'; |
FileReader |
Use '@amazon-devices/react-native-kepler/Libraries/Blob/FileReader' to importimport FileReader from '@amazon-devices/react-native-kepler/Libraries/Blob/FileReader'; |
URLSearchParams |
Use '@amazon-devices/react-native-kepler/Libraries/Blob/URL' to importimport {URLSearchParams} from '@amazon-devices/react-native-kepler/Libraries/Blob/URL'; |
I18nManager |
Use '@amazon-devices/react-native-kepler/Libraries/ReactNative/I18nManager' to importimport {I18nManager} from '@amazon-devices/react-native-kepler/Libraries/ReactNative/I18nManager'; |
Supported React Native libraries in headless JS context
Since the headless JS runtime supports limited modules, it only supports a limited set of React Native libraries, listed in the following table.
Library | Note |
---|---|
@amazon-devices/react-native-kepler |
Only select modules are available in headless JS context, see the list above. Do not import from root @amazon-devices/react-native-kepler . |
@amazon-devices/headless-task-manager |
|
@amazon-devices/react-native-w3cmedia |
Only import modules using "@amazon-devices/react-native-w3cmedia/dist/headless" . Only version 2.1.66 and up have this "headless" export |
@amazon-devices/react-native-mmkv |
|
@amazon-devices/react-native-async-storage/async-storage |
See react-native-async-storage documentation |
@amazon-devices/kepler-subscription-entitlement |
|
@amazon-devices/kepler-media-account-login |
|
@amazon-devices/kepler-content-personalization |
|
@amazon-devices/kepler-epg-provider |
|
@amazon-devices/kepler-epg-sync-scheduler |
Using unsupported modules
Using any module or library that isn't listed above results in runtime errors like the following example. This error complains about the DeviceInfo
module that isn't available in headless JS context, but it could be any other unsupported module.
E Volta:[KeplerScript-JavaScript] Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'DeviceInfo' could not be found. Verify that a module by this name is registered in the native binary., js engine: hermes.
You might not be importing the offending module directly and would still see this error. This can happen if you import some other module or library that transitively imports the offending module. This occurs if you try to use a library that is not supported yet, or if you import a module differently from how it is specified above.
For example, while importing from @amazon-devices/react-native-w3cmedia
in the JS code that is meant to run in Headless JS context, instead of importing via its headless export, you import it directly.
import { VideoPlayer } from '@amazon-devices/react-native-w3cmedia/dist/headless'; // Correct
import { VideoPlayer } from '@amazon-devices/react-native-w3cmedia'; // Incorrect
Via the headless export, @amazon-devices/react-native-w3cmedia
ensures that it only exports those modules that are safe to be executed in headless JS context. If headless isn't used while importing, headless JS task/service bundle ends up importing and initializing UI related modules that then transitively try to initialize unsupported modules like DeviceInfo
from @amazon-devices/react-native-kepler
, resulting in the above error.
Future release will introduce tools that can detect the usage of unsupported modules or libraries during the build process. Until then, ensure that you are only using modules and libraries that Vega has deemed safe for headless JavaScript tasks and services.
Build headless JS tasks and services
To register your headless JS tasks and services you need to do the following:
Step 1: Advertise your components in app's manifest (manifest.toml
)
// manifest.toml
# Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All rights reserved.
#
# PROPRIETARY/CONFIDENTIAL. USE IS SUBJECT TO LICENSE TERMS.
#
schema-version = 1
[package]
title = "Demo for Headless JS Task and Service"
version = "1.0.0"
id = "com.yourcompany.headlessjsdemo"
[components]
[[components.interactive]]
id = "com.yourcompany.headlessjsdemo.main"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
categories = ["com.amazon.category.main"]
# App is indicating that it has a headless JS task
[[components.task]]
id = "com.yourcompany.headlessjsdemo.task"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
# App is indicating that it has a headless JS service
[[components.service]]
id = "com.yourcompany.headlessjsdemo.service"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
launch-type = "singleton"
Variations in manifest files
The declaration of Headless JS tasks or services in manifest.toml varies based on their specific purpose. For instance, the documentation to declare EPG Sync Task instructs to use "runtime-module = /com.amazon.kepler.headless.runtime.loader_2@IKeplerScript_2_0"
, while the Headless JS Playback documentation specifies "runtime-module = /com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
.
Similarly, the process in which your Headless JS task or service runs (whether in the same process as your UI component or separately) is use-case specific. For example, Headless JS Playback runs in the same process as your app’s UI component, whereas EPG Sync Tasks are declared to run in a separate process from app’s UI component.
Therefore, it's crucial to consult the documentation for individual features to determine the exact content for the manifest.toml file, rather than copying configurations between different Headless JS use cases, as even subtle differences can have critical impacts.
Step 2: Register entry points
To register entry points for tasks (using the doTask
function) and services (using the onStartService
and onStopService
functions), we recommend that you create two separate files: task.js
and service.js
. These files should be placed alongside index.js
, which registers the entry point for the UI using AppRegistry.registerComponent
. task.js
and service.js
can be used to register entry points for multiple Headless JS tasks and services.
// task.js - Registers entry points for Headless JS task
import { HeadlessEntryPointRegistry } from "@amazon-devices/headless-task-manager";
// SampleHeadlessTask.ts implements `doTask`
import { doTaskSampleTask } from "./src/SampleHeadlessTask";
HeadlessEntryPointRegistry.registerHeadlessEntryPoint2("com.yourcompany.headlessjsdemo.task::doTask",
() => doTaskSampleTask);
// service.js - Registers entry points for Headless JS service
import {HeadlessEntryPointRegistry} from '@amazon-devices/headless-task-manager';
// SampleHeadlessService.ts implements `onStartSampleService` and `onStopSampleServie`
import {onStartSampleService, onStopSampleService} from './src/SampleHeadlessService';
HeadlessEntryPointRegistry.registerHeadlessEntryPoint(
'com.yourcompany.headlessjsdemo.service::onStartService',
() => onStartSampleService,
);
HeadlessEntryPointRegistry.registerHeadlessEntryPoint(
'com.yourcompany.headlessjsdemo.service::onStopService',
() => onStopSampleService,
);
If you use react-native build-kepler
to build your app (see the snippet from package.json below), then to build the hermes bundles for headless JS tasks (task.hermes.bundle
) and services (service.hermes.bundle
), all you need to do is have task.js
and/or service.js
alongside package.json and build-kepler
will look for those automatically and generate the Hermes bytecode bundle for task (task.hermes.bundle
) and service (service.hermes.bundle
) and put them in the app package (VPKG) for you.
.js
extension. If they are called task.ts
or service.ts
, the react-native build-kepler
command will not pick them up, resulting in the missing task.hermes.bundle
or service.hermes.bundle
from the app VPKG. This could lead to ANR (Application Not Responding) crashes or other runtime errors.
// package.json
"scripts": {
.
.
"build:release": "react-native build-kepler --build-type Release",
"build:debug": "react-native build-kepler --build-type Debug",
"build:app": "npm-run-all build:release build:debug",
.
},
But if you don't use react-native build-kepler
to generate your UI Hermes bytecode bundle (index.hermes.bundle
) and package it in the VPKG, then you'll have to do the same to take your entry point file for Headless JS task (typically task.js
) and service (typically service.js
) and generate separate bundles for them and put them in the VPKG.
Debug headless JS tasks and services
Logging
To debug your code in headless JS tasks/services, you can use Console logs (console.log
) and see them in Device Logs
Run the following command from your device/simulator shell:
loggingctl log -f | grep -i "<your package name>\|KeplerScript-Native\|KeplerScript-JavaScript"
To limit the logs from getting trimmed, use the following commands from the device/simulator shell:
loggingctl config --set-rate all 60000
<reboot the device>
loggingctl config --set-rate all 60000 // run this again
Quick JS bundle updates without rebuild
Headless JS tasks and services can now fetch the updated JS bundle without having to rebuild and reinstall the app.
Headless JS tasks and services don't support Hot Reload or Fast Refresh, which allows changes to be visible without relaunching. Instead, Headless JS tasks and services must be relaunched to pull the updated JS bundle. However, this process is still significantly faster than rebuilding and reinstalling the app, and will considerably speed up your development workflow.
To use this feature, follow the steps below.
- Build your app (UI and headless JS components) in debug mode. Note: Line-by-line debugging, as with UI components, doesn't work with release builds.
- Install the app.
- Run the Metro server (
npm start
) from the project root. - Set up reverse port forwarding from your device (target) to your development machine (host) for the port that the app and Metro are configured to use (typically
8081
). Without this step, the app won't be able to connect to the Metro server.
kepler device start-port-forwarding --device <DEVICE_NAME> -p 8081 --forward false
- Launch the app. You should see that all JS bundles (both UI and Headless JS) are now being served by Metro.
Line-by-line Debugging
Headless JS tasks and services support line-by-line debugging. The current line-by-line debugging implementation has a limitation: it only works with Dev Tools shipped with Vega Studio, and Dev Tools only displays the most recently launched JS component when multiple components (UI, tasks, services) are running simultaneously. For example, in the Headless JS Media Playback app, both UI and headless JS playback service run together. Since the headless JS service launches after the UI, it becomes the last launched JS component. As a result, Dev Tools automatically shows service.bundle
as the source. If you want to debug the UI component line-by-line in the Headless JS Media Playback app, first launch the Headless JS service manually using kepler device launch-app -a <Headless JS component name>
and then launch the UI component. This sequence will make Dev Tools show index.bundle
as the source instead.
Follow these steps to use line-by-line debugging.
- Build your app (UI and headless JS components) in debug mode. Note: Line-by-line debugging, as with UI components, doesn't work with release builds.
- Install the app.
- Run the Metro server (
npm start
) from the project root. - Set up reverse port forwarding from your device (target) to your development machine (host) for port 8081 (or the port configured for your app and Metro). Without this step, the app won't be able to connect to the Metro server and interact with the Dev Tools.
kepler device start-port-forwarding --device <DEVICE_NAME> -p 8081 --forward false
- Launch the Headless JS task or service that you want to debug.
- Manually launch with this command:
kepler device launch-app -a <Headless JS component name>
. - Launch the UI component.
- Manually launch with this command:
- In Vega Studio, open the Command Palette and choose "Vega: Launch Dev Tools". Navigate to the "Sources" tab and select "React Native".
- Verify that the listed bundle (
index.bundle
/service.bundle
/task.bundle
) is the one you want to debug and that its source is loaded. Note: Dev Tools will display an error about failing to load the Source Map - this can be ignored. - To set breakpoints, search for your target code and click on the line number where you want to add the breakpoint. Note that breakpoints will only be hit for code that executes after the breakpoint is set. This means breakpoints in code that executes immediately upon launch (such as
doTask
for Headless JS tasks oronStartService
for services) won't be triggered. For these cases, continue using logs for debugging. - Please remember to close the Dev Tools instance and relaunch it between app launches, as Dev Tools won't refresh automatically.
Crash Reports
Unhandled JS errors from a Headless JS Task or Service will cause the app to crash and generate an Aggregated Crash Report (ACR). Currently, this behavior only applies to release builds of Headless JS tasks and services. For debug builds of Headless JS tasks and services, please continue to use Device Logs to detect any unhandled JS exceptions.
Troubleshooting issues with headless JS tasks and services
My headless JS task/services work in debug mode but doesn't launch or errors out in release mode
Check the logs and if you see an error like the following then your task/service bundle is trying to initialize or use a module that's not supported in headless JS context.
E Volta:[KeplerScript-JavaScript] Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'DeviceInfo' could not be found. Verify that a module by this name is registered in the native binary., js engine: hermes.
To identify if that's indeed the case you can do the following:
Step 1: Use react-native-bundle-visualizer
to look at the composition of your headless JS task/service bundle
For example, compare the composition of a task.bundle
when it accidentally pulls in the unsupported modules from @amazon-devices/react-native-kepler
.
npx react-native-bundle-visualizer --entry-file task.js
Scenario 1: Correct import
// task.js
// correct import
import { ComponentType } from '@amazon-devices/react-native-kepler/Libraries/ComponentInstance/ComponentInstanceManager';
const componentInstance = {
name: "TestComponent",
type: ComponentType.TASK,
id: "test-123"
};
// Logging with enum value to string conversion
console.log(`Headless Task Test: Component Type: ${ComponentType[componentInstance.type]}`);
Scenario 2: Wrong import
// task.js
// wrong import
import { ComponentType } from '@amazon-devices/react-native-kepler';
const componentInstance = {
name: "TestComponent",
type: ComponentType.TASK,
id: "test-123"
};
// Logging with enum value to string conversion
console.log(`Headless Task Test: Component Type: ${ComponentType[componentInstance.type]}`);
As you can see from the composition of the task.bundle
in Scenario 2, it clearly indicates that the bundle has included numerous modules from @amazon-devices/react-native-kepler
, even those that are not supported in a headless JS context. Once you have confirmed that the task/service bundle indeed includes unsupported modules, proceed to step 2 to determine who is responsible for bringing them in.
Step 2: Inspect the debug built task.bundle/service.bundle manually
Open the JS bundle (not the Hermes bytecode bundle) for the service/task (whichever is throwing error). You can typically find them in build/lib/rn-bundles/Debug
. Let's say it is task.bundle
. You can open it using any of your favorite editors.
For example, let's take the above task.bundle
with the wrong import, and the following error:
E Volta:[KeplerScript-JavaScript] Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'DeviceInfo' could not be found. Verify that a module by this name is registered in the native binary., js engine: hermes.
You can search for TurboModuleRegistry.getEnforcing
call that takes DeviceInfo
as the input.
Once you find that, you can determine which module has this code.
For example, in above task.bundle
it is:
var NativeModule = TurboModuleRegistry.getEnforcing('DeviceInfo');
var constants = null;
var NativeDeviceInfo = {
getConstants: function getConstants() {
if (constants == null) {
constants = NativeModule.getConstants();
}
return constants;
},
// amznmod_react Use NativeDeviceInfo instead of RCTDeviceEventEmitter to add listener events
addListener: function addListener(eventName, handler) {
NativeModule.addListener(eventName, handler);
},
getDimensionsV2: function getDimensionsV2(rootTag) {
return NativeModule.getDimensionsV2(rootTag);
},
// amznmod_react Use NativeDeviceInfo instead of RCTDeviceEventEmitter to add listener events
addListenerV2: function addListenerV2(rootTag, eventName, callback) {
return NativeModule.addListenerV2(rootTag, eventName, callback);
}
};
var _default = exports.default = NativeDeviceInfo;
},247,[19,3,5],"node_modules/@amazon-devices/react-native-kepler/Libraries/Utilities/NativeDeviceInfo.js");
This code comes from @amazon-devices/react-native-kepler/Libraries/Utilities/NativeDeviceInfo.js
.
Next step is to find out who is depending on this module. For that you can search for module ID 247
in task.bundle
.
In the above task.bundle
, there are two modules that depend on module ID 247:
},246,[3,12,13,4,5,247,20],"node_modules/@amazon-devices/react-native-kepler/Libraries/Utilities/Dimensions.js");
and
},564,[3,22,49,247,441],"node_modules/@amazon-devices/react-native-kepler/Libraries/Utilities/useVegaWindowDimensionsV2.js");
You can pick any one of these and search for its consumers. Doing this few more times leads to the following module: node_modules/@amazon-devices/react-native-kepler/index.js
},1,[2,471,481,484,485,378,20,487,395,490,491,493,494,498,500,28,478,472,404,271,324,408,457,503,288,505,507,418,482,483,421,511,512,513,232,514,515,516,521,171,523,526,375,528,222,199,531,534,444,535,537,455,188,246,337,45,267,334,538,540,364,365,436,82,174,152,151,542,544,245,546,548,550,551,553,259,31,555,556,19,35,446,558,559,563,564,565,568,569,98,570,572,573,4,575,182,21,17,207,204,274,441,296],"node_modules/@amazon-devices/react-native-kepler/index.js");
which is found to be imported from task.js
},0,[1],"task.js");
Future releases will automate the detection of offending modules using a tool. This tool could be integrated into the build step itself to warn about unsupported modules and their origin, potentially causing the build to fail.
Step 3: Reach out to your Amazon contact
If you've encountered a dead end due to the absence of a suitable tool to detect unsupported modules, Amazon will be more than willing to assist you in diagnosing the issue. Please reach out to your Amazon contact for the same.
Current applications of headless JS tasks and services
Headless JS tasks are employed for the following Fire TV integrations:
- Electronic Programming Guide
- Schedule work on app Install or Update: EPG Update, App Manifest Tasks
Headless JS Services are leveraged for the following use cases:
Last updated: Sep 30, 2025