Building and Installing Tizen Application Widgets

Building Application Widgets

To build your Tizen application widget, run the following command from your project’s youi folder:

youi-tv build -p tizen-nacl -c Release -t Package

This script builds two versions of your app in the build/tizen-nacl/debug/CurrentBin folder:

  • The stripped version with no symbols is <your-appname>_armv7.nexe.
  • The version with symbols is <your-appname>. You can specify this unstripped version in the Tizen studio debug field called nexe.

By default, the build scripts generate an armv7 binary that is ready for deployment to a TV. However, if you’d like to test your application with an emulator, you can request an x86_32 version instead with this build flag:

-d YI_ARCH="x86_32"

Note that we recommend against using our built-in flag for overriding signing certificates (-d YI_SIGNING_IDENTITY="MyTizenCertificate") as it will not work due to a known issue with the --sign / -s argument in the most recent versions of Tizen CLI.

Installing Application Widgets

There are several ways to install a Tizen application widget to your Tizen TV or emulator. You can use Tizen Studio as well as the device manager, but we recommend the Tizen CLI. You can also use the SDB to install widget files, but you’ll need a TV with development firmware installed in order to do this.

For more information on this process, refer to Connecting to Your Tizen TV and Tizen Command Line Interface.

Installation Error Codes

The following Tizen (CLI) installation error codes have proven helpful when debugging installation of an application widget on your TV.

Error Code Notes
-5 Failed to install widget, could not find package name from uploaded widget file.
This may be an issue with newly updated firmware. You may not be able to install to this device; try from another computer or try a different device.
-12 Widget signature error
Check that the widget has been properly signed with a valid certificate.
114 Widget upload failure.
Lost connection to device while transferring widget or started uninstallation while already installing a widget.
116 Not enough free space on the target device.
118 Device is either out of space and does not have enough room to install your widget, or it is already installed. Possible causes include:

- There could be an issue with your Tizen config.xml manifest file.
- This device requires a Samsung certificate instead of a Tizen certificate.
- Your device is not whitelisted in your Samsung certificate.
- There may be restricted privileges or trust anchors in your Tizen config.xml manifest file.

On some TVs, the 118### sub-errors are often generalized into a 118 error, so it can often be difficult to track down the root cause of the error.
118001 Trust anchor not allowed in Tizen config.xml manifest file, or there may be some other issue with your manifest.
118003 There is an issue with your certificate. Try using a Tizen certificate instead.
118012 Application already installed with the same ID; uninstall the existing application first. This device may not support Tizen certificate signed widget files; try re-signing your widget using a Samsung certificate. If another Samsung certificate has been pushed to this device, you may need to re-push the certificate to the device.
118014 Not permitted to install this widget onto the target device with the current certificate. Either select a different certificate or ensure that your device unique ID has been added to the certificate whitelist.
118019 Your Tizen config.xml manifest file contains a restricted privilege. Remove it.
118022 Not permitted to install this widget onto the target device with the current certificate. Either select a different certificate or ensure that your device unique ID has been added to the certificate whitelist. A Samsung certificate may be required for this device instead of a Tizen certificate.
[Fail] Fail install. There is the problem to transfer package. The network connection has been dropped while transferring the widget to the target device. Try to re-copy the widget file once connectivity is restored.
APPSYNC_NOT_COMPLETE Applications are currently updating. Wait a few minutes and try again.
wascmd cannot working in released image Your device requires a firmware update from Samsung to install widgets using the Tizen command line interface. You can bypass this error by installing your widget using Tizen Studio instead.
PKGMGR_ERROR If the application is already installed on the TV, try uninstalling it first. If you are installing a Samsung signed widget, try re-signing it with a Tizen certificate instead.

See also the Tizen SDK documentation for additional installation error codes and information.

Config.xml

Similar to Android’s AndroidManifest.xml, Tizen provides a file named config.xml that defines a number of different properties about your application widget, including:

  • Application Display Name
  • Application Version
  • Application Description
  • Application ID - also known as the Tizen ID on the store page
  • Package ID - the prefix of the application ID — that is, the ID generated by Tizen Studio, consisting of 10 characters (0-9, a-z, A-Z)
  • Author Name
  • Application Icon - note that this is used temporarily in the Smart Hub preview when sideloading; the actual icon is pulled from the store page after submission
  • Minimum Tizen Platform Version
  • Target Tizen Platform Version
  • Dimensions - Width / Height
  • Privileges - similar to Android permissions
  • Main Template for Web View

For more information on the specification of this file and editing it manually, see Configuring Applications in the Tizen developer docs.

You.i Platform provides a templated config.xml file that is populated from values set within the project’s CMake code. During the build process, these values are assembled into the final manifest file (config.xml) for your Tizen application. Rather than changing your config.xml file directly, edit the CMakeLists.txt file in the root of your project folder. For a Tizen application, add the following lines to your CMakeLists.txt file, replacing the values as appropriate:

set(YI_SIGNING_IDENTITY "${YI_SIGNING_IDENTITY}" CACHE STRING "Specifies the signing identity to use when packaging the wgt (If not specified the active identity in Tizen Studio will be used.)")
set(YI_AUTHOR_NAME "You.i TV" CACHE STRING "The name of the company that is publishing the widget.")
set(YI_COMPANY_URL "http://your_domain" CACHE STRING "The company URL that will be inserted into the 'config.xml' file.")
set(YI_DESCRIPTION "Description of your widget" CACHE STRING "The description of the widget.")
set(YI_PACKAGE_ID "FmHXPQSBwZ" CACHE STRING "The ID value of the package. This gets combined in 'config.xml' with the YI_APP_NAME to make the application ID.")
set(YI_SMART_HUB_PREVIEW_URL "" CACHE STRING "Set the remote JSON file URL for Smart Hub public preview.")
set(YI_PERMISSIONS "PPB_FileIO,PPB_NetworkMonitor,PPB_File_IO_Private,PPB_FileRef,PPB_FileSystem,PPB_RemovableStorage_Dev" CACHE STRING "The permissions that should be added to the generated .nmf file during packaging.")

Your application must have a custom Package ID before submitting to the store and cannot use the default value of FmHXPQSBwZ as shown above. Using this value will cause a rejection by the Tizen store. For more information on how to generate and set a new Package ID, see Preparing for Store Submission.

To learn more about how the variables are combined to make the final output file, review the config.xml.in file found at <youi-engine-install>/templates/main/Resources/tizen-nacl/WidgetFiles.

To add extra variables or custom metadata to the final config.xml file, copy config.xml.in from <youi-engine-install>/templates/main/Resources/tizen-nacl/WidgetFiles to Resources/tizen-nacl/WidgetFiles in your project folder, and edit the file as required.

If you are planning to target the 2.4 (2016) platforms, add the following line to your config.xml.in file in order for Smart Hub to function correctly:

<tizen:metadata key="http://samsung.com/tv/metadata/devel.api.version" value="2.4"></tizen:metadata>

JavaScript

In addition to the metadata above, there are several You.i Platform JavaScript files that are added to your widget at build time. These files can be found in <youi-engine-install>/templates/main/Resources/tizen-nacl/web/scripts. If making changes to any of these scripts, copy the scripts folder tree to Resources/tizen-nacl/web/scripts in your project, and remember to apply those changes to the updated files at each You.i Platform release.

Smart Hub Preview

When creating a Tizen application, you can add it to a quick launch bar on your Tizen TV known as the Smart Hub. When you focus on an application in this bar, it will display a preview bar spanning the full width of the screen with some featured content from your application that you can deep-link into if you specify a Smart Hub preview configuration.

For more information on the Smart Hub specification and how to create your own, see Smart Hub Preview in the Tizen developer docs. The Smart Hub preview can be set up one of two ways:

Option 1: Using JavaScript Code

// sample preview data
var previewData = {
  "sections": [{
    "title": "Popular Now",
    "tiles": [{
      "title": "Parks and Recreation",
      "subtitle": "Pawnee Pony",
      "display_from": 1422766800,
      "display_until": 1441937400,
      "image_url": "http://yourserver.com/image.jpg",
      "action_url": "myapp://playVideo?videoId=185595-0324",
      "is_playable": true
    }]
  }]
};

try {
  webapis.preview.setPreviewData(
    JSON.stringify(previewData),
    function() {
      console.log("Successfully updated Smart Hub preview.");
    },
    function(error) {
      console.error("Failed to update Smart Hub preview: " + error.message);
      console.error(error.stack);
    }
  );
}
catch(error) {
  console.error("Failed to Set Smart Hub Preview: " + error.message);
  console.error(error.stack);
}

For more information on using the Smart Hub JavaScript preview API, visit this page.

Option 2: Specifying a Preview Configuration URL in config.xml.in

  1. Copy config.xml.in from <youi-engine-install>/templates/main/Resources/tizen-nacl/WidgetFiles to Resources/tizen-nacl/WidgetFiles in your project folder.
  2. Add the following line to the file, replacing http://mywebsite.com/tizen/smarthub_preview.json with the path to your Smart Hub preview configuration file or API endpoint:

    <tizen:metadata key="http://samsung.com/tv/metadata/use.preview" value="endpoint_URL=http://mywebsite.com/tizen/smarthub_preview.json">
    </tizen:metadata>
    

Application Debugging

The Dev Panel and network logger are your main tools for troubleshooting any Tizen application.

When you build your Tizen app in debug mode, a log window appears automatically over the splash screen with information about which scripts are loaded, and any messages logged before the NaCl C++ module loads. These logs will be removed from the UI and forwarded to the engine’s logging system, and ultimately to the network logger if it’s configured. This log window can be scrolled using the up and down directional buttons on the remote control, to help you identify and troubleshoot any issues that occurred before the NaCl C++ module has loaded.

Note: for JavaScript exceptions, like a syntax error, the message is shown along with a stack trace, if available. If an exception is encountered at runtime or the exception is part of a script that is not loaded using the web file loading system, these errors may not be displayed anywhere. Runtime exceptions and promise rejections are typically automatically caught and forwarded to the NaCl C++ module when they’re triggered as part of code being executed by the web messaging bridge.

After the C++ NaCl module loads, you can access the Dev Panel to use an assortment of widgets to help with app debugging.

See also Using the Dev Panel.

Add Log Messages in JavaScript Code

While debugging your application, we strongly suggest you print messages using console.log(), console.debug(), console.info(), and console.warn(). Argument substitution isn’t supported, but you can use multiple arguments. Only warning and error messages are visible for release builds. Remember to remove or comment any such messages you don’t want in your final published your application.

You can also log messages with console.error(), however we suggest you reserve this only for failures where you don’t require the code to halt. Coding best practices suggest it may be better to throw the error or reject a promise. Thrown errors and rejected promises are handled by CYIMessaging and forwarded back to the C++ code for handling instead.

Unhandled errors and promise rejections in Tizen JavaScript files are captured by the logger and printed out with a complete stack trace when possible.

Debugging

Consider the following items when debugging Tizen widgets:

  • You.i TV highly recommends that you use a universal remote when developing on a physical device because it makes testing and interaction a lot easier. You can also optionally use a keyboard and mouse by plugging them into a USB port on the back of your TV; if one USB port doesn’t work, try another. If there are too few available USB ports, try plugging in a USB hub instead.
  • If you need the wired or wireless MAC address, device model, firmware version, or unique id, this information can be found under Support/Contact Samsung on 2016 TVs and Support/About This TV on newer devices.
  • Widget (.wgt) files are actually zip files, and can be extracted using any standard decompression software to view its contents. This can be useful when attempting to determine if a widget has been correctly signed and if there are any issues with the final manifest (config.xml) file.
  • When loading a fully built widget (.wgt) file to a Tizen device or emulator, You.i TV recommends that you use the Tizen CLI instead of the Smart Developer Bridge (SDB) or Tizen Studio; that is, use the tizen install -n my_file.wgt command.
  • Uninstalling a widget from a 2016 TV using the Tizen CLI often indicates that the package ID is invalid; this is normal. Installing over top of the existing widget works fine.
  • To achieve NaCl level debugging on a Tizen widget, you need development firmware. Development firmware allows you to access system logs, access a command line interface to the device, free up space, and so on.
  • The sdb dlog and sdb root on/off commands are not available when using SDB on a device that does not have development firmware. Run the sdb capability command to check what capabilities the device allows for. This command is not documented anywhere on the Samsung website.
  • When you hover over an application in the Smart Hub quick launch area, it displays a preview of a number of different pieces of content that can be played within the application. You can change what is shown either through a custom JSON file through a URL in the config.xml file or through code. Forcing the application to show the latest version in Smart Hub can be problematic. To do so, try power cycling your device by unplugging it and plugging it back in. Pressing the power button on the remote only puts it in standby mode.
  • To clear the Smart Hub cache, you may need to factory reset the Tizen device from the secret hidden factory menu.

You can also refer to the Tizen developer documentation on Tizen System Logs and Checking Logs with Log View.

You.i TV offers the following insight on particular issues that can come up.

Issue Description
Unresponsive application / failure to load Likely caused by a JavaScript or native code crash.
Try surrounding code changes with try or catch and console.log call to find out what the error is.
To get a stack trace if it’s available, print out error.stack.
Native crashes
  • JavaScript syntax errors
  • JavaScript reference errors
  • Empty file names
  • Missing JavaScript files
  • Missing CSS files
  • Files with unsupported extensions
  • Files with no extension

Creating Custom JavaScript Bridges

Custom bridges are used when the software being integrated requires access to the native platform functionality. A custom bridge requires both C++ and JavaScript code which can interface with one another such that native JavaScript based functionality and information is now accessible at the C++ level and vice versa.

For example, you could use a custom bridge to:

  • Gather extra data from the native platform such as the device time zone, the model name, or the manufacturer
  • Invoke a function to initialize a native third-party library if one is available

To create your own custom JavaScript bridge, refer to the following example:

// Sample C++ header file for your custom bridge
#ifndef _EXAMPLE_BRIDGE_WEB_H_
#define _EXAMPLE_BRIDGE_WEB_H_

#include "ExampleBridge.h"

#include <platform/YiWebMessagingBridge.h>
#include <platform/YiWebViewController.h>
#include <utility/YiRapidJSONUtility.h>
#include <utility/YiString.h>

#include <vector>

class CYIWebFileLoader;

class ExampleBridgeWeb : public ExampleBridge
{
public:
    ExampleBridgeWeb();
    virtual ~ExampleBridgeWeb();

    virtual void Init() override;
    virtual CYIString GetNickname() override;
    virtual bool SetNickname(const CYIString &nickname) override;
    virtual CYIString GetIPAddress() override;
    virtual std::vector<CYIString> GetLoadedScripts() override;

private:
    CYIWebMessagingBridge *GetWebMessagingBridge() const;
    CYIWebFileLoader *GetFileLoader() const;
    CYIWebMessagingBridge::FutureResponse CallStaticFunction(yi::rapidjson::Document &&message, const CYIString &functionName, yi::rapidjson::Value &&functionArgumentsValue, bool *pMessageSent);
    CYIWebMessagingBridge::FutureResponse CallInstanceFunction(yi::rapidjson::Document &&message, const CYIString &functionName, yi::rapidjson::Value &&functionArgumentsValue, bool *pMessageSent);
    uint64_t RegisterEventHandler(const CYIString &eventName, CYIWebMessagingBridge::EventCallback &&eventCallback);
    void UnregisterEventHandler(uint64_t &eventHandlerId);
    bool LoadScript();

    uint64_t m_sequentialNumberEventHandlerId;
    mutable std::unique_ptr<CYIWebViewController> m_pWebViewController;
};

#endif // _EXAMPLE_BRIDGE_WEB_H_
// Sample C++ source file for your custom bridge
#include "ExampleBridge_Web.h"

#include <logging/YiLogger.h>
#include <platform/YiWebBridgeLocator.h>
#include <platform/YiWebFileLoader.h>
#include <utility/YiError.h>

#include <chrono>

#define LOG_TAG "ExampleBridgeWeb"

static constexpr const char *EXAMPLE_BRIDGE_CLASS_NAME = "ExampleBridge";
static constexpr const char *EXAMPLE_BRIDGE_INSTANCE_ACCESSOR_NAME = "getInstance";
static constexpr std::chrono::milliseconds GET_IP_ADDRESS_RESPONSE_TIMEOUT_MS(5000);

CYIWebMessagingBridge *ExampleBridgeWeb::GetWebMessagingBridge() const
{
    CYIWebMessagingBridge *pWebMessagingBridge = CYIWebBridgeLocator::GetWebMessagingBridge();

    if (!pWebMessagingBridge)
    {
        if (!m_pWebViewController)
        {
            YI_LOGI(LOG_TAG, "No required web view controller provided, creating instance.");

            m_pWebViewController = std::move(CYIWebViewController::CreateIfSupported());

            YI_ASSERT(m_pWebViewController, LOG_TAG, "Failed to create required web view controller.");
        }

        pWebMessagingBridge = CYIWebBridgeLocator::GetWebMessagingBridge(m_pWebViewController.get());
    }

    YI_ASSERT(pWebMessagingBridge, LOG_TAG, "Web messaging bridge is not available on this platform, or web view controller state has changed.");

    return pWebMessagingBridge;
}

CYIWebFileLoader *ExampleBridgeWeb::GetFileLoader() const
{
    CYIWebFileLoader *pWebFileLoader = GetWebMessagingBridge()->GetFileLoader();

    YI_ASSERT(pWebFileLoader, LOG_TAG, "Web file loader is not available on this platform, or web view controller state has changed.");

    return pWebFileLoader;
}

CYIWebMessagingBridge::FutureResponse ExampleBridgeWeb::CallStaticFunction(yi::rapidjson::Document &&message, const CYIString &functionName, yi::rapidjson::Value &&functionArgumentsValue, bool *pMessageSent)
{
    return GetWebMessagingBridge()->CallStaticFunctionWithArgs(std::move(message), EXAMPLE_BRIDGE_CLASS_NAME, functionName, std::move(functionArgumentsValue), pMessageSent);
}

CYIWebMessagingBridge::FutureResponse ExampleBridgeWeb::CallInstanceFunction(yi::rapidjson::Document &&message, const CYIString &functionName, yi::rapidjson::Value &&functionArgumentsValue, bool *pMessageSent)
{
    return GetWebMessagingBridge()->CallInstanceFunctionWithArgs(std::move(message), EXAMPLE_BRIDGE_CLASS_NAME, EXAMPLE_BRIDGE_INSTANCE_ACCESSOR_NAME, functionName, std::move(functionArgumentsValue), yi::rapidjson::Value(yi::rapidjson::kArrayType), pMessageSent);
}

uint64_t ExampleBridgeWeb::RegisterEventHandler(const CYIString &eventName, CYIWebMessagingBridge::EventCallback &&eventCallback)
{
    return GetWebMessagingBridge()->RegisterEventHandler(EXAMPLE_BRIDGE_CLASS_NAME, eventName, std::move(eventCallback));
}

void ExampleBridgeWeb::UnregisterEventHandler(uint64_t &eventHandlerId)
{
    GetWebMessagingBridge()->UnregisterEventHandler(eventHandlerId);
    eventHandlerId = 0;
}

bool ExampleBridgeWeb::LoadScript()
{
    static constexpr const char *SCRIPT_FILE_NAME = "ExampleBridge.js";

    CYIWebFileLoader *pFileLoader = GetWebMessagingBridge()->GetFileLoader();

    bool messageSent = false;
    CYIWebMessagingBridge::FutureResponse futureResponse = pFileLoader->LoadFile(SCRIPT_FILE_NAME, &messageSent);

    if (!messageSent)
    {
        YI_LOGE(LOG_TAG, "Failed to invoke web file loader load file function when attempting to load example bridge script.");
        return false;
    }

    bool valueAssigned = false;
    CYIWebMessagingBridge::Response response = futureResponse.Take(CYIWebMessagingBridge::DEFAULT_RESPONSE_TIMEOUT_MS, &valueAssigned);

    if (!valueAssigned)
    {
        YI_LOGE(LOG_TAG, "Did not receive a response from the web messaging bridge when attempting to load example bridge script!");
        return false;
    }

    if (response.HasError())
    {
        YI_LOGE(LOG_TAG, "%s", response.GetError()->GetStacktraceOrMessage().GetData());
        return false;
    }

    return true;
}

ExampleBridgeWeb::ExampleBridgeWeb()
    : m_sequentialNumberEventHandlerId(0)
{
    static constexpr const char *SEQUENTIAL_NUMBER_EVENT_NAME = "sequentialNumber";

    m_sequentialNumberEventHandlerId = RegisterEventHandler(SEQUENTIAL_NUMBER_EVENT_NAME, [this](yi::rapidjson::Document &&event) {
        if (event.HasMember(CYIWebMessagingBridge::EVENT_DATA_ATTRIBUTE_NAME) && event[CYIWebMessagingBridge::EVENT_DATA_ATTRIBUTE_NAME].IsInt())
        {
            SequentialNumber.Emit(event[CYIWebMessagingBridge::EVENT_DATA_ATTRIBUTE_NAME].GetInt());
        }
        else
        {
            YI_LOGE(LOG_TAG, "Invalid '%s' event data. JSON string for event: '%s'.", SEQUENTIAL_NUMBER_EVENT_NAME, CYIRapidJSONUtility::CreateStringFromValue(event).GetData());
        }
    });

    if (!LoadScript())
    {
        YI_ASSERT(false, LOG_TAG, "Failed to load example bridge script!");
    }
}

ExampleBridgeWeb::~ExampleBridgeWeb()
{
}

void ExampleBridgeWeb::Init()
{
    static constexpr const char *FUNCTION_NAME = "init";

    bool messageSent = false;
    CYIWebMessagingBridge::FutureResponse futureResponse = CallInstanceFunction(yi::rapidjson::Document(), FUNCTION_NAME, yi::rapidjson::Value(yi::rapidjson::kArrayType), &messageSent);

    if (!messageSent)
    {
        YI_LOGE(LOG_TAG, "Failed to invoke %s function.", FUNCTION_NAME);
    }
    else
    {
        bool valueAssigned = false;
        CYIWebMessagingBridge::Response response = futureResponse.Take(CYIWebMessagingBridge::DEFAULT_RESPONSE_TIMEOUT_MS, &valueAssigned);

        if (!valueAssigned)
        {
            YI_LOGE(LOG_TAG, "%s did not receive a response from the web messaging bridge!", FUNCTION_NAME);
        }
        else if (response.HasError())
        {
            YI_LOGE(LOG_TAG, "%s", response.GetError()->GetMessage().GetData());
        }
    }
}

CYIString ExampleBridgeWeb::GetNickname()
{
    static constexpr const char *FUNCTION_NAME = "getNickname";

    bool messageSent = false;
    CYIWebMessagingBridge::FutureResponse futureResponse = CallInstanceFunction(yi::rapidjson::Document(), FUNCTION_NAME, yi::rapidjson::Value(yi::rapidjson::kArrayType), &messageSent);

    if (!messageSent)
    {
        YI_LOGE(LOG_TAG, "Failed to invoke %s function.", FUNCTION_NAME);
    }
    else
    {
        bool valueAssigned = false;
        CYIWebMessagingBridge::Response response = futureResponse.Take(CYIWebMessagingBridge::DEFAULT_RESPONSE_TIMEOUT_MS, &valueAssigned);

        if (!valueAssigned)
        {
            YI_LOGE(LOG_TAG, "%s did not receive a response from the web messaging bridge!", FUNCTION_NAME);
        }
        else if (response.HasError())
        {
            YI_LOGE(LOG_TAG, "%s", response.GetError()->GetMessage().GetData());
        }
        else
        {
            const yi::rapidjson::Value *pData = response.GetResult();

            if (!pData->IsString())
            {
                YI_LOGE(LOG_TAG, "%s expected a string type for result, received %s. JSON string for result: %s", FUNCTION_NAME, CYIRapidJSONUtility::TypeToString(pData->GetType()).GetData(), CYIRapidJSONUtility::CreateStringFromValue(*pData).GetData());
            }
            else
            {
                return pData->GetString();
            }
        }
    }

    return CYIString();
}

bool ExampleBridgeWeb::SetNickname(const CYIString &nickname)
{
    static constexpr const char *FUNCTION_NAME = "setNickname";

    bool messageSent = false;

    yi::rapidjson::Document command(yi::rapidjson::kObjectType);
    yi::rapidjson::Value arguments(yi::rapidjson::kArrayType);
    yi::rapidjson::MemoryPoolAllocator<yi::rapidjson::CrtAllocator> &allocator = command.GetAllocator();

    yi::rapidjson::Value nicknameValue(nickname.GetData(), allocator);
    arguments.PushBack(nicknameValue, allocator);

    CYIWebMessagingBridge::FutureResponse futureResponse = CallInstanceFunction(std::move(command), FUNCTION_NAME, std::move(arguments), &messageSent);

    if (!messageSent)
    {
        YI_LOGE(LOG_TAG, "Failed to invoke %s function.", FUNCTION_NAME);
    }
    else
    {
        bool valueAssigned = false;
        CYIWebMessagingBridge::Response response = futureResponse.Take(CYIWebMessagingBridge::DEFAULT_RESPONSE_TIMEOUT_MS, &valueAssigned);

        if (!valueAssigned)
        {
            YI_LOGE(LOG_TAG, "%s did not receive a response from the web messaging bridge!", FUNCTION_NAME);
        }
        else if (response.HasError())
        {
            YI_LOGE(LOG_TAG, "%s", response.GetError()->GetMessage().GetData());
        }
        else
        {
            return true;
        }
    }

    return false;
}

CYIString ExampleBridgeWeb::GetIPAddress()
{
    static constexpr const char *FUNCTION_NAME = "getIPAddress";

    bool messageSent = false;
    CYIWebMessagingBridge::FutureResponse futureResponse = CallInstanceFunction(yi::rapidjson::Document(), FUNCTION_NAME, yi::rapidjson::Value(yi::rapidjson::kArrayType), &messageSent);

    if (!messageSent)
    {
        YI_LOGE(LOG_TAG, "Failed to invoke %s function.", FUNCTION_NAME);
    }
    else
    {
        bool valueAssigned = false;
        CYIWebMessagingBridge::Response response = futureResponse.Take(GET_IP_ADDRESS_RESPONSE_TIMEOUT_MS.count(), &valueAssigned);

        if (!valueAssigned)
        {
            YI_LOGE(LOG_TAG, "%s did not receive a response from the web messaging bridge!", FUNCTION_NAME);
        }
        else if (response.HasError())
        {
            YI_LOGE(LOG_TAG, "%s", response.GetError()->GetMessage().GetData());
        }
        else
        {
            const yi::rapidjson::Value *pData = response.GetResult();

            if (!pData->IsString())
            {
                YI_LOGE(LOG_TAG, "%s expected a string type for result, received %s. JSON string for result: %s", FUNCTION_NAME, CYIRapidJSONUtility::TypeToString(pData->GetType()).GetData(), CYIRapidJSONUtility::CreateStringFromValue(*pData).GetData());
            }
            else
            {
                return pData->GetString();
            }
        }
    }

    return CYIString();
}

std::vector<CYIString> ExampleBridgeWeb::GetLoadedScripts()
{
    static constexpr const char *FUNCTION_NAME = "getLoadedScripts";

    std::vector<CYIString> loadedScripts;

    bool messageSent = false;
    CYIWebMessagingBridge::FutureResponse futureResponse = CallStaticFunction(yi::rapidjson::Document(), FUNCTION_NAME, yi::rapidjson::Value(yi::rapidjson::kArrayType), &messageSent);

    if (!messageSent)
    {
        YI_LOGE(LOG_TAG, "Failed to invoke %s function.", FUNCTION_NAME);
    }
    else
    {
        bool valueAssigned = false;
        CYIWebMessagingBridge::Response response = futureResponse.Take(CYIWebMessagingBridge::DEFAULT_RESPONSE_TIMEOUT_MS, &valueAssigned);

        if (!valueAssigned)
        {
            YI_LOGE(LOG_TAG, "GetLoadedScripts did not receive a response from the web messaging bridge!");
        }
        else if (response.HasError())
        {
            YI_LOGE(LOG_TAG, "%s", response.GetError()->GetMessage().GetData());
        }
        else
        {
            const yi::rapidjson::Value *pData = response.GetResult();

            if (!pData->IsArray())
            {
                YI_LOGE(LOG_TAG, "GetLoadedScripts expected an array type for result, received %s. JSON string for result: %s", CYIRapidJSONUtility::TypeToString(pData->GetType()).GetData(), CYIRapidJSONUtility::CreateStringFromValue(*pData).GetData());
            }
            else
            {
                for (uint32_t i = 0; i < pData->Size(); i++)
                {
                    if ((*pData)[i].IsString())
                    {
                        loadedScripts.emplace_back((*pData)[i].GetString());
                    }
                    else
                    {
                        YI_LOGW(LOG_TAG, "GetLoadedScripts encountered an invalid script name at index %u, expected string but received %s. JSON string for element: %s", i, CYIRapidJSONUtility::TypeToString((*pData)[i].GetType()).GetData(), CYIRapidJSONUtility::CreateStringFromValue((*pData)[i]).GetData());
                    }
                }
            }
        }
    }

    return loadedScripts;
}
// Sample C++ source file for your custom bridge base class
#include "ExampleBridge.h"

ExampleBridge::ExampleBridge() = default;

ExampleBridge::~ExampleBridge() = default;

void ExampleBridge::Init()
{
}
// Sample C++ header file for your custom bridge base class
#ifndef _EXAMPLE_BRIDGE_H_
#define _EXAMPLE_BRIDGE_H_

#include <signal/YiSignal.h>
#include <utility/YiString.h>

class ExampleBridge
{
public:
    ExampleBridge();
    virtual ~ExampleBridge();

    virtual void Init();
    virtual CYIString GetNickname() = 0;
    virtual bool SetNickname(const CYIString &nickname) = 0;
    virtual CYIString GetIPAddress() = 0;
    virtual std::vector<CYIString> GetLoadedScripts() = 0;

    CYISignal<int32_t /* sequentialNumber */> SequentialNumber;
};

#endif // _EXAMPLE_BRIDGE_H_
// Sample C++ source file for your custom bridge locator
#include "ExampleBridgeLocator.h"

#include "ExampleBridge.h"

#include <framework/YiPredef.h>

#define LOG_TAG "ExampleBridgeLocator"

#if defined(YI_TIZEN_NACL) || defined(YI_UWP)
#    include "ExampleBridge_Web.h"
#endif

static std::unique_ptr<ExampleBridge> s_pCachedExampleBridge = nullptr;

ExampleBridge *ExampleBridgeLocator::GetExampleBridge()
{
    if (!s_pCachedExampleBridge)
    {
#if defined(YI_TIZEN_NACL) || defined(YI_UWP)
        s_pCachedExampleBridge = std::make_unique<ExampleBridgeWeb>();
#endif

        if (s_pCachedExampleBridge)
        {
            s_pCachedExampleBridge->Init();
        }
    }

    return s_pCachedExampleBridge.get();
}
// Sample C++ header file for your custom bridge locator
#ifndef _EXAMPLE_BRIDGE_LOCATOR_H_
#define _EXAMPLE_BRIDGE_LOCATOR_H_

#include "ExampleBridge.h"

class ExampleBridgeLocator
{
public:
    static ExampleBridge *GetExampleBridge();
};

#endif // _EXAMPLE_BRIDGE_LOCATOR_H_
// Sample JavaScript code for your custom bridge
/**
 * @file ExampleBridge.js
 * @brief An example Tizen NaCl JavaScript bridge.
 */

"use strict";

function ExampleBridge() {
    var self = this;

    self.nickname = "";
    self.sequentialNumberEventIdCounter = 1;
    self.sequentialNumberEventTimeoutId = undefined;
}

ExampleBridge.getInstance = function getInstance() {
    if(CYIUtilities.isInvalid(ExampleBridge.instance)) {
        ExampleBridge.instance = new ExampleBridge();
    }

    return ExampleBridge.instance;
}

ExampleBridge.prototype.init = function init() {
    var self = this;

    self.nickname = "Example Bridge";

    self.startSequentialNumberEventLoop();
};

ExampleBridge.prototype.getNickname = function getNickname() {
    var self = this;

    return self.nickname;
};

ExampleBridge.prototype.setNickname = function setNickname(nickname) {
    var self = this;

    nickname = CYIUtilities.trimString(nickname, "");

    if(CYIUtilities.isEmptyString(nickname)) {
        throw CYIUtilities.createError("Empty or invalid nickname!");
    }

    self.nickname = nickname;
};

ExampleBridge.getLoadedScripts = function getLoadedScripts() {
    var documentScripts = document.getElementsByTagName("script");
    var scriptSources = [];

    for(var i = 0; i < documentScripts.length; i++) {
        scriptSources.push(documentScripts[i].src);
    }

    return scriptSources;
};

ExampleBridge.prototype.getIPAddress = function getIPAddress(options, callback) {
    var self = this;

    if(CYIUtilities.isFunction(options)) {
        callback = options;
        options = null;
    }

    if(!CYIUtilities.isObjectStrict(options)) {
        options = { };
    }

    options.promisify = CYIUtilities.parseBoolean(options.promisify, true);

    if(!CYIUtilities.isFunction(callback)) {
        if(options.promisify) {
            return CYIUtilities.promisify(self.getIPAddressHelper.bind(self))();
        }
    }
    else {
        return self.getIPAddressHelper(callback);
    }
};

ExampleBridge.prototype.getIPAddressHelper = function getIPAddressHelper(callback) {
    var self = this;

    var request = new XMLHttpRequest();

    request.open("GET", "https://api.ipify.org", true);

    request.timeout = 5000;

    request.addEventListener("load", function(event) {
        var ipAddress = request.responseText;

        if(CYIUtilities.isEmptyString(ipAddress)) {
            return callback(CYIUtilities.createError("Invalid IP address response received!"));
        }

        return callback(null, ipAddress);
    });

    request.addEventListener("error", function(event) {
        return callback(CYIUtilities.createError("Connection failed!"));
    });

    request.send();
};

ExampleBridge.prototype.sendEvent = function sendEvent(eventName, data) {
    var self = this;

    return CYIMessaging.sendEvent({
        context: ExampleBridge.name,
        name: eventName,
        data: data
    });
};

ExampleBridge.prototype.startSequentialNumberEventLoop = function startSequentialNumberEventLoop() {
    var self = this;

    if(CYIUtilities.isValid(self.sequentialNumberEventTimeoutId)) {
        return;
    }

    self.sequentialNumberEventTimeoutId = setTimeout(function() {
        self.sendEvent("sequentialNumber", self.sequentialNumberEventIdCounter++);

        self.sequentialNumberEventTimeoutId = undefined;
        self.startSequentialNumberEventLoop();
    }, Math.floor((Math.random() * 9000) + 1000));
};

ExampleBridge.prototype.stopSequentialNumberEventLoop = function stopSequentialNumberEventLoop() {
    var self = this;

    if(CYIUtilities.isValid(self.sequentialNumberEventTimeoutId)) {
        clearTimeout(self.sequentialNumberEventTimeoutId);
        self.sequentialNumberEventTimeoutId = undefined;
    }
};
// C++ code snippet demonstrating how to use your custom bridge
ExampleBridge *pExampleBridge = ExampleBridgeLocator::GetExampleBridge();

if (pExampleBridge)
{
    pExampleBridge->SequentialNumber.Connect([](int32_t sequentialNumber) {
        YI_LOGI(LOG_TAG, "Sequential Number Event #%d!", sequentialNumber);
    });

    CYIString nickname(pExampleBridge->GetNickname());

    YI_LOGI(LOG_TAG, "Default Nickname: '%s'.", nickname.GetData());

    pExampleBridge->SetNickname("Awesome Bridge");

    CYIString newNickname(pExampleBridge->GetNickname());

    YI_LOGI(LOG_TAG, "New Nickname: '%s'.", newNickname.GetData());

    CYIString ipAddress(pExampleBridge->GetIPAddress());

    YI_LOGI(LOG_TAG, "IP Address: '%s'.", ipAddress.GetData());

    std::vector<CYIString> loadedScripts = pExampleBridge->GetLoadedScripts();

    YI_LOGI(LOG_TAG, "%s Scripts Loaded:", CYIString::FromValue(loadedScripts.size()).GetData());

    for (size_t i = 0; i < loadedScripts.size(); i++)
    {
        YI_LOGI(LOG_TAG, "%s. %s", CYIString::FromValue(i + 1).GetData(), loadedScripts[i].GetData());
    }
}
else
{
    YI_LOGW(LOG_TAG, "Example bridge not available for current platform!");
}

Factory Menus

Tizen TVs have admin functions that you can access from the television’s factory menu. Contact Samsung for information on enabling the factory menus.

Be very careful of what options you change in these menus, as there is a danger of damaging your TV to the extent of rendering it inoperable. When debugging and testing, you may find it helpful to delete some applications to make more space available for your application.