clevertap-roku-sdk

中文文档
1.0.0发布于 2026/11/03MIT零依赖

CleverTap SDK for Roku (BrightScript / SceneGraph) - analytics: recordEvent, recordChargedEvent, onUserLogin, profilePush.

原始使用文档
文档目录22 节
  1. Introduction
  2. What this SDK covers
  3. Requirements
  4. Installation
  5. The node is named with a prefix
  6. Installing by hand instead
  7. Integration
  8. Putting the channel on a device
  9. Usage
  10. init takes a map, every other call takes a list
  11. Always write property keys in quotes
  12. Dates
  13. Nested properties
  14. Phone numbers
  15. Profile commands
  16. Privacy and GDPR
  17. Right to suppress - opt out
  18. Device network information
  19. Marketing opt out
  20. Sample channel
  21. Development
  22. License

CleverTap Roku SDK

Analytics SDK for Roku channels, written in BrightScript / SceneGraph. It records events and identifies users, and sends that data to CleverTap.

Introduction

CleverTap brings together real-time user insights, an advanced segmentation engine, and easy-to-use marketing tools in one platform, giving your team the power to create experiences that deepen customer relationships.

For more information, check out our website and documentation. To get started, sign up here.

What this SDK covers

This first version is analytics only. It exposes four calls:

  • recordEvent - record a custom event, with optional properties.
  • recordChargedEvent - record a purchase with line items.
  • onUserLogin - identify a user.
  • profilePush - set attributes on the current user.

Eight more calls change one profile property without replacing it. See Profile commands.

Two calls cover privacy. See Privacy and GDPR.

  • setOptOut - stop or resume reporting for the current viewer.
  • enableDeviceNetworkInfoReporting - turn reporting of network details on or off.

It also exposes getCleverTapID, which returns the current device id (the CleverTap ID) so you can match a session to its profile in the dashboard.

Requirements

  • A Roku SceneGraph channel written in BrightScript.
  • A CleverTap account. Your Account ID, Token, and region are in your CleverTap Dashboard, under Settings -> Integration.

Installation

The SDK is a set of BrightScript source files and SceneGraph components. Install it with ropm, the package manager for Roku channels.

Run both commands from your channel's root - the folder that holds manifest. ropm works from the current directory, so running it a level up writes the SDK somewhere your channel cannot see it.

npm install ropm --save-dev
npx ropm install clevertap-roku-sdk

ropm copies the SDK into source/roku_modules/clevertaprokusdk/ and components/roku_modules/clevertaprokusdk/ in your channel. Commit those folders or re-run npx ropm install in your build, whichever your team prefers.

Check that it landed:

ls source/roku_modules/clevertaprokusdk

Worth doing, because ropm install exits 0 even when it installed nothing. A build step that only looks at the exit code will carry on and produce a channel with no SDK in it, and the first sign of trouble is the node failing to create at runtime. The last line of ropm's own output is the other tell: ropm: Installed 1 module rather than ropm: No modules installed.

The node is named with a prefix

ropm gives every package its own prefix so that two packages cannot register the same component name. The prefix is the package name with the hyphens taken out, so the SDK node is clevertaprokusdk_CleverTap, not CleverTap.

The prefix applies to the node name only. Every call you make on the node keeps its plain name, so callfunc("init", ...) and all the calls further down this page are written exactly as they appear here.

Installing by hand instead

ropm is the supported route and the one the sample uses. If you cannot add a build step, copy this repo's source/ and components/ folders into your channel's own source/ and components/ folders. Nothing renames anything on this route, so the node is plain CleverTap. Every other line on this page is the same.

Integration

Create the SDK node once, keep it somewhere every component can reach, for example on m.global, and initialize it.

' In your main scene's init():
ct = CreateObject("roSGNode", "clevertaprokusdk_CleverTap")
m.global.addFields({ clevertap: ct })

' Pass your credentials directly...
ct.callfunc("init", { accountId: "YOUR_ACCOUNT_ID", token: "YOUR_TOKEN", region: "in1" })

Or, instead of passing credentials in code, add them to your channel manifest:

clevertap_account_id=YOUR_ACCOUNT_ID
clevertap_token=YOUR_TOKEN
clevertap_region=YOUR_REGION

and call init with an empty object:

ct.callfunc("init", {})

Putting the channel on a device

Nothing about this is specific to the SDK, but you need it to see any of the above work.

A Roku channel is a zip with manifest at the top level. Build it from inside your channel's root, or the manifest ends up in a subfolder and the device rejects the zip:

zip -r ../my-channel.zip manifest source components

Put the device in developer mode, then open http://<roku-ip> in a browser and upload the zip. The remote sequence that enables developer mode, and how to find the device's IP, are in sample/RUN_ON_DEVICE.md - steps 1 and 2 there apply to any channel, not only the sample.

To watch what the SDK is doing, connect to the debug console before you sideload:

nc <roku-ip> 8085

With clevertap_log_level=debug in your manifest you should see the SDK start up:

[CleverTap] initialized, account TEST-123-4567, sdk 1.0.0
[CleverTap] event recorded: App Launched
[CleverTap] response status 200, body {"status":"success",...}

No [CleverTap] lines at all usually means the account id or token is missing, which the SDK reports once and then stays inactive.

Usage

Once initialized, call the SDK from anywhere on the render thread through the node you stored:

ct = m.global.clevertap

' Identify a user
ct.callfunc("onUserLogin", [{ "Identity": "u123", "Email": "[email protected]", "Name": "Sam" }])

' Set attributes on the current user
ct.callfunc("profilePush", [{ "Name": "Sam", "plan": "pro", "favTeam": "IND" }])

' Record an event, with optional properties
ct.callfunc("recordEvent", ["Product viewed", { "id": 42, "name": "Bat" }])

' Record a purchase with line items
ct.callfunc("recordChargedEvent", [{ "Amount": 300 }, [{ "id": 1 }, { "id": 2 }]])

' Get the current CleverTap device id (the CleverTap ID)
ctId = ct.callfunc("getCleverTapID", invalid)

init takes a map, every other call takes a list

These two lines look inconsistent, and the difference is easy to miss:

ct.callfunc("init", { accountId: "...", token: "..." })   ' a map, passed as it is
ct.callfunc("recordEvent", ["Product viewed", { "id": 42 }])   ' a list of arguments

init is handed your config map directly. Every other call is handed a list holding its arguments in order, which the SDK unpacks.

A call that takes one argument still takes a list of one. profilePush wants [profileFields], not profileFields:

ct.callfunc("profilePush", [{ "Name": "Sam" }])    ' right
ct.callfunc("profilePush", { "Name": "Sam" })      ' wrong

A call that takes nothing is passed invalid, as getCleverTapID is above.

Nothing stops you at build time, because callfunc names and arguments are not checked until the line runs. Passing a map where a list belongs fails there instead, with this error:

Associative array indexes must be strings

The SDK reads the first argument as args[0], and a map cannot be read that way. The call never happens, so the event or the profile change is simply never recorded.

Always write property keys in quotes

Every key above is in quotes. This matters on Roku.

A key written without quotes inside { } is an identifier to BrightScript, and BrightScript identifiers are case-insensitive, so the device stores the key in lower case. { Amount: 300 } becomes { amount: 300 } before the SDK ever sees it, and the original spelling is gone.

CleverTap matches reserved names exactly. Amount is the purchase value. amount is an ordinary custom property, and the purchase is not reported as revenue. The same applies to Identity, Name, Email, Phone and the other profile fields.

Nothing fails, so this is easy to miss. The SDK reports a key that looks like a reserved name spelled in the wrong case:

[CleverTap] ERROR charge detail 'amount' is sent as a custom property. The server reads it as
'Amount' only when it is spelled that way. Write the key in quotes: { "Amount": ... }

Quoting every key is the simple rule that avoids the whole problem.

Dates

DOB, Birthday and any custom attribute can hold a date. Write the date as a map with one key, date. The value is either an ISO 8601 date and time, or a count of seconds from 1 January 1970:

ct.callfunc("profilePush", [{ "DOB": { "date": "1990-04-17T00:00:00Z" } }])
ct.callfunc("profilePush", [{ "DOB": { "date": 640310400 } }])

Both lines send the same thing. On the wire the value becomes "$D_640310400": the number of seconds from 1 January 1970, behind a prefix that marks the value as a date.

A date before 1 January 1970 is a negative second count, and that works the same way. 3 March 1965 is sent as "$D_-152496000".

Write the ISO 8601 date with a T between the date and the time, as above. A date the device cannot read is reported and left out, so check the console if a date does not arrive.

An event property can be a date too:

ct.callfunc("recordEvent", ["Watched", { "airedOn": { "date": 640310400 } }])

The SDK looks inside maps and lists, so a date is found wherever you write it. The line items of a purchase are a list of maps, and a date in one of them works the same way:

ct.callfunc("recordChargedEvent", [
    { "Amount": 300, "boughtOn": { "date": 640310400 } },
    [{ "id": 1, "name": "Match ticket", "airedOn": { "date": 640310400 } }]
])

The search goes three levels down, counting both maps and lists. A value nested deeper than that is sent without the parts nested inside it, and a date written down there stays a map rather than becoming a date. The SDK says so:

[CleverTap] ERROR 'a.b.c.d' is nested more than 3 levels deep. Its own values were sent, and
anything nested inside it was left out. A date written this deep is not converted.

The three-level limit is also what keeps a mistake cheap. A map can be made to hold itself:

show = {}
show["self"] = show    ' don't do this

Roku's JSON writer refuses a value like that. Without the limit, one such property would make every event waiting on the device unsendable. The SDK copies what you give it, so a value that holds itself costs you that one property and nothing else.

Why a map and not roDateTime. BrightScript has a date object, roDateTime, but you cannot hand one to the SDK. Every call reaches the SDK through callfunc, and an roDateTime does not survive that call. It arrives as nothing. A map does survive, which is why the date is written as one.

Any map that carries a date key is read as a date, before the SDK looks at it as nested data. Whatever else that map holds is dropped, so give the date a map of its own.

A number on its own is not read as a date:

' Stored as the number 640310400, not as a date.
ct.callfunc("profilePush", [{ "DOB": 640310400 }])

The SDK cannot tell a second count apart from a score or a price, so it leaves a plain number alone. Use the map whenever you mean a date.

A date the device cannot read is left out of the request and reported. It is not sent as 1 January 1970, and it does not replace a date CleverTap already holds:

[CleverTap] ERROR date for 'DOB' could not be read from '17 April 1990', so it was left out.
Write the date as "1990-04-17T00:00:00Z", with a T between the date and the time.

Nested properties

A property can hold a map or a list, not just a single value. The SDK sends the whole thing:

ct.callfunc("recordEvent", ["Product viewed", {
    "id": 42
    "show": {
        "title": "Final over"
        "airedOn": { "date": 640310400 }
        "teams": ["IND", "AUS"]
    }
}])

Nesting has to be switched on for your account. The SDK sends a nested value as written, but CleverTap only stores it once the feature is enabled. Event properties and profile attributes are switched on separately, so ask the CleverTap team for both if you need both. Until then the request still succeeds, the console still shows the nested payload going out, and the value is dropped on arrival. Nothing is logged at either end, so the only sign is that the data never reaches the dashboard.

Once it is on, CleverTap flattens what you send. It does not keep the nesting. Each value inside becomes its own property, named by the path it sat at, and the map itself is not a property you can report on. The show map above arrives as three properties, next to the flat id:

show.title     Final over
show.airedOn   17/04/1990
show.teams     IND, AUS

A list adds its position to the name, so teams[0].city is what a map inside a list is called. Build your dashboard reports against those names.

Two things this does not cover. A purchase is left out: CleverTap does not flatten anything inside a Charged event, so keep the line items of a purchase flat and put nested data on an event of your own. A date is not affected either way, because it stops being a map before it leaves the device - it travels as plain text and lands as a date wherever you wrote it.

The nesting limit is the same three levels the date search uses, counting maps and lists the same way.

Phone numbers

Write a phone number in quotes, with a leading + and its country code:

ct.callfunc("profilePush", [{ "Phone": "+14155550123" }])

A number without a + still works when the device reports a country, because CleverTap completes it from there. Some Roku accounts report no country, and then a number without a + cannot be completed. The SDK reports that case and sends the number anyway:

[CleverTap] ERROR Phone '4155550123' carries no country code, and this device reports no
country either. The server may not be able to use the number.

Write the number as text. { "Phone": 14155550123 } is a number, and a number cannot hold a leading + or a leading zero. The SDK reports that too.

The SDK never changes or drops a phone number. Only your app knows what it meant, so both checks are reports.

Profile commands

profilePush replaces the value of a key. The calls below change one part of a value instead, so you do not have to read the old value first.

ct = m.global.clevertap

' Remove a property from the profile
ct.callfunc("profileRemoveValueForKey", ["plan"])

' Replace the whole list of a multi-value property
ct.callfunc("profileSetMultiValues", ["favTeams", ["IND", "AUS"]])

' Put values into that list
ct.callfunc("profileAddMultiValue", ["favTeams", "ENG"])
ct.callfunc("profileAddMultiValues", ["favTeams", ["NZ", "SA"]])

' Take values out of that list
ct.callfunc("profileRemoveMultiValue", ["favTeams", "AUS"])
ct.callfunc("profileRemoveMultiValues", ["favTeams", ["NZ", "SA"]])

' Move a number property up or down
ct.callfunc("profileIncrementValueBy", ["points", 50])
ct.callfunc("profileDecrementValueBy", ["points", 20])

Every one of these takes its arguments in the same order: the property name first, then the value.

A few rules worth knowing:

  • A multi-value property holds text. A number you pass is turned into text, so 5 is stored as "5". One property holds at most 100 values, and anything past that is dropped.
  • The amount for an increment or a decrement must be above zero. To lower a value, use profileDecrementValueBy rather than a negative amount.
  • The SDK keeps no copy of the profile, so it never merges a command with the current value. It sends the command and CleverTap applies it. This also means there is no way to read a property back on the device.
  • A call with a bad property name or a bad value is dropped and logged. Nothing else in the queue is affected.

Privacy and GDPR

Right to suppress - opt out

A viewer can ask not to be reported. Your app offers that choice in its own settings, then tells the SDK:

' Stop reporting this viewer.
clevertap.callfunc("setOptOut", [true])

' Report this viewer again.
clevertap.callfunc("setOptOut", [false])

While a viewer is opted out, nothing is sent. Events you record are dropped before they are queued, so they do not sit on the device waiting either.

There is a middle setting. Pass a second argument to keep the SDK's own bookkeeping events flowing while everything you record is still dropped:

clevertap.callfunc("setOptOut", [true, true])

The only such event this SDK sends is App Launched, which tells CleverTap a session started. Without it, sessions on the server never close. Use this when a viewer asked not to be tracked but you still need usage figures that name nobody. A purchase is not treated as a bookkeeping event, so recordChargedEvent is dropped under either setting.

you record an eventrecordChargedEventApp Launchedprofile changes
opted in (default)sentsentsentsent
[true, true]droppeddroppedsentsent
[true]droppeddroppeddroppeddropped

The SDK sends the choice to CleverTap as a profile attribute, so campaigns built on the server are suppressed too. It then keeps the choice on the device, against that viewer's CleverTap ID. The viewer does not have to opt out again next launch, and a viewer who signs out and back in is still opted out.

By default the SDK reports, and the opt-out is false. Your app is still the place the answer really lives, so calling setOptOut with the stored answer on every launch is good practice.

Device network information

The SDK reports nothing about the network, and tells CleverTap not to work out where a viewer is from the address the request came from. To turn that on:

clevertap.callfunc("enableDeviceNetworkInfoReporting", [true])

That adds whether the device is on WiFi rather than a cable, and lets CleverTap read an approximate location from the request address. A Roku device has no mobile radio, so unlike the mobile SDKs there is no carrier or mobile network to report.

This is off unless you turn it on, and the setting is kept on the device.

Marketing opt out

To take a viewer out of all marketing channels, set these attributes on their profile. This needs no special call:

clevertap.callfunc("profilePush", [{
    "MSG-email": false
    "MSG-push": false
    "MSG-sms": false
    "MSG-push-all": false
}])

By default every channel is available.

Sample channel

A runnable sample lives in sample/. It starts the SDK and shows a menu of these calls; select a row and press OK to fire that call, then check your CleverTap dashboard. See sample/README.md for how to assemble and sideload it.

Development

Building the SDK from source and running its tests is covered in DEVELOPMENT.md.

License

MIT, as declared in package.json.

版本与发布记录

clevertap-roku-sdk 共发布 2 个版本,首次发布于 2026/11/03;过去 12 个月发布了 2 个版本。

版本发布日期距今
1.0.0latest2026/11/03今天
0.0.0-stage2026/11/03今天

关于 clevertap-roku-sdk 的常见问题

clevertap-roku-sdk 是什么?

CleverTap SDK for Roku (BrightScript / SceneGraph) -分析:recordEvent, recordChargedEvent, onUserLogin, profilePush。

如何安装 clevertap-roku-sdk?

在项目目录运行 npm install clevertap-roku-sdk,或使用 pnpm add / yarn add / bun add。

clevertap-roku-sdk 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/clevertap-roku-sdk 是否存在。

clevertap-roku-sdk 可以商用吗?

clevertap-roku-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

clevertap-roku-sdk 还在维护吗?

最新版本 1.0.0 发布于 2026/11/03(今天);至今共发布 2 个版本,其中过去 12 个月发布了 2 个;首次发布于 2026/11/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。