{"id":28724163,"url":"https://github.com/featurevisor/featurevisor-roku","last_synced_at":"2026-01-20T16:46:42.486Z","repository":{"id":226393121,"uuid":"768071302","full_name":"featurevisor/featurevisor-roku","owner":"featurevisor","description":"Roku (BrightScript) SDK for Featurevisor","archived":false,"fork":false,"pushed_at":"2025-05-23T11:32:41.000Z","size":222,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-05-23T12:46:59.899Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Brightscript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/featurevisor.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-06T12:19:25.000Z","updated_at":"2025-05-23T11:32:43.000Z","dependencies_parsed_at":"2024-03-14T11:29:22.845Z","dependency_job_id":"64a08294-88f8-422e-994b-2a18ca94aa22","html_url":"https://github.com/featurevisor/featurevisor-roku","commit_stats":null,"previous_names":["featurevisor/featurevisor-roku"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/featurevisor/featurevisor-roku","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-roku","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-roku/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-roku/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-roku/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/featurevisor","download_url":"https://codeload.github.com/featurevisor/featurevisor-roku/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-roku/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259957280,"owners_count":22937549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-15T10:09:21.545Z","updated_at":"2025-10-28T18:32:15.307Z","avatar_url":"https://github.com/featurevisor.png","language":"Brightscript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Featurevisor](./assets/banner-bordered.png)](https://featurevisor.com)\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch3\u003e\u003cstrong\u003eFeature management for developers\u003c/strong\u003e\u003c/h3\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003csmall\u003eManage your feature flags and experiments declaratively from the comfort of your Git workflow.\u003c/small\u003e\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003c!-- NPM version --\u003e\n  \u003ca href=\"https://npmjs.org/package/@featurevisor/roku\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/@featurevisor/roku.svg\"\n      alt=\"NPM version\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- License --\u003e\n  \u003ca href=\"./LICENSE\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/@featurevisor/sdk.svg?style=flat-square\"\n      alt=\"License\" /\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch3\u003e\n    \u003ca href=\"https://featurevisor.com\"\u003e\n      Website\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://featurevisor.com/docs/sdks/roku\"\u003e\n      Documentation\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://github.com/featurevisor/featurevisor-roku/issues\"\u003e\n      Issues\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://featurevisor.com/docs/contributing\"\u003e\n      Contributing\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://github.com/featurevisor/featurevisor-roku/blob/main/CHANGELOG.md\"\u003e\n      Changelog\n    \u003c/a\u003e\n  \u003c/h3\u003e\n\u003c/div\u003e\n\n---\n\n# @featurevisor/roku \u003c!-- omit in toc --\u003e\n\nBrightScript SDK for Roku is meant to be used with [kopytko-framework](https://github.com/getndazn/kopytko-framework).\n\nHowever, if you don't use it, you can simply copy all SDK files and their dependencies to your project (a version will be prepared in the future if anyone is interested).\n\nVisit [https://featurevisor.com/docs/sdks/roku](https://featurevisor.com/docs/sdks/roku) for more information.\n\n- [Installation](#installation)\n- [Usage](#usage)\n- [Options](#options)\n  - [`bucketKeySeparator`](#bucketkeyseparator)\n  - [`configureAndInterceptStaticContext`](#configureandinterceptstaticcontext)\n  - [`configureBucketKey`](#configurebucketkey)\n  - [`configureBucketValue`](#configurebucketvalue)\n  - [`datafile`](#datafile)\n  - [`datafileUrl`](#datafileurl)\n  - [`initialFeatures`](#initialfeatures)\n  - [`interceptContext`](#interceptcontext)\n  - [`onActivation`](#onactivation)\n  - [`onReady`](#onready)\n  - [`onRefresh`](#onrefresh)\n  - [`onUpdate`](#onupdate)\n  - [`refreshInterval`](#refreshinterval)\n  - [`stickyFeatures`](#stickyfeatures)\n- [API](#api)\n  - [`f.isEnabled`](#fisenabled)\n  - [`f.getVariation`](#fgetvariation)\n  - [`f.getVariable`](#fgetvariable)\n  - [`f.activate`](#factivate)\n  - [`f.onActivation`](#fonactivation)\n  - [`f.onReady`](#fonready)\n  - [`f.onRefresh`](#fonrefresh)\n  - [`f.onUpdate`](#fonupdate)\n  - [`f.clear`](#fclear)\n  - [`f.getRevision`](#fgetrevision)\n  - [`f.isReady`](#fisready)\n  - [`f.refresh`](#frefresh)\n  - [`f.setDatafile`](#fsetdatafile)\n  - [`f.setStickyFeatures`](#fsetstickyfeatures)\n  - [`f.startRefreshing`](#fstartrefreshing)\n  - [`f.stopRefreshing`](#fstoprefreshing)\n\n## Installation\n\n```bash\nnpm i -P @featurevisor/roku\n```\n\n## Usage\n\nInitialize the SDK (creates `FeaturevisorInstance` node).\nFor example, in the new `MyFeaturevisorNode` created:\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    datafileUrl: \"\u003cfeaturevisor-datafile-url\u003e\",\n  })\nend sub\n```\n\nYou can also pass an existing instance to SDK, to not create a new instance, but to use an existing one to use FeaturevisorSDK methods that are invoked on it.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    ' options from an existing instance are kept but could be overridden\n  }, existingInstance)\nend sub\n```\n\n## Options\n\nOptions you can pass when creating Featurevisor SDK instance:\n\n### `bucketKeySeparator`\n\n- Type: `string`\n- Required: no\n- Defaults to: `.`\n\n### `configureAndInterceptStaticContext`\n\n- Type: `associativeArray`\n- Required: no\n\nThe context for `configureBucketKey`, `configureBucketValue`, and `interceptContext` functions,\nthis object will be accessible via `m` in those functions.\n\n### `configureBucketKey`\n\n- Type: `function`\n- Required: no\n\nUse it to take over bucketing key generation process.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    configureBucketKey: function (feature as Dynamic, context as Object, bucketKey as String) as String\n      return bucketKey\n    end function,\n  })\nend sub\n```\n\n### `configureBucketValue`\n\n- Type: `function`\n- Required: no\n\nUse it to take over bucketing process.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    configureBucketValue: function (feature as Dynamic, context as Object, bucketValue as String) as Integer\n      return bucketValue ' 0 to 100000\n    end function,\n  })\nend sub\n```\n\n### `datafile`\n\n- Type: `associativeArray`\n- Required: either `datafile` or `datafileUrl` is required\n\nUse it to pass the datafile object directly.\n\n### `datafileUrl`\n\n- Type: `string`\n- Required: either `datafile` or `datafileUrl` is required\n\nUse it to pass the URL to fetch the datafile from.\n\n### `initialFeatures`\n\n- Type: `associativeArray`\n- Required: no\n\nPass set of initial features with their variation and (optional) variables that you want the SDK to return until the datafile is fetched and parsed:\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    initialFeatures: {\n      myFeatureKey: {\n        enabled: true,\n\n        ' optional\n        variation: \"treatment\",\n        variables: {\n          myVariableKey: \"my-variable-value\",\n        },\n      },\n    },\n  })\nend sub\n```\n\n### `interceptContext`\n\n- Type: `function`\n- Required: no\n\nIntercept given context before they are used to bucket the user:\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  defaultContext = {\n    platform: \"roku\",\n    locale: \"en_US\",\n    country: \"US\",\n    timezone: \"America/New_York\",\n  }\n  f = FeaturevisorSDK()\n  f.createInstance({\n    configureAndInterceptStaticContext: defaultContext,\n    interceptContext: function (context as Object) as Object\n      joinedContext = {}\n      joinedContext.append(m)\n      joinedContext.append(context)\n\n      return joinedContext\n    end function,\n  })\nend sub\n```\n\n### `onActivation`\n\n- Type: `associativeArray`\n- Required: no\n- Structure: `{ callback: function, context?: associativeArray }`\n\nCapture activated features along with their evaluated variation:\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    onActivation: {\n      callback: sub (data as Object)\n        ' feature has been activated\n      end sub,\n      context: {}, ' optional context for the callback\n    },\n  })\nend sub\n```\n\n`data` Object fields:\n\n- `captureContext`\n- `feature`\n- `context`\n- `variationValue`\n\n`captureContext` will only contain attributes that are marked as `capture: true` in the Attributes' YAML files.\n\n### `onReady`\n\n- Type: `associativeArray`\n- Required: no\n- Structure: `{ callback: function, context?: associativeArray }`\n\nTriggered maximum once when the SDK is ready to be used.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    onReady: {\n      callback: sub ()\n        ' agent has been createInstanced and it is ready\n      end sub,\n      context: {}, ' optional context for the callback\n    },\n  })\nend sub\n```\n\n### `onRefresh`\n\n- Type: `associativeArray`\n- Required: no\n- Structure: `{ callback: function, context?: associativeArray }`\n\nTriggered every time the datafile is refreshed.\n\nWorks only when `datafileUrl` and `refreshInterval` are set.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    onRefresh: {\n      callback: sub ()\n        ' datafile has been refreshed\n      end sub,\n      context: {}, ' optional context for the callback\n    },\n  })\nend sub\n```\n\n### `onUpdate`\n\n- Type: `associativeArray`\n- Required: no\n- Structure: `{ callback: function, context?: associativeArray }`\n\nTriggered every time the datafile is refreshed, and the newly fetched datafile is detected to have different content than last fetched one.\n\nWorks only when `datafileUrl` and `refreshInterval` are set.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    onUpdate: {\n      callback: sub ()\n        ' datafile has been updated (the revision has changed)\n      end sub,\n      context: {}, ' optional context for the callback\n    },\n  })\nend sub\n```\n\n### `refreshInterval`\n\n- Type: `integer` (in seconds)\n- Required: no\n\nSet the interval grater than zero to refresh the datafile.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    datafileUrl: \"\u003cfeaturevisor-datafile-url\u003e\",\n    refreshInterval: 60 * 5, ' every 5 minutes\n  })\nend sub\n```\n\n### `stickyFeatures`\n\n- Type: `associativeArray`\n- Required: no\n\nIf set, the SDK will skip evaluating the datafile and return variation and variable results from this object instead.\n\nIf a feature key is not present in this object, the SDK will continue to evaluate the datafile.\n\n```brightscript\n' @import /components/libs/featurevisor/FeaturevisorSDK.brs from @featurevisor/roku\n\nsub init()\n  f = FeaturevisorSDK()\n  f.createInstance({\n    stickyFeatures: {\n      myFeatureKey: {\n        enabled: true,\n\n        ' optional\n        variation: \"treatment\",\n        variables: {\n          myVariableKey: \"my-variable-value\",\n        },\n      },\n    },\n  })\nend sub\n```\n\n## API\n\n### `f.isEnabled`\n\nCheck if a feature is enabled or not.\n\n\u003e `f.isEnabled(featureKey as String, context = {} as Object) as Boolean`\n\n### `f.getVariation`\n\nGet feature variation.\n\n\u003e `f.getVariation(feature as Dynamic, context = {} as Object) as Dynamic`\n\n### `f.getVariable`\n\nGet feature variable.\n\n\u003e `f.getVariable(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n\nAlso supports additional type specific methods, returns the value of the desired type, or Invalid if the value does not exist or it does not have a desired type:\n\n- `f.getVariableBoolean(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n- `f.getVariableString(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n- `f.getVariableInteger(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n- `f.getVariableDouble(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n- `f.getVariableArray(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n- `f.getVariableObject(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n- `f.getVariableJSON(feature as Dynamic, variableKey as String, context = {} as Object) as Dynamic`\n\n### `f.activate`\n\nSame as `getVariation`, but also calls the `onActivation` callback.\n\nThis is a convenience method meant to be called when you know the User has been exposed to your Feature, and you also want to track the activation.\n\n\u003e `f.activate(feature as Dynamic, context = {} as Object) as Object`\n\n### `f.onActivation`\n\nAdds on activation callback which will be called after an feature activation.\n\n\u003e `f.onActivation(func as Function, context = Invalid as Object)`\n\n### `f.onReady`\n\nAdds on ready callback which will be called after an instance is ready (datafile is saved).\n\n**It should be called before `createInstance`**\n\n\u003e `f.onReady(func as Function, context = Invalid as Object)`\n\n### `f.onRefresh`\n\nAdds on refresh callback which will be called after a successful datafile refresh. But the file doesn't need to change.\n\n\u003e `f.onRefresh(func as Function, context = Invalid as Object)`\n\n### `f.onUpdate`\n\nAdds on update callback which will be called after a successful datafile refresh when it has been changed compared to the previous one.\n\n\u003e `f.onUpdate(func as Function, context = Invalid as Object)`\n\n### `f.clear`\n\nStop refreshing and clear the whole instance. It needs to be initialized once again.\n\n\u003e `f.clear()`\n\n### `f.getRevision`\n\nGet the datafile revision.\n\n\u003e `f.getRevision() as String`\n\n### `f.isReady`\n\nCheck if the instance is ready to be used (the datafile is set).\n\n\u003e `f.isReady() as Boolean`\n\n### `f.refresh`\n\nManually refresh datafile.\n\n\u003e `f.refresh()`\n\n### `f.setDatafile`\n\nSet datafile manually.\n\n\u003e `f.setDatafile(datafile as Object)`\n\n### `f.setStickyFeatures`\n\nSet sticky features.\n\n\u003e `f.setStickyFeatures(stickyFeatures as Object)`\n\n### `f.startRefreshing`\n\nResume or start refreshing if refreshInterval was provided.\n\n\u003e `f.startRefreshing()`\n\n### `f.stopRefreshing`\n\nStop refreshing.\n\n\u003e `f.stopRefreshing()`\n\n## License \u003c!-- omit in toc --\u003e\n\nMIT © Błażej Chełkowski\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeaturevisor%2Ffeaturevisor-roku","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeaturevisor%2Ffeaturevisor-roku","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeaturevisor%2Ffeaturevisor-roku/lists"}