├── archbee.yaml ├── templates ├── cra-template-brightsign-app │ ├── template │ │ ├── .nvmrc │ │ ├── .env │ │ ├── gitignore │ │ ├── public │ │ │ ├── robots.txt │ │ │ ├── favicon.ico │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ ├── manifest.json │ │ │ └── index.html │ │ ├── src │ │ │ ├── index.js │ │ │ ├── __mocks__ │ │ │ │ ├── os.js │ │ │ │ └── @brightsign │ │ │ │ │ └── deviceinfo.js │ │ │ ├── index.css │ │ │ ├── App.test.js │ │ │ ├── App.css │ │ │ ├── server │ │ │ │ └── index.js │ │ │ ├── autorun.brs │ │ │ ├── logo.svg │ │ │ └── App.js │ │ ├── scripts │ │ │ └── put │ │ ├── webpack.config.js │ │ └── README.md │ ├── .babelrc │ ├── jest.config.js │ ├── package.json │ ├── template.json │ └── README.md ├── cra-template-brightsign-dashboard │ ├── template │ │ ├── .nvmrc │ │ ├── gitignore │ │ ├── public │ │ │ ├── robots.txt │ │ │ ├── wifi.png │ │ │ ├── XD-1035.png │ │ │ ├── ethernet.png │ │ │ ├── favicon.ico │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ ├── sd_card.png │ │ │ ├── bsn-cloud-logo.png │ │ │ ├── display_settings.png │ │ │ ├── manifest.json │ │ │ └── index.html │ │ ├── src │ │ │ ├── __mocks__ │ │ │ │ ├── @brightsign │ │ │ │ │ ├── videooutput.js │ │ │ │ │ ├── filesysteminfo.js │ │ │ │ │ ├── deviceinfo.js │ │ │ │ │ └── videomodeconfiguration.js │ │ │ │ └── os.js │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ ├── server │ │ │ │ └── index.js │ │ │ ├── App.test.js │ │ │ ├── App.css │ │ │ ├── autorun.brs │ │ │ └── logo.svg │ │ ├── scripts │ │ │ └── put │ │ ├── webpack.config.js │ │ └── README.md │ ├── .babelrc │ ├── jest.config.js │ ├── package.json │ ├── template.json │ └── README.md ├── html5-app-template │ ├── jest.config.js │ ├── .babelrc │ ├── src │ │ ├── __mocks__ │ │ │ └── @brightsign │ │ │ │ └── raptor.ts │ │ ├── app.test.js │ │ ├── device-info.ts │ │ ├── config.ts │ │ ├── index.ts │ │ ├── info.ts │ │ ├── player.ts │ │ ├── index.html │ │ └── autorun.brs │ ├── src-js │ │ ├── __mocks__ │ │ │ └── @brightsign │ │ │ │ └── raptor.js │ │ ├── device-info.js │ │ ├── config.js │ │ ├── app.test.js │ │ ├── index.js │ │ ├── player.js │ │ ├── info.js │ │ ├── autorun.brs │ │ └── index.html │ ├── tsconfig.json │ ├── .eslintrc │ ├── webpack.config.js │ └── package.json └── README.md ├── .prettierignore ├── PoweredByPurple.jpg ├── .gitignore ├── docs ├── images │ ├── ronodejs.png │ └── rohtmlwidget.png └── node-js-notes.md ├── examples ├── provisioning-server │ ├── .dockerignore │ ├── .gitignore │ ├── content │ │ ├── static │ │ │ ├── XD-1035.png │ │ │ └── logo192.png │ │ ├── autorun.brs │ │ └── index.html │ ├── Dockerfile │ ├── package.json │ └── docker-compose.yml ├── html-starter │ ├── static │ │ ├── XD-1035.png │ │ └── logo192.png │ ├── README.md │ ├── autorun.brs │ └── index.html ├── bs-self-updater │ ├── server │ │ ├── autorun.zip │ │ ├── package.json │ │ └── index.js │ ├── tsconfig.json │ ├── webpack.config.js │ ├── autorun.brs │ ├── package.json │ ├── README.md │ └── index.ts ├── node-simple-server │ ├── src │ │ ├── index.js │ │ ├── __mocks__ │ │ │ └── @brightsign │ │ │ │ └── deviceinfo.js │ │ ├── autorun.brs │ │ ├── app.test.js │ │ └── app.js │ ├── jest.config.js │ ├── webpack.config.js │ ├── package.json │ └── README.md ├── enable-ldws │ ├── javascript │ │ ├── autorun.brs │ │ └── index.js │ ├── autorun.brs │ └── registry-config │ │ └── autorun.brs ├── node-starter │ ├── autorun.brs │ ├── index.js │ └── README.md ├── cec-interface │ ├── brightscript │ │ ├── index.html │ │ ├── README.md │ │ └── autorun.brs │ └── javascript │ │ ├── index.html │ │ ├── autorun.brs │ │ ├── README.md │ │ └── index.js ├── self-signed-certs │ ├── index.js │ └── README.md ├── htmlwidget-iframes │ ├── autorun.brs │ └── README.md ├── local-storage │ ├── autorun.brs │ └── README.md ├── send-plugin-message │ ├── pluginMessageApp.html │ ├── README.md │ └── pluginMessageTransfer.brs ├── bluetooth-scan │ ├── autorun.brs │ ├── README.md │ └── index.html ├── indexeddb-caching │ ├── autorun.brs │ └── README.md ├── bs-sqlite-db │ ├── index.js │ └── README.md └── README.md ├── .husky ├── prepare-commit-msg ├── pre-push ├── pre-commit └── common.sh ├── .prettierrc.js ├── config.md ├── scripts ├── bump_version.sh └── workspace_actions.js ├── .eslintrc ├── .github ├── workflows │ ├── release-please.yml │ ├── publish.yml │ └── run-tests.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── LICENSE.txt ├── package.json ├── CONTRIBUTING.md └── CHANGELOG.md /archbee.yaml: -------------------------------------------------------------------------------- 1 | shadowDocs: 2 | - "*" -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/.nvmrc: -------------------------------------------------------------------------------- 1 | 14.17.6 -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/.nvmrc: -------------------------------------------------------------------------------- 1 | 14.17.6 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | coverage 4 | node_modules 5 | yarn.lock -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_PORT=8020 -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /PoweredByPurple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/PoweredByPurple.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log 4 | package-lock.json 5 | .DS_Store 6 | prettiercache 7 | .npmrc -------------------------------------------------------------------------------- /docs/images/ronodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/docs/images/ronodejs.png -------------------------------------------------------------------------------- /docs/images/rohtmlwidget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/docs/images/rohtmlwidget.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/provisioning-server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | data/ 4 | *.db 5 | *.db-journal 6 | .DS_Store 7 | .env 8 | -------------------------------------------------------------------------------- /examples/provisioning-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | data/ 3 | *.db 4 | *.db-journal 5 | npm-debug.log 6 | .DS_Store 7 | .env 8 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | . .husky/common.sh 5 | 6 | npx cz --hook || true -------------------------------------------------------------------------------- /examples/html-starter/static/XD-1035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/examples/html-starter/static/XD-1035.png -------------------------------------------------------------------------------- /examples/html-starter/static/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/examples/html-starter/static/logo192.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/bs-self-updater/server/autorun.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/examples/bs-self-updater/server/autorun.zip -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo 'Running pre-push tasks...' 5 | node ./scripts/workspace_actions.js test -------------------------------------------------------------------------------- /examples/node-simple-server/src/index.js: -------------------------------------------------------------------------------- 1 | import app from "./app.js"; 2 | 3 | app().catch((err) => { 4 | console.error(`Error running server: ${err}`); 5 | }); 6 | -------------------------------------------------------------------------------- /examples/node-simple-server/jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | verbose: true, 3 | testRegex: "(test|spec)\\.[jt]sx?$", 4 | }; 5 | 6 | module.exports = config; 7 | -------------------------------------------------------------------------------- /examples/provisioning-server/content/static/XD-1035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/examples/provisioning-server/content/static/XD-1035.png -------------------------------------------------------------------------------- /examples/provisioning-server/content/static/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/examples/provisioning-server/content/static/logo192.png -------------------------------------------------------------------------------- /templates/html5-app-template/jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | verbose: true, 3 | testRegex: "(test|spec)\\.[jt]sx?$", 4 | }; 5 | 6 | module.exports = config; 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo 'Running pre-commit tasks...' 5 | node ./scripts/workspace_actions.js format 6 | git add -u -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-app/template/public/favicon.ico -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-app/template/public/logo192.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-app/template/public/logo512.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/wifi.png -------------------------------------------------------------------------------- /templates/html5-app-template/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": "defaults" 5 | }], 6 | "@babel/preset-typescript" 7 | ] 8 | } -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/XD-1035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/XD-1035.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/ethernet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/ethernet.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/favicon.ico -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/logo192.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/logo512.png -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/sd_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/sd_card.png -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | command_exists () { 2 | command -v "$1" >/dev/null 2>&1 3 | } 4 | 5 | # Workaround for Windows 10, Git Bash, and Yarn 6 | if command_exists winpty && test -t 1; then 7 | exec < /dev/tty 8 | fi 9 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/bsn-cloud-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/bsn-cloud-logo.png -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | 3 | const config = { 4 | trailingComma: "es5", 5 | tabWidth: 4, 6 | semi: true, 7 | singleQuote: false, 8 | }; 9 | 10 | module.exports = config; -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/display_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brightsign/dev-cookbook/HEAD/templates/cra-template-brightsign-dashboard/template/public/display_settings.png -------------------------------------------------------------------------------- /templates/html5-app-template/src/__mocks__/@brightsign/raptor.ts: -------------------------------------------------------------------------------- 1 | export const info = { 2 | model: "XC4055", 3 | osVersion: "9.0.120", 4 | bootVersion: "9.0.0", 5 | serialNumber: "XC0000000000", 6 | family: "raptor", 7 | }; 8 | -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/__mocks__/@brightsign/raptor.js: -------------------------------------------------------------------------------- 1 | export const info = { 2 | model: "XC4055", 3 | osVersion: "9.0.120", 4 | bootVersion: "9.0.0", 5 | serialNumber: "XC0000000000", 6 | family: "raptor", 7 | }; 8 | -------------------------------------------------------------------------------- /examples/bs-self-updater/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true 9 | }, 10 | "include": ["index.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/__mocks__/@brightsign/videooutput.js: -------------------------------------------------------------------------------- 1 | // __mocks__/@brightsign/videooutput.js 2 | 3 | class VideoOutput { 4 | constructor() { 5 | this.getEdidIdentity = () => 6 | Promise.resolve({ monitorName: "mockMonitor" }); 7 | } 8 | } 9 | 10 | module.exports = VideoOutput; 11 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/device-info.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | 3 | constructor(deviceMock) { 4 | this.model = deviceMock.model; 5 | this.osVersion = deviceMock.osVersion; 6 | this.bootVersion = deviceMock.bootVersion; 7 | this.serialNumber = deviceMock.serialNumber; 8 | this.family = deviceMock.family; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/__mocks__/@brightsign/filesysteminfo.js: -------------------------------------------------------------------------------- 1 | // __mocks__/@brightsign/filesysteminfo.js 2 | 3 | class FileSystemInfo { 4 | constructor() { 5 | this.getStatistics = () => 6 | Promise.resolve({ bytesFree: 1000, sizeBytes: 1000 }); 7 | } 8 | } 9 | 10 | // Export the mock class 11 | module.exports = FileSystemInfo; 12 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/__mocks__/os.js: -------------------------------------------------------------------------------- 1 | // __mocks__/os.js 2 | 3 | class OSClass { 4 | static networkInterfaces() { 5 | return { 6 | eth0: [ 7 | { 8 | family: "IPv4", 9 | address: "0.0.0.0", 10 | }, 11 | ], 12 | }; 13 | } 14 | } 15 | 16 | module.exports = OSClass; 17 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | verbose: true, 3 | preset: "../../node_modules/@babel/preset-react", 4 | testRegex: "(test|spec)\\.[jt]sx?$", 5 | transform: { 6 | "^.+\\.(js|jsx)$": "babel-jest", 7 | ".+\\.(css|styl|less|sass|scss)$": "jest-transform-css", 8 | }, 9 | testEnvironment: "jsdom", 10 | }; 11 | 12 | module.exports = config; 13 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/__mocks__/os.js: -------------------------------------------------------------------------------- 1 | // __mocks__/os.js 2 | 3 | class OSClass { 4 | static networkInterfaces() { 5 | return { 6 | eth0: [ 7 | { 8 | family: "IPv4", 9 | address: "0.0.0.0", 10 | }, 11 | ], 12 | }; 13 | } 14 | } 15 | 16 | module.exports = OSClass; 17 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | verbose: true, 3 | preset: "../../node_modules/@babel/preset-react", 4 | testRegex: "(test|spec)\\.[jt]sx?$", 5 | transform: { 6 | "^.+\\.(js|jsx)$": "babel-jest", 7 | ".+\\.(css|styl|less|sass|scss)$": "jest-transform-css", 8 | }, 9 | testEnvironment: "jsdom", 10 | }; 11 | 12 | module.exports = config; 13 | -------------------------------------------------------------------------------- /examples/provisioning-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | RUN apk add --no-cache python3 make g++ 4 | 5 | WORKDIR /app 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm install --omit=dev 10 | 11 | COPY server.js ./ 12 | COPY autorun ./autorun 13 | COPY content ./content 14 | 15 | RUN mkdir -p /app/data 16 | 17 | EXPOSE 3000 18 | 19 | ENV PORT=3000 20 | ENV DB_PATH=/app/data/provisioning.db 21 | 22 | CMD ["node", "server.js"] 23 | -------------------------------------------------------------------------------- /examples/enable-ldws/javascript/autorun.brs: -------------------------------------------------------------------------------- 1 | Sub Main() 2 | print "Starting LDWS configuration..." 3 | 4 | ' Create message port to communicate with Node.js 5 | mp = CreateObject("roMessagePort") 6 | nodeApp = CreateObject("roNodeJs", "index.js", { message_port: mp }) 7 | 8 | ' Event loop to handle Node.js messages 9 | while true 10 | msg = wait(0, mp) 11 | if type(msg) = "roNodeJsEvent" then 12 | print "Node.js: "; msg 13 | end if 14 | end while 15 | End Sub 16 | -------------------------------------------------------------------------------- /config.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ::use{file="examples/enable-ldws-example/javascript/index.js"} 7 | ::use{file="examples/enable-ldws-example/autorun.brs"} 8 | ::use{file="examples/enable-ldws-example/registry-config/autorun.brs"} -------------------------------------------------------------------------------- /examples/node-simple-server/src/__mocks__/@brightsign/deviceinfo.js: -------------------------------------------------------------------------------- 1 | // __mocks__/@brightsign/deviceinfo.js 2 | 3 | const startTime = Date.now() / 1000; 4 | 5 | class DeviceInfo { 6 | constructor() { 7 | this.model = "MockModel"; 8 | this.osVersion = "MockOSVersion"; 9 | this.serialNumber = "MockSerialNumber"; 10 | this.deviceUptime = Date.now() / 1000 - startTime; 11 | } 12 | } 13 | 14 | // Export the mock class 15 | module.exports = DeviceInfo; 16 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", 4 | "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", 5 | "Helvetica Neue", sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", 4 | "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", 5 | "Helvetica Neue", sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/__mocks__/@brightsign/deviceinfo.js: -------------------------------------------------------------------------------- 1 | // __mocks__/@brightsign/deviceinfo.js 2 | 3 | const startTime = Date.now() / 1000; 4 | 5 | class DeviceInfo { 6 | constructor() { 7 | this.model = "MockModel"; 8 | this.osVersion = "MockOSVersion"; 9 | this.serialNumber = "MockSerialNumber"; 10 | this.deviceUptime = Date.now() / 1000 - startTime; 11 | } 12 | } 13 | 14 | // Export the mock class 15 | module.exports = DeviceInfo; 16 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/__mocks__/@brightsign/deviceinfo.js: -------------------------------------------------------------------------------- 1 | // __mocks__/@brightsign/deviceinfo.js 2 | 3 | const startTime = Date.now() / 1000; 4 | 5 | class DeviceInfo { 6 | constructor() { 7 | this.model = "MockModel"; 8 | this.osVersion = "MockOSVersion"; 9 | this.serialNumber = "MockSerialNumber"; 10 | this.deviceUptime = Date.now() / 1000 - startTime; 11 | } 12 | } 13 | 14 | // Export the mock class 15 | module.exports = DeviceInfo; 16 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/__mocks__/@brightsign/videomodeconfiguration.js: -------------------------------------------------------------------------------- 1 | // __mocks__/@brightsign/videooutputconfiguration.js 2 | 3 | class VideoOutputConfiguration { 4 | constructor() { 5 | this.getActiveMode = () => 6 | Promise.resolve({ 7 | graphicsPlaneHeight: "1080", 8 | graphicsPlaneWidth: "1920", 9 | frequency: "60hz", 10 | }); 11 | } 12 | } 13 | 14 | module.exports = VideoOutputConfiguration; 15 | -------------------------------------------------------------------------------- /examples/bs-self-updater/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bs-autorun-zip-server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "format": "prettier . --write --config ../../../.prettierrc.js --cache --cache-location=../../../prettiercache && yarn lint --fix", 7 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../../.eslintrc template/src/**/*.{js,jsx}", 8 | "start": "node index.js" 9 | }, 10 | "dependencies": { 11 | "express": "^4.18.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/node-starter/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | ' Create directory to store crash-dumps (optional) 3 | dir = CreateDirectory("SD:/brightsign-dumps") 4 | if not dir then 5 | print "Could not create directory" 6 | end if 7 | 8 | mp = createobject("roMessagePort") 9 | 10 | node = createobject("roNodeJs", "index.js", {message_port:mp}) 11 | 12 | while true 13 | msg = wait(0, mp) 14 | if type(msg) = "roMessagePortEvent" 15 | ? "Message received: ";msg 16 | end if 17 | end while 18 | 19 | end function -------------------------------------------------------------------------------- /templates/html5-app-template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "target": "es5", 7 | "outDir": "./dist", 8 | "rootDir": "src", 9 | "sourceMap": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["tmp", "dist", "coverage", "node_modules"], 15 | "watchOptions": { 16 | "excludeDirectories": ["tmp", "dist", "coverage", "**/node_modules"] 17 | } 18 | } -------------------------------------------------------------------------------- /scripts/bump_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script bumps the version of every package to the current latest version in preparation for publishing to Github Packages 4 | 5 | workspace_dirs=$(yarn workspaces --json info | jq -r '.data | fromjson | to_entries[] | .value.location') 6 | 7 | for dir in $workspace_dirs; do 8 | pushd $dir 9 | echo "Current directory: $(pwd)" 10 | 11 | # add package registry configuration from setup-node at the root of the repo 12 | cp ../.npmrc .npmrc 13 | 14 | npm version $GH_REF_VERSION 15 | 16 | popd 17 | done 18 | -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/config.js: -------------------------------------------------------------------------------- 1 | const version = "1.0.0"; 2 | let config; 3 | 4 | const getFromEnv = () => { 5 | return { 6 | version, 7 | isDesktop: process.env.IS_DESKTOP == "true", 8 | nodeEnv: process.env.NODE_ENV || "development", 9 | dwsBaseRoute: process.env.DWS_BASE_ROUTE || "http://localhost", 10 | dwsPassword: process.env.DWS_PASSWORD || "", 11 | }; 12 | }; 13 | 14 | const getConfig = () => { 15 | if (!config) config = getFromEnv(); 16 | return config; 17 | }; 18 | 19 | export { getConfig }; 20 | -------------------------------------------------------------------------------- /examples/node-simple-server/src/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | ' Create directory to store crash-dumps (optional) 3 | dir = CreateDirectory("SD:/brightsign-dumps") 4 | if not dir then 5 | print "Could not create directory" 6 | end if 7 | 8 | mp = createobject("roMessagePort") 9 | 10 | node = createobject("roNodeJs", "dist/bundle.js", {message_port:mp}) 11 | 12 | while true 13 | msg = wait(0, mp) 14 | if type(msg) = "roMessagePortEvent" 15 | ? "Message received: ";msg 16 | end if 17 | end while 18 | 19 | end function -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier", "plugin:jest/recommended"], 3 | "plugins": ["prettier", "jest"], 4 | "rules": { 5 | "prettier/prettier": ["warn"], 6 | "import/no-unresolved": ["off"], 7 | "import/no-extraneous-dependencies": ["off"], 8 | "no-console": ["off"], 9 | "react/jsx-filename-extension": ["off"], 10 | "jest/no-mocks-import": ["off"], 11 | "global-require": ["off"], 12 | "import/extensions": ["error", "ignorePackages", { 13 | "ts": "never" 14 | }] 15 | }, 16 | "env" : { 17 | "browser": true 18 | } 19 | } -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/create-github-app-token@v1 17 | id: app-token 18 | with: 19 | app-id: ${{ secrets.APP_ID }} 20 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 21 | - uses: googleapis/release-please-action@v4 22 | with: 23 | token: ${{ steps.app-token.outputs.token }} 24 | release-type: simple -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/app.test.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const { JSDOM } = require("jsdom"); 4 | 5 | describe("index.html", () => { 6 | it("should display", () => { 7 | const html = fs.readFileSync( 8 | path.resolve(__dirname, "./index.html"), 9 | "utf8" 10 | ); 11 | const dom = new JSDOM(html); 12 | const { document } = dom.window; 13 | 14 | // Check that the document has a element 15 | const title = document.querySelector("title"); 16 | expect(title).not.toBeNull(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /templates/html5-app-template/src/app.test.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const { JSDOM } = require("jsdom"); 4 | 5 | describe("index.html", () => { 6 | it("should display", () => { 7 | const html = fs.readFileSync( 8 | path.resolve(__dirname, "./index.html"), 9 | "utf8" 10 | ); 11 | const dom = new JSDOM(html); 12 | const { document } = dom.window; 13 | 14 | // Check that the document has a <title> element 15 | const title = document.querySelector("title"); 16 | expect(title).not.toBeNull(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/provisioning-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brightsign-provisioning-server", 3 | "version": "1.0.0", 4 | "description": "BrightSign provisioning server for local network deployment", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "node server.js" 9 | }, 10 | "keywords": [ 11 | "brightsign", 12 | "provisioning", 13 | "recovery", 14 | "docker" 15 | ], 16 | "author": "", 17 | "license": "MIT", 18 | "dependencies": { 19 | "express": "^4.18.2", 20 | "better-sqlite3": "^9.2.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/bs-self-updater/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./index.ts", 5 | target: "node", 6 | output: { 7 | filename: "index.js", 8 | path: path.resolve(__dirname, "dist"), 9 | }, 10 | mode: "development", 11 | devtool: false, 12 | resolve: { 13 | extensions: [".ts", ".js"], 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.ts$/, 19 | use: "ts-loader", 20 | }, 21 | ], 22 | }, 23 | externals: { 24 | "@brightsign/system": "commonjs @brightsign/system", 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /examples/provisioning-server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | provisioning-server: 5 | build: . 6 | container_name: brightsign-provisioning 7 | ports: 8 | - "3000:3000" 9 | volumes: 10 | # Persist database across container restarts 11 | - ./data:/app/data 12 | # Mount autorun and content directories for easy updates 13 | - ./autorun:/app/autorun:ro 14 | - ./content:/app/content:ro 15 | environment: 16 | - PORT=3000 17 | - DB_PATH=/app/data/provisioning.db 18 | restart: unless-stopped 19 | networks: 20 | - provisioning-net 21 | 22 | networks: 23 | provisioning-net: 24 | driver: bridge 25 | -------------------------------------------------------------------------------- /examples/cec-interface/brightscript/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang='en'> 3 | <head> 4 | <title>BrightScript CEC Example 5 | 6 | 7 | 22 | 23 | 24 |

BrightScript CEC Example to turn display on and off

25 | 26 | -------------------------------------------------------------------------------- /templates/html5-app-template/src/device-info.ts: -------------------------------------------------------------------------------- 1 | export interface DeviceInfo { 2 | model: string; 3 | osVersion: string; 4 | bootVersion: string; 5 | serialNumber: string; 6 | family: string; 7 | } 8 | 9 | export default class { 10 | model: string; 11 | 12 | osVersion: string; 13 | 14 | bootVersion: string; 15 | 16 | serialNumber: string; 17 | 18 | family: string; 19 | 20 | constructor(deviceMock: DeviceInfo) { 21 | this.model = deviceMock.model; 22 | this.osVersion = deviceMock.osVersion; 23 | this.bootVersion = deviceMock.bootVersion; 24 | this.serialNumber = deviceMock.serialNumber; 25 | this.family = deviceMock.family; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/bs-self-updater/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | 3 | mp = CreateObject("roMessagePort") 4 | 5 | 'Enable Local DWS 6 | EnableLDWS() 7 | 8 | ' Create Node JS Server 9 | node = createobject("roNodeJs", "SD:/index.js", { message_port:mp }) 10 | 11 | 'Event Loop 12 | while true 13 | msg = wait(0,mp) 14 | print "msg received - type=";type(msg) 15 | 16 | if type(msg) = "roNodeJsEvent" then 17 | print "msg: ";msg 18 | end if 19 | end while 20 | 21 | end function 22 | 23 | function EnableLDWS() 24 | registrySection = CreateObject("roRegistrySection", "networking") 25 | if type(registrySection) = "roRegistrySection" then 26 | registrySection.Write("http_server", "80") 27 | end if 28 | registrySection.Flush() 29 | end function 30 | -------------------------------------------------------------------------------- /templates/html5-app-template/src/config.ts: -------------------------------------------------------------------------------- 1 | const version = "1.0.0"; 2 | let config: Config; 3 | 4 | interface Config { 5 | version: string; 6 | isDesktop: boolean; 7 | nodeEnv: string; 8 | dwsBaseRoute: string; 9 | dwsPassword: string; 10 | } 11 | 12 | const getFromEnv = (): Config => { 13 | return { 14 | version, 15 | isDesktop: process.env.IS_DESKTOP == "true", 16 | nodeEnv: process.env.NODE_ENV || "development", 17 | dwsBaseRoute: process.env.DWS_BASE_ROUTE || "http://localhost", 18 | dwsPassword: process.env.DWS_PASSWORD || "", 19 | }; 20 | }; 21 | 22 | const getConfig = (): Config => { 23 | if (!config) config = getFromEnv(); 24 | return config; 25 | }; 26 | 27 | export { getConfig }; 28 | -------------------------------------------------------------------------------- /examples/cec-interface/javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JavaScript CEC Example 5 | 6 | 7 | 22 | 23 | 24 | 25 |

JavaScript CEC Example to turn display on and off

26 | 27 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "BrightSign React App", 3 | "name": "Create React App for BrightSign Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "BrightSign React App", 3 | "name": "Create React App for BrightSign Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | // Path: `__dirname` on player == /storage/sd/dist 4 | const path = __dirname; 5 | const app = express(); 6 | 7 | app.use(express.json()); 8 | app.use(express.static(path)); 9 | 10 | let text = "BrightSign React Web App Dashboard Template"; 11 | 12 | // POST endpoint to receive updates 13 | app.post("/text", (req, res) => { 14 | if (req === undefined) { 15 | res.status(400).send("Bad request: no body provided."); 16 | } 17 | 18 | text = req.body.text; 19 | 20 | res.status(200).send("done"); 21 | }); 22 | 23 | app.get("/text", (req, res) => { 24 | res.status(200).json({ text }); 25 | }); 26 | 27 | const PORT = 8020; 28 | app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve dev-cookbook 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **BrightSign Device (please complete the following information):** 27 | - OS Version: [e.g. 9.0.123] 28 | - Device model: [e.g. XC4055...] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@testing-library/jest-dom"; 3 | import { render, screen } from "@testing-library/react"; 4 | import App from "./App"; 5 | 6 | const DeviceInfo = require("@brightsign/deviceinfo"); 7 | 8 | describe("Device Info Mock", () => { 9 | it("should return mocked device info", () => { 10 | const deviceInfo = new DeviceInfo(); 11 | 12 | expect(deviceInfo.model).toBe("MockModel"); 13 | expect(deviceInfo.osVersion).toBe("MockOSVersion"); 14 | expect(deviceInfo.serialNumber).toBe("MockSerialNumber"); 15 | }); 16 | }); 17 | 18 | test("renders expected body text", () => { 19 | render(); 20 | const linkElement = screen.getByText(/MockOSVersion/i); 21 | expect(linkElement).toBeInTheDocument(); 22 | }); 23 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/App.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | margin: 0; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | font-family: Arial, sans-serif; 9 | background: rgb(16, 9, 34); 10 | background: linear-gradient( 11 | 90deg, 12 | rgba(16, 9, 34, 1) 0%, 13 | rgba(48, 27, 105, 1) 38% 14 | ); 15 | color: white; 16 | display: flex; 17 | align-items: center; 18 | flex-direction: column; 19 | justify-content: center; 20 | padding: 16px; 21 | } 22 | 23 | h1 { 24 | font-size: 48px; 25 | text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); 26 | } 27 | 28 | #info { 29 | margin-top: 32px; 30 | display: flex; 31 | flex-direction: column; 32 | gap: 16px; 33 | font-size: 32px; 34 | } 35 | 36 | .label { 37 | font-weight: bold; 38 | margin-right: 8px; 39 | } 40 | -------------------------------------------------------------------------------- /templates/html5-app-template/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "prettier" 4 | ], 5 | "plugins": ["prettier", "@typescript-eslint"], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaVersion": 2020, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "prettier/prettier": ["warn"], 13 | "import/no-unresolved": ["off"], 14 | "import/no-extraneous-dependencies": ["off"], 15 | "no-console": ["off"], 16 | "import/prefer-default-export": ["off"], 17 | "import/extensions": ["off"], 18 | "import/order": ["off"], 19 | "import/newline-after-import": ["off"], 20 | "no-use-before-define": ["off"], 21 | "arrow-body-style": ["off"], 22 | "eqeqeq": ["off"], 23 | "prefer-destructuring": ["off"], 24 | "@typescript-eslint/no-unused-vars": ["error"] 25 | }, 26 | "env": { 27 | "browser": true, 28 | "node": true 29 | } 30 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for dev-cookbook 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | Describe the problem you're trying to solve. Ex. I'm always frustrated when [...] or, I have this [...] use case that I want to use a BrightSign device for. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of the template or tool you'd like to have. For example, "a template using XYZ framework" or "an example of streaming video from a URL" 15 | 16 | **Describe alternatives you've considered** 17 | Suggest other projects that might be similar to what you'd like to see built, or another solution that we might consider. 18 | 19 | **Additional context** 20 | Add any other context, links, videos, or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /examples/cec-interface/javascript/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | ' Create directory to store crash-dumps (optional) 3 | dir = CreateDirectory("SD:/brightsign-dumps") 4 | if not dir then 5 | print "Could not create directory" 6 | end if 7 | 8 | mp = CreateObject("roMessagePort") 9 | r = CreateObject("roRectangle", 0, 0, 1920, 1080) 10 | 11 | config = { 12 | nodejs_enabled: true, 13 | security_params: { 14 | websecurity: false 15 | }, 16 | url: "file:///sd:/index.html", 17 | brightsign_js_objects_enabled: true, 18 | javascript_enabled: true, 19 | inspector_server: { 20 | port: 2999 21 | } 22 | } 23 | 24 | htmlWidget = CreateObject("roHtmlWidget", r, config) 25 | htmlWidget.SetPort(mp) 26 | htmlWidget.Show() 27 | 28 | while true 29 | msg = wait(0, mp) 30 | if type(msg) = "roMessagePortEvent" 31 | ? "Message received: ";msg 32 | end if 33 | end while 34 | 35 | end function -------------------------------------------------------------------------------- /examples/bs-self-updater/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bs-self-updater", 3 | "description": "A simple self-updater example using TypeScript to run on BrightSign players.", 4 | "version": "1.0.0", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "format": "prettier . --write --config ../../.prettierrc.js --cache --cache-location=../../prettiercache && yarn lint --fix", 8 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc template/src/**/*.{js,jsx}", 9 | "build": "webpack" 10 | }, 11 | "dependencies": { 12 | "@types/decompress": "^4.2.7", 13 | "@types/node-fetch": "^2.6.12", 14 | "decompress": "^4.2.1", 15 | "md5-file": "^5.0.0", 16 | "node-fetch": "2.7.0" 17 | }, 18 | "devDependencies": { 19 | "ts-loader": "^9.5.2", 20 | "typescript": "^5.0.0", 21 | "webpack": "^5.0.0", 22 | "webpack-cli": "^5.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/html-starter/README.md: -------------------------------------------------------------------------------- 1 | # Example HTML App 2 | 3 | ## Introduction 4 | 5 | This example is the first stepping stone of the starter examples to get familiar with developing with a BrightSign. The intention of this example is to show how to get a simple HTML application running on your player. 6 | 7 | The HTML application is defined in `index.html` and is a simplified version of the html templates we offer. In addition to the html file, there is an `autorun.brs` file which is what tells your player to run the application and a `static` directory which contains images displayed by the html. 8 | 9 | ## How to run on a player 10 | 11 | Since this example is simplified, there are only two files and one directory which you will need to copy to the root of the player's sd card. Upon the player booting successfully, it will run the `autorun.brs` file. This file instantiates an html object which points the player to the `index.html` file to display the images in the `static` directory. 12 | -------------------------------------------------------------------------------- /examples/enable-ldws/autorun.brs: -------------------------------------------------------------------------------- 1 | Sub Main() 2 | ' Create network configuration object (0 = eth0, 1 = wlan0, 2 = ppp0, usb0 and usb1 can be input as well) 3 | nc = CreateObject("roNetworkConfiguration", 0) 4 | 5 | ' Configure LDWS: enable on port 80 with password 6 | dwsConfig = { port: 80, open: "your_password_here" } 7 | 8 | print "Enabling LDWS..." 9 | rebootRequired = nc.SetupDWS(dwsConfig) 10 | nc.Apply() 11 | 12 | ' Get device IP address to show user where to connect 13 | currentConfig = nc.GetCurrentConfig() 14 | if type(currentConfig) = "roAssociativeArray" then 15 | ipAddress$ = currentConfig.ip4_address 16 | else 17 | ipAddress$ = "" 18 | endif 19 | 20 | ' Some configurations require restart to take effect 21 | if rebootRequired then 22 | print "Restarting device to apply changes..." 23 | RebootSystem() 24 | else 25 | print "LDWS enabled! Access at: http://"; ipAddress$; "/" 26 | print "Password: your_password_here" 27 | end if 28 | End Sub 29 | -------------------------------------------------------------------------------- /examples/node-simple-server/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const isProduction = process.env.NODE_ENV === "production"; 4 | 5 | module.exports = { 6 | entry: "./src/index", 7 | target: "node", 8 | output: { 9 | filename: "bundle.js", 10 | path: path.resolve(__dirname, "dist"), 11 | }, 12 | mode: isProduction ? "production" : "development", 13 | plugins: [], 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, 18 | type: "asset/resource", 19 | }, 20 | ], 21 | }, 22 | resolve: { 23 | extensions: [".js", ".jsx"], 24 | }, 25 | externals: ({ request }, callback) => { 26 | if (/^@brightsign\//.test(request)) { 27 | return callback(null, "commonjs " + request); 28 | } 29 | callback(); 30 | }, 31 | devtool: isProduction ? "source-map" : "eval-source-map", 32 | }; 33 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Packages Publishing 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish-gpr: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | packages: write 12 | contents: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 1 17 | - name: Use Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: '14.17.6' 21 | registry-url: 'https://registry.npmjs.org' 22 | cache: 'yarn' 23 | always-auth: 'true' 24 | token: ${{ secrets.NPM_TOKEN }} 25 | scope: '@brightsign' 26 | - run: yarn --frozen-lockfile 27 | - run: echo '//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}' > ${{ github.workspace }}/.npmrc 28 | - run: ./scripts/bump_version.sh 29 | env: 30 | GH_REF_VERSION: ${{ github.ref_name }} 31 | - run: yarn workspaces run publish-package -------------------------------------------------------------------------------- /examples/bs-self-updater/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | const app = express(); 6 | const PORT = process.env.PORT || 7000; 7 | 8 | app.get("/autorun.zip", (req, res) => { 9 | const zipPath = path.join(__dirname, "autorun.zip"); 10 | fs.access(zipPath, fs.constants.F_OK, (err) => { 11 | if (err) { 12 | return res.status(404).json({ error: "autorun.zip not found" }); 13 | } 14 | res.sendFile(zipPath, (err) => { 15 | if (err) { 16 | res.status(500).json({ error: "Failed to send file" }); 17 | } 18 | }); 19 | }); 20 | }); 21 | 22 | // Catch-all error handler 23 | app.use((err, req, res, next) => { 24 | res.status(err.status || 500).json({ 25 | error: err.message || "Internal Server Error", 26 | }); 27 | }); 28 | 29 | app.listen(PORT, () => { 30 | console.log(`Server running on port ${PORT}`); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/enable-ldws/registry-config/autorun.brs: -------------------------------------------------------------------------------- 1 | function Main() 2 | print "Enabling LDWS via registry..." 3 | 4 | ' Access the networking section of device registry 5 | registrySection = CreateObject("roRegistrySection", "networking") 6 | 7 | if type(registrySection) = "roRegistrySection" then 8 | 9 | ' Check if LDWS is already enabled 10 | if registrySection.read("http_server") <> "" and registrySection.read("http_server") <> "0" then 11 | print "HTTP server port already set. LDWS may already be enabled." 12 | else if registrySection.read("dwse") = "yes" then 13 | print "LDWS already enabled." 14 | else 15 | ' Set HTTP server to enable LDWS on the specified port 16 | registrySection.write("http_server", "80") 17 | ' Enable Local DWS on the default port 80 18 | ' registrySection.write("dwse", "yes") 19 | print "Registry updated - restart device" 20 | print "After restart: http:///" 21 | RebootSystem() 22 | end if 23 | else 24 | print "Error: Could not access registry" 25 | end if 26 | end function -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/index.js: -------------------------------------------------------------------------------- 1 | import { displayCurrentNetwork, displayDeviceInfo } from "./info"; 2 | import { getConfig } from "./config"; 3 | import express from "express"; 4 | import path from "path"; 5 | const spawn = require("child_process").spawn; 6 | 7 | const app = express(); 8 | const port = 9000; 9 | const config = getConfig(); 10 | 11 | const start = async () => { 12 | console.log( 13 | `config.env: desktop mode - ${config.isDesktop}, environment - ${config.nodeEnv}` 14 | ); 15 | 16 | if (!config.isDesktop) { 17 | displayCurrentNetwork(); 18 | await displayDeviceInfo(); 19 | } else { 20 | // open default browser 21 | app.use(express.static(path.join(__dirname))); 22 | app.listen(port, () => { 23 | console.log(`Server running at http://localhost:${port}/`); 24 | }); 25 | spawn("open", [`http://localhost:${port}/`]); 26 | } 27 | }; 28 | 29 | if (config.isDesktop) { 30 | start(); 31 | } else { 32 | window.main = start; 33 | } 34 | -------------------------------------------------------------------------------- /templates/html5-app-template/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const isProduction = process.env.NODE_ENV === 'production'; 5 | 6 | module.exports = { 7 | entry: './src/index.ts', 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.tsx?$/, 12 | use: 'ts-loader', 13 | exclude: /node_modules/, 14 | }, 15 | ], 16 | }, 17 | resolve: { 18 | extensions: ['.tsx', '.ts', '.js', '.jsx'], 19 | }, 20 | output: { 21 | filename: 'bundle.js', 22 | path: path.resolve(__dirname, 'dist'), 23 | }, 24 | target: 'node', 25 | plugins: [ 26 | new HtmlWebpackPlugin({ 27 | template: './src/index.html', 28 | }), 29 | ], 30 | externals: ({ request }, callback) => { 31 | if (/^@brightsign\//.test(request)) { 32 | return callback(null, 'commonjs ' + request); 33 | } 34 | callback(); 35 | }, 36 | mode: isProduction ? 'production' : 'development', 37 | devtool: isProduction ? 'source-map' : 'eval-source-map', 38 | }; -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 BrightSign 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /templates/html5-app-template/src/index.ts: -------------------------------------------------------------------------------- 1 | import { displayCurrentNetwork, displayDeviceInfo } from "./info"; 2 | import { getConfig } from "./config"; 3 | import express from "express"; 4 | import path from "path"; 5 | const spawn = require("child_process").spawn; 6 | 7 | const app = express(); 8 | const port = 9000; 9 | const config = getConfig(); 10 | 11 | const start = async () => { 12 | console.log( 13 | `config.env: desktop mode - ${config.isDesktop}, environment - ${config.nodeEnv}` 14 | ); 15 | 16 | if (!config.isDesktop) { 17 | displayCurrentNetwork(); 18 | await displayDeviceInfo(); 19 | } else { 20 | // open default browser 21 | app.use(express.static(path.join(__dirname))); 22 | app.listen(port, () => { 23 | console.log(`Server running at http://localhost:${port}/`); 24 | }); 25 | spawn("open", [`http://localhost:${port}/`]); 26 | } 27 | }; 28 | 29 | declare global { 30 | interface Window { 31 | main: () => void; 32 | } 33 | } 34 | 35 | if (config.isDesktop) { 36 | start(); 37 | } else { 38 | window.main = start; 39 | } 40 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | // Path: `__dirname` on player == /storage/sd/dist 4 | const path = __dirname; 5 | const app = express(); 6 | 7 | app.use(express.json()); 8 | app.use(express.static(path)); 9 | app.use((req, res, next) => { 10 | res.setHeader("Access-Control-Allow-Origin", "*"); 11 | res.setHeader("Access-Control-Allow-Headers", "Content-Type"); 12 | res.setHeader( 13 | "Access-Control-Allow-Methods", 14 | "GET, POST, PUT, DELETE, OPTIONS" 15 | ); 16 | next(); 17 | }); 18 | 19 | let text = "BrightSign React Web App Template"; 20 | 21 | // POST endpoint to receive updates 22 | app.post("/text", (req, res) => { 23 | if (req === undefined) { 24 | res.status(400).send("Bad request: no body provided."); 25 | } 26 | 27 | text = req.body.text; 28 | 29 | res.status(200).send("done"); 30 | }); 31 | 32 | app.get("/text", (req, res) => { 33 | res.status(200).json({ text }); 34 | }); 35 | 36 | const port = process.env.REACT_APP_PORT || 8020; 37 | app.listen(port, () => console.log(`Server running on port ${port}`)); 38 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@testing-library/jest-dom"; 3 | import { render, screen, act, waitFor } from "@testing-library/react"; 4 | import App from "./App"; 5 | 6 | const DeviceInfo = require("@brightsign/deviceinfo"); 7 | 8 | describe("Device Info Mock", () => { 9 | it("should return mocked device info", () => { 10 | const deviceInfo = new DeviceInfo(); 11 | 12 | expect(deviceInfo.model).toBe("MockModel"); 13 | expect(deviceInfo.osVersion).toBe("MockOSVersion"); 14 | expect(deviceInfo.serialNumber).toBe("MockSerialNumber"); 15 | }); 16 | }); 17 | 18 | test("renders expected dashboard properties text", async () => { 19 | await act(async () => { 20 | render(); 21 | }); 22 | 23 | const linkElement = screen.getByText(/MockOSVersion/i); 24 | expect(linkElement).toBeInTheDocument(); 25 | 26 | await waitFor(() => { 27 | expect(screen.getByText(/mockMonitor/i)).toBeInTheDocument(); 28 | expect(screen.getByText(/1080x1920@60hz/i)).toBeInTheDocument(); 29 | expect(screen.getByText(/1000b/i)).toBeInTheDocument(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra-template-brightsign-web-app", 3 | "version": "1.0.0", 4 | "description": "Create and publish a web app onto a BrightSign player.", 5 | "main": "index.js", 6 | "keywords": [ 7 | "BrightSign" 8 | ], 9 | "author": "Diego Benitez ", 10 | "license": "SEE LICENSE IN License.txt", 11 | "repository": { 12 | "url": "https://github.com/brightsign/dev-cookbook.git", 13 | "type": "git", 14 | "directory": "cra-template-brightsign-app/" 15 | }, 16 | "publishConfig": { 17 | "registry": "https://registry.npmjs.org" 18 | }, 19 | "scripts": { 20 | "test": "jest --config jest.config.js template/src/", 21 | "format:check": "prettier . --check --config ../.prettierrc.js --cache --cache-location=../prettiercache", 22 | "format": "prettier . --write --config ../.prettierrc.js --cache --cache-location=../prettiercache && yarn lint --fix", 23 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc template/src/**/*.{js,jsx}", 24 | "publish-package": "npm publish --access public" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/player.js: -------------------------------------------------------------------------------- 1 | import { getConfig } from "./config"; 2 | 3 | const config = getConfig(); 4 | 5 | const initializePlayer = async () => { 6 | console.log("initializePlayer()"); 7 | 8 | return { 9 | DeviceInfo: (await import("@brightsign/deviceinfo")).default, 10 | RegistryClass: (await import("@brightsign/registry")).default, 11 | }; 12 | }; 13 | 14 | const initializeDesktop = async () => { 15 | console.log("initializeDesktop()"); 16 | 17 | return { 18 | DeviceInfo: (await import("./device-info")).default, 19 | mockPlayer: (await import("./__mocks__/@brightsign/raptor")).info, 20 | }; 21 | }; 22 | 23 | export async function bsPlayer() { 24 | const player = {}; 25 | 26 | if (config.isDesktop) { 27 | const playerModule = await initializeDesktop(); 28 | player.BSDeviceInfo = new playerModule.DeviceInfo( 29 | playerModule.mockPlayer 30 | ); 31 | } else { 32 | const playerModule = await initializePlayer(); 33 | player.BSDeviceInfo = new playerModule.DeviceInfo(); 34 | player.registry = new playerModule.RegistryClass(); 35 | } 36 | return player; 37 | } 38 | -------------------------------------------------------------------------------- /examples/self-signed-certs/index.js: -------------------------------------------------------------------------------- 1 | const { Agent } = require('undici'); 2 | 3 | // Create an agent that accepts self-signed certificates 4 | const httpsAgent = new Agent({ 5 | connect: { 6 | rejectUnauthorized: false, 7 | }, 8 | }); 9 | 10 | // Example: Making a request to a BrightSign player with self-signed cert 11 | async function communicateWithPlayer(playerIP, endpoint = '/api/v1/status') { 12 | const url = `https://${playerIP}:8443${endpoint}`; 13 | 14 | try { 15 | const response = await fetch(url, { 16 | dispatcher: httpsAgent 17 | }); 18 | 19 | const data = await response.json(); 20 | console.log('Player response:', data); 21 | return data; 22 | } catch (error) { 23 | console.error('Communication failed:', error.message); 24 | throw error; 25 | } 26 | } 27 | 28 | // Usage example 29 | if (require.main === module) { 30 | const playerIP = '192.168.1.100'; // Replace with your player's IP 31 | communicateWithPlayer(playerIP) 32 | .then(data => console.log('Success:', data)) 33 | .catch(err => console.error('Error:', err)); 34 | } 35 | 36 | module.exports = { httpsAgent, communicateWithPlayer }; -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra-template-brightsign-dashboard", 3 | "version": "1.0.0", 4 | "description": "Create and publish a rich dashboard onto a BrightSign player.", 5 | "main": "index.js", 6 | "keywords": [ 7 | "BrightSign" 8 | ], 9 | "author": "Diego Benitez ", 10 | "license": "SEE LICENSE IN License.txt", 11 | "repository": { 12 | "url": "https://github.com/brightsign/dev-cookbook.git", 13 | "type": "git", 14 | "directory": "cra-template-brightsign-dashboard/" 15 | }, 16 | "publishConfig": { 17 | "registry": "https://registry.npmjs.org" 18 | }, 19 | "scripts": { 20 | "test": "jest --config jest.config.js template/src/", 21 | "format:check": "prettier . --check --config ../.prettierrc.js --cache --cache-location=../prettiercache", 22 | "format": "prettier . --write --config ../.prettierrc.js --cache --cache-location=../prettiercache && yarn lint --fix", 23 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc template/src/**/*.{js,jsx}", 24 | "publish-package": "npm publish --access public" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/node-starter/index.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const port = 3000; 3 | // Define the hostname as '0.0.0.0', which means the server will bind to all available network interfaces 4 | const hostname = '0.0.0.0'; 5 | 6 | // Create the HTTP server 7 | const server = http.createServer((req, res) => { 8 | // Check if the request method is 'GET' and the requested URL is '/' 9 | if (req.method === 'GET' && req.url === '/') { 10 | res.statusCode = 200; 11 | res.setHeader('Content-Type', 'text/plain'); 12 | // Send the response body with a congratulatory message 13 | res.end('Congratulations on interacting with your BrightSign running a simple node http application!'); 14 | 15 | } else { 16 | res.statusCode = 404; 17 | res.setHeader('Content-Type', 'text/plain'); 18 | res.end('Route not found. Try a GET request to /'); 19 | } 20 | }); 21 | 22 | // The callback function logs a message to the console when the server starts successfully 23 | server.listen(port, hostname, () => { 24 | // Log a message to indicate that the server setup was successful 25 | console.log('Congratulations on setting up a simple node http application with your BrightSign!'); 26 | console.log(`Server is running at http://${hostname}:${port}`); 27 | }); 28 | -------------------------------------------------------------------------------- /examples/htmlwidget-iframes/autorun.brs: -------------------------------------------------------------------------------- 1 | Sub main() 2 | x = 0 3 | y = 0 4 | width = 1920 5 | height = 1080 6 | url = "https://www.brightsign.biz" 7 | 8 | rect = CreateObject("roRectangle", x, y, width, height) 9 | 10 | securityParams = { 11 | trusted_iframes_enabled: true ' This option is available after OS 9.1.75.3 12 | } 13 | config = { 14 | url: url, 15 | nodejs_enabled: true, 16 | brightsign_js_objects_enabled: true, 17 | security_params: securityParams, 18 | storage_path: "/local/", 19 | } 20 | html = CreateObject("roHtmlWidget", rect, config) 21 | 22 | ' Fallback in case the above fails due to unsupported security parameter 23 | if html = invalid then 24 | config = { 25 | url: url, 26 | nodejs_enabled: true, 27 | brightsign_js_objects_enabled: true, 28 | storage_path: "/local/", 29 | } 30 | html = CreateObject("roHtmlWidget", rect, config) 31 | end if 32 | 33 | html.Show() 34 | 35 | m.msgPort = CreateObject("roMessagePort") 36 | syslog = CreateObject("roSystemLog") 37 | while true 38 | event = wait(0, m.msgPort) 39 | syslog.sendline("@@ type(event)="+ type(event)) 40 | end while 41 | End Sub -------------------------------------------------------------------------------- /examples/local-storage/autorun.brs: -------------------------------------------------------------------------------- 1 | Sub Main() 2 | width = 1920 3 | height = 1080 4 | 5 | vidmode = CreateObject("roVideoMode") 6 | if type(vidmode) = "roVideoMode" 7 | width = vidmode.GetResX() 8 | height = vidmode.GetResY() 9 | end if 10 | 11 | msgPort = CreateObject("roMessagePort") 12 | rect = CreateObject("roRectangle", 0, 0, width, height) 13 | 14 | 'inspector disabled currently 15 | 'inspectorServer = { 16 | ' port: 3010 17 | '} 18 | 19 | config = { 20 | url: "file:/SD:/index.html", ' you can use a server or website URL here - "https://example.com/index.html" 21 | mouse_enabled: false, ' set to true to enable mouse/keyboard 22 | 'inspector_server: inspectorServer ' uncomment to enable inspector server 23 | security_params: { websecurity: true } 24 | javascript_enabled: true, ' Else set to false to disable JavaScript 25 | port: msgPort, 26 | } 27 | 28 | html = CreateObject("roHtmlWidget", rect, config) 29 | html.Show() 30 | 31 | syslog = CreateObject("roSystemLog") 32 | 33 | while true 34 | event = wait(0, msgPort) 35 | syslog.sendline("@@ type(event)="+ type(event)) 36 | end while 37 | End Sub 38 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "scripts": { 4 | "build": "webpack --mode=development", 5 | "build:prod": "webpack --mode=production --node-env=production", 6 | "clean": "rm -rf dist", 7 | "reinstall": "npm run clean && rm -rf node_modules && npm install", 8 | "watch": "webpack --watch", 9 | "put": "npm run build && ./scripts/put", 10 | "put:prod": "npm run build:prod && ./scripts/put" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.23.0", 14 | "@babel/preset-env": "^7.22.20", 15 | "@babel/preset-react": "^7.23.3", 16 | "@webpack-cli/generators": "^3.0.2", 17 | "babel-loader": "^9.1.3", 18 | "html-webpack-plugin": "^5.5.3", 19 | "css-loader": "^6.10.0", 20 | "style-loader": "^3.3.4", 21 | "webpack": "^5.82.0", 22 | "webpack-cli": "^5.1.0", 23 | "@testing-library/jest-dom": "^6.4.2", 24 | "@testing-library/react": "^14.2.2" 25 | }, 26 | "engines": { 27 | "node": "<=14.17.6" 28 | }, 29 | "dependencies": { 30 | "node": "^14.17.6", 31 | "nvm": "^0.39.1" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/node-simple-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-simple-server-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest --config jest.config.js src/", 8 | "build:prod": "webpack --mode=production --node-env=production", 9 | "build": "webpack --mode=development", 10 | "format:check": "prettier . --check --config ../.prettierrc.js --cache --cache-location=../prettiercache", 11 | "format": "prettier . --write --config ../.prettierrc.js --cache --cache-location=../prettiercache && yarn lint --fix", 12 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc src/**/*.{js,jsx}", 13 | "publish-package": "npm publish --access public" 14 | }, 15 | "repository": { 16 | "url": "https://github.com/brightsign/dev-cookbook.git", 17 | "type": "git", 18 | "directory": "node-simple-server-example/" 19 | }, 20 | "publishConfig": { 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "keywords": [], 24 | "author": "", 25 | "license": "ISC", 26 | "devDependencies": { 27 | "webpack": "^5.94.0", 28 | "webpack-cli": "^5.1.0" 29 | }, 30 | "dependencies": { 31 | "http": "^0.0.1-security", 32 | "node": "14.17.6" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/scripts/put: -------------------------------------------------------------------------------- 1 | # List of required and optional environment variables 2 | : ' 3 | 4 | required_vars=("PLAYER") 5 | optional_vars=("PLAYER_PW") 6 | 7 | # Iterate over the required variables 8 | for var in "${required_vars[@]}"; do 9 | if [[ -z "${!var}" ]]; then 10 | echo "ERROR: Environment variable $var is not set." 11 | exit 1 12 | fi 13 | done 14 | 15 | # Iterate over the required variables 16 | for var in "${optional_vars[@]}"; do 17 | if [[ -z "${!var}" ]]; then 18 | echo "INFO: Environment variable $var is not set. Consider whether $var needs to be set." 19 | fi 20 | done 21 | 22 | ' 23 | 24 | echo "copying files to $PLAYER" 25 | 26 | # add digest auth when DWS password is set 27 | if [ -z $PLAYER_PW ]; then 28 | AUTH="" 29 | else 30 | AUTH="--digest --user admin:$PLAYER_PW" 31 | fi 32 | echo "AUTH: $AUTH" 33 | 34 | # fi 35 | for f in src/autorun.brs 36 | do 37 | echo $f 38 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd" --form "=@"$f"" | jq -r .data 39 | done 40 | 41 | for f in dist/* 42 | do 43 | echo $f 44 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd/dist" --form "=@"$f"" | jq -r .data 45 | done 46 | 47 | # reboot the player to restart the application 48 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/control/reboot/" | jq -r .data 49 | -------------------------------------------------------------------------------- /examples/cec-interface/brightscript/README.md: -------------------------------------------------------------------------------- 1 | # BrightScript CEC Example 2 | 3 | This example demonstrates how to send CEC (Consumer Electronics Control) commands using BrightScript on BrightSign devices. 4 | 5 | ## What it does 6 | - Sends an "Image View On" (display on) CEC command 7 | - Waits 15 seconds 8 | - Sends a "Standby" (display off) CEC command 9 | 10 | ## How to use 11 | 1. Copy the `autorun.brs` and `index.html` files to the root of the SD card of your BrightSign player. 12 | 2. Reboot the player. 13 | 3. The script will automatically run and send the CEC commands as described. 14 | 15 | ## Troubleshooting 16 | 17 | A common error that you may see when using CEC is the 133 error which indicates "destination address not acknowledged" or in plainer English "the device you are trying to talk to is not listening": 18 | ``` 19 | { 238.703} [INFO] [source file:///sd:/index.html:52]: Error while sending CEC command: {"error":133} 20 | ``` 21 | 22 | To resolve this: 23 | - Ensure that your BrightSign device is connected to a CEC-compatible display via HDMI. 24 | - Make sure that CEC is enabled on your display device. 25 | - Try replacing the HDMI cable if CEC commands are still not being received. 26 | - Try running your code on a different display device to rule out compatibility issues. 27 | 28 | ## Reference 29 | - [BrightSign roCecInterface documentation](https://docs.brightsign.biz/developers/rocecinterface) 30 | -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/info.js: -------------------------------------------------------------------------------- 1 | import os from "os"; 2 | import { bsPlayer } from "./player"; 3 | 4 | const displayCurrentNetwork = () => { 5 | const networkInterfaces = os.networkInterfaces() || {}; 6 | let ipAddress = ""; 7 | 8 | Object.keys(networkInterfaces).forEach((interfaceName) => { 9 | networkInterfaces[interfaceName]?.forEach((interfaceInfo) => { 10 | if (interfaceInfo.family === "IPv4") { 11 | ipAddress += `${interfaceName}: ${interfaceInfo.address} `; 12 | console.log( 13 | `Network Interface - ${interfaceName}: ${interfaceInfo.address}` 14 | ); 15 | } 16 | }); 17 | }); 18 | 19 | document.querySelector("#ipAddress .value").textContent = ipAddress; 20 | }; 21 | 22 | const displayDeviceInfo = async () => { 23 | const player = await bsPlayer(); 24 | const deviceInfo = player.BSDeviceInfo; 25 | const { model, osVersion, serialNumber } = deviceInfo; 26 | 27 | document.querySelector("#model .value").textContent = model; 28 | document.querySelector("#osVersion .value").textContent = osVersion; 29 | document.querySelector("#serialNumber .value").textContent = serialNumber; 30 | 31 | console.log( 32 | `DeviceInfo - Model: ${model}, OS Version: ${osVersion}, Serial Number: ${serialNumber}` 33 | ); 34 | }; 35 | 36 | export { displayCurrentNetwork, displayDeviceInfo }; 37 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | install_and_test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 1 12 | - name: Fetch latest main branch commit 13 | run: git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: '14.17.6' 18 | cache: 'yarn' 19 | - run: yarn --frozen-lockfile 20 | - run: node ./scripts/workspace_actions.js test 21 | 22 | format_all: 23 | runs-on: ubuntu-latest 24 | needs: install_and_test 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 1 29 | - name: Fetch latest main branch commit 30 | run: git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main 31 | - name: Use Node.js 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: '14.17.6' 35 | cache: 'yarn' 36 | - run: yarn --frozen-lockfile 37 | - run: node ./scripts/workspace_actions.js format 38 | - name: Commit formatting changes 39 | uses: stefanzweifel/git-auto-commit-action@v5 40 | with: 41 | commit_message: Applied automatic formatting changes 42 | branch: ${{ github.head_ref }} -------------------------------------------------------------------------------- /examples/enable-ldws/javascript/index.js: -------------------------------------------------------------------------------- 1 | const DWSConfiguration = require("@brightsign/dwsconfiguration"); 2 | const systemClass = require("@brightsign/system"); 3 | const registryClass = require("@brightsign/registry"); 4 | 5 | async function configureLDWS() { 6 | console.log("Enabling LDWS..."); 7 | const system = new systemClass(); 8 | const registry = new registryClass(); 9 | 10 | // Create DWS configuration instance 11 | const dwsConfig = new DWSConfiguration(); 12 | const config = { 13 | port: 80, // HTTP port for web interface 14 | password: { 15 | value: "your_password_here", 16 | obfuscated: false // Password stored as plain text 17 | }, 18 | authenticationList: ["digest"] // Use digest HTTP authentication 19 | }; 20 | 21 | try { 22 | const httpServerPort = await registry.read("networking", "http_server"); 23 | // Check if LDWS is already enabled 24 | if (httpServerPort !== "" && httpServerPort !== "0") { 25 | // LDWS is already enabled 26 | console.log(`HTTP server port already set to ${httpServerPort}. LDWS may already be enabled.`); 27 | return; 28 | } 29 | 30 | // Apply LDWS configuration to device 31 | dwsConfig.applyConfig(config); 32 | console.log("LDWS enabled!"); 33 | 34 | // Reboot device to apply changes 35 | console.log("Rebooting device to apply changes..."); 36 | system.reboot(); 37 | } catch (error) { 38 | console.error("Configuration failed:", error.message); 39 | } 40 | } 41 | 42 | configureLDWS(); -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "scripts": { 4 | "build": "webpack --mode=development", 5 | "build:prod": "webpack --mode=production --node-env=production", 6 | "clean": "rm -rf dist", 7 | "reinstall": "npm run clean && rm -rf node_modules && npm install", 8 | "watch": "webpack --watch", 9 | "put": "npm run build && ./scripts/put", 10 | "put:prod": "npm run build:prod && ./scripts/put", 11 | "put:watch": "nodemon -e js,brs,css --watch src --watch package.json --exec 'npm put:prod'" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.23.0", 15 | "@babel/preset-env": "^7.22.20", 16 | "@babel/preset-react": "^7.23.3", 17 | "@webpack-cli/generators": "^3.0.2", 18 | "babel-loader": "^9.1.3", 19 | "html-webpack-plugin": "^5.5.3", 20 | "webpack": "^5.82.0", 21 | "webpack-cli": "^5.1.0", 22 | "nodemon": "^3.0.3", 23 | "css-loader": "^6.10.0", 24 | "style-loader": "^3.3.4", 25 | "@testing-library/jest-dom": "^6.4.2", 26 | "@testing-library/react": "^14.2.2" 27 | }, 28 | "engines": { 29 | "node": "<=14.17.6" 30 | }, 31 | "dependencies": { 32 | "node": "^14.17.6", 33 | "nvm": "^0.39.1" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/send-plugin-message/pluginMessageApp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Html Messages 7 | 8 | 9 | 10 | 11 | 12 | 43 | 44 | -------------------------------------------------------------------------------- /examples/cec-interface/javascript/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript CEC Example 2 | 3 | This example demonstrates how to send and receive CEC (Consumer Electronics Control) commands using JavaScript on BrightSign devices. 4 | 5 | ## What it does 6 | - Uses the `@brightsign/cec` package to send CEC commands 7 | - Listens for CEC receive events 8 | - Sends an "Image View On" (display on) command, waits 15 seconds, then sends a "Standby" (display off) command 9 | 10 | ## How to use 11 | 1. Copy the `autorun.brs`, `index.html` and `index.js` files to the root of the SD card of your BrightSign player. 12 | 2. Reboot the player. 13 | 3. The script will automatically run and send the CEC commands as described. 14 | 15 | ## Troubleshooting 16 | 17 | A common error that you may see when using CEC is the 133 error which indicates "destination address not acknowledged" or in plainer English "the device you are trying to talk to is not listening": 18 | ``` 19 | { 238.703} [INFO] [source file:///sd:/index.html:52]: Error while sending CEC command: {"error":133} 20 | ``` 21 | 22 | To resolve this: 23 | - Ensure that your BrightSign device is connected to a CEC-compatible display via HDMI. 24 | - Make sure that CEC is enabled on your display device. 25 | - Try replacing the HDMI cable if CEC commands are still not being received. 26 | - Try running your code on a different display device to rule out compatibility issues. 27 | 28 | ## Reference 29 | - [BrightSign @brightsign/cec documentation](https://docs.brightsign.biz/developers/cec) 30 | -------------------------------------------------------------------------------- /examples/htmlwidget-iframes/README.md: -------------------------------------------------------------------------------- 1 | # HTML Widget Iframes Example 2 | 3 | To enhance security, the BrightSign OS has disabled the access to JavaScript APIs, BS-JS objects, and Node.js within iframes in BOS v9.1. However, a security parameter `trusted_iframes_enabled` has been added to the `roHtmlWidget` configuration, which will enable iframes to have access to that functionality. We DO NOT recommend this configuration as the content in these iframes can gain access to core player APIs, and this content is not always under the application's control. 4 | 5 | See the following pages for details: 6 | 7 | - `trusted_iframes_enabled` in the [roHtmlWidget documentation](https://docs.brightsign.biz/developers/rohtmlwidget#23vJS) 8 | - `trustedIframesEnabled` in the [JavaScript htmlwidget documentation](https://docs.brightsign.biz/developers/htmlwidget) 9 | 10 | ## Introduction 11 | 12 | This example demonstrates how to use the `roHtmlWidget` component on a BrightSign player to display a fullscreen web page with support for trusted iframes and enhanced security options (available in OS `v9.1.75.3` and later). 13 | 14 | ## How to Run on a Player 15 | 16 | 1. Copy the `autorun.brs` file to the root of an SD card. 17 | 2. Insert the SD card into your BrightSign player. 18 | 3. Power on the player. The player will automatically run the `autorun.brs` file and display the configured web page. 19 | 20 | ## File Structure 21 | 22 | - `autorun.brs`: BrightScript file that initializes the HTML widget with iframe and security options. 23 | -------------------------------------------------------------------------------- /examples/bluetooth-scan/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | ' This interface is the preferred way for JavaScript content to communicate with its parent application. 3 | ' https://brightsign.atlassian.net/wiki/x/-gAeG 4 | mp = CreateObject("roMessagePort") 5 | 6 | ' Create HTML Widget which is defined below in its own function 7 | widget = CreateHTMLWidget(mp) 8 | widget.Show() 9 | 10 | ' Event Loop to view the events that are being sent from the HTML content. 11 | ' The roHtmlWidgetEvent object is sent to the message port when an event occurs in the HTML content. 12 | while true 13 | msg = wait(0,mp) 14 | print "msg received - type=";type(msg) 15 | if type(msg) = "roHtmlWidgetEvent" then 16 | print "msg: ";msg 17 | end if 18 | end while 19 | end function 20 | 21 | function CreateHTMLWidget(mp as object) as object 22 | ' Get Screen Resolution 23 | ' https://brightsign.atlassian.net/wiki/x/SQUYFg 24 | vidmode = CreateObject("roVideoMode") 25 | width = vidmode.GetResX() 26 | height = vidmode.GetResY() 27 | 28 | ' https://brightsign.atlassian.net/wiki/x/HwUYFg 29 | r = CreateObject("roRectangle",0,0,width,height) 30 | 31 | ' Create HTML Widget config 32 | ' https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget#Initialization-Parameters 33 | config = { 34 | nodejs_enabled: true 35 | url: "file:///sd:/index.html" 36 | port: mp 37 | } 38 | 39 | ' Create HTML Widget 40 | ' https://brightsign.atlassian.net/wiki/x/AAUYFg 41 | h = CreateObject("roHtmlWidget",r,config) 42 | return h 43 | end function -------------------------------------------------------------------------------- /templates/html5-app-template/src/info.ts: -------------------------------------------------------------------------------- 1 | import os from "os"; 2 | 3 | import { DeviceInfo } from "./device-info"; 4 | import { bsPlayer, BrightSignPlayer } from "./player"; 5 | 6 | const displayCurrentNetwork = () => { 7 | const networkInterfaces = os.networkInterfaces() || {}; 8 | let ipAddress = ""; 9 | 10 | Object.keys(networkInterfaces).forEach((interfaceName) => { 11 | networkInterfaces[interfaceName]?.forEach((interfaceInfo) => { 12 | if (interfaceInfo.family === "IPv4") { 13 | ipAddress += `${interfaceName}: ${interfaceInfo.address} `; 14 | console.log( 15 | `Network Interface - ${interfaceName}: ${interfaceInfo.address}` 16 | ); 17 | } 18 | }); 19 | }); 20 | 21 | document.querySelector("#ipAddress .value")!.textContent = ipAddress; 22 | }; 23 | 24 | const displayDeviceInfo = async () => { 25 | const player: BrightSignPlayer = await bsPlayer(); 26 | const deviceInfo: DeviceInfo = player.BSDeviceInfo; 27 | const { model, osVersion, serialNumber } = deviceInfo; 28 | 29 | document.querySelector("#model .value")!.textContent = model; 30 | document.querySelector("#osVersion .value")!.textContent = osVersion; 31 | document.querySelector("#serialNumber .value")!.textContent = serialNumber; 32 | 33 | console.log( 34 | `DeviceInfo - Model: ${model}, OS Version: ${osVersion}, Serial Number: ${serialNumber}` 35 | ); 36 | }; 37 | 38 | export { displayCurrentNetwork, displayDeviceInfo }; 39 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/scripts/put: -------------------------------------------------------------------------------- 1 | # List of required and optional environment variables 2 | : ' 3 | 4 | required_vars=("PLAYER") 5 | optional_vars=("PLAYER_PW") 6 | 7 | # Iterate over the required variables 8 | for var in "${required_vars[@]}"; do 9 | if [[ -z "${!var}" ]]; then 10 | echo "ERROR: Environment variable $var is not set." 11 | exit 1 12 | fi 13 | done 14 | 15 | # Iterate over the required variables 16 | for var in "${optional_vars[@]}"; do 17 | if [[ -z "${!var}" ]]; then 18 | echo "INFO: Environment variable $var is not set. Consider whether $var needs to be set." 19 | fi 20 | done 21 | 22 | ' 23 | 24 | echo "copying files to $PLAYER" 25 | 26 | # add digest auth when DWS password is set 27 | if [ -z $PLAYER_PW ]; then 28 | AUTH="" 29 | else 30 | AUTH="--digest --user admin:$PLAYER_PW" 31 | fi 32 | echo "AUTH: $AUTH" 33 | 34 | # fi 35 | for f in src/autorun.brs 36 | do 37 | echo $f 38 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd" --form "=@"$f"" | jq -r .data 39 | done 40 | 41 | for f in dist/* 42 | do 43 | echo $f 44 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd/dist" --form "=@"$f"" | jq -r .data 45 | done 46 | 47 | for f in public/* 48 | do 49 | echo $f 50 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd/dist" --form "=@"$f"" | jq -r .data 51 | done 52 | 53 | 54 | # reboot the player to restart the application 55 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/control/reboot/" | jq -r .data 56 | -------------------------------------------------------------------------------- /examples/indexeddb-caching/autorun.brs: -------------------------------------------------------------------------------- 1 | Sub Main() 2 | width = 1920 3 | height = 1080 4 | 5 | vidmode = CreateObject("roVideoMode") 6 | if type(vidmode) = "roVideoMode" 7 | width = vidmode.GetResX() 8 | height = vidmode.GetResY() 9 | end if 10 | 11 | msgPort = CreateObject("roMessagePort") 12 | rect = CreateObject("roRectangle", 0, 0, width, height) 13 | 14 | 'inspector disabled currently 15 | 'inspectorServer = { 16 | ' port: 3010 17 | '} 18 | 19 | config = { 20 | url: "file:/SD:/index.html", ' you can use a server or website URL here - "https://example.com/index.html" 21 | mouse_enabled: false, ' set to true to enable mouse/keyboard 22 | 'inspector_server: inspectorServer ' uncomment to enable inspector server 23 | security_params: { websecurity: true } 24 | javascript_enabled: true, ' Else set to false to disable JavaScript 25 | nodejs_enabled: true, ' Else set to false to disable Node.js 26 | storage_path: "/cache", ' Name of the directory for local storage cache 27 | storage_quota: "2147483648", ' 2GB 28 | port: msgPort, 29 | } 30 | 31 | html = CreateObject("roHtmlWidget", rect, config) 32 | html.Show() 33 | 34 | syslog = CreateObject("roSystemLog") 35 | 36 | while true 37 | event = wait(0, msgPort) 38 | syslog.sendline("@@ type(event)="+ type(event)) 39 | end while 40 | End Sub 41 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/App.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | margin: 0; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | font-family: Arial, sans-serif; 9 | background: rgb(16, 9, 34); 10 | background: linear-gradient( 11 | 90deg, 12 | rgba(48, 27, 105, 1) 0%, 13 | rgba(16, 9, 34, 1) 38% 14 | ); 15 | color: white; 16 | padding: 16px; 17 | } 18 | 19 | h1 { 20 | font-size: 48px; 21 | text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); 22 | } 23 | 24 | #info { 25 | margin-top: 32px; 26 | display: flex; 27 | flex-direction: column; 28 | gap: 16px; 29 | font-size: 32px; 30 | } 31 | 32 | .label { 33 | font-weight: bold; 34 | margin-right: 8px; 35 | } 36 | 37 | .container { 38 | display: flex; 39 | padding: 16px; 40 | margin-left: 50%; 41 | margin-right: 5%; 42 | height: 100vh; 43 | } 44 | 45 | .column { 46 | width: 100%; 47 | margin: 10px; 48 | padding: 15px; 49 | display: flex; 50 | flex-direction: column; 51 | } 52 | 53 | .row { 54 | background-color: rgba(48, 27, 105, 0.5); 55 | border-radius: 15px; 56 | display: flex; 57 | justify-content: flex-start; 58 | align-items: center; 59 | width: 100%; 60 | height: max-content; 61 | margin: 10px; 62 | } 63 | 64 | .header { 65 | font-weight: bold; 66 | } 67 | 68 | .column img { 69 | height: auto; 70 | margin-right: 10px; 71 | } 72 | 73 | .background-img { 74 | position: absolute; 75 | top: 25%; 76 | left: 20%; 77 | z-index: -1; 78 | } 79 | -------------------------------------------------------------------------------- /templates/html5-app-template/src/player.ts: -------------------------------------------------------------------------------- 1 | import { getConfig } from "./config"; 2 | 3 | const config = getConfig(); 4 | 5 | const initializePlayer = async () => { 6 | console.log("initializePlayer()"); 7 | 8 | return { 9 | // @ts-expect-error: Should accept import 10 | DeviceInfo: (await import("@brightsign/deviceinfo")).default, 11 | // @ts-expect-error: Should accept import 12 | RegistryClass: (await import("@brightsign/registry")).default, 13 | }; 14 | }; 15 | 16 | const initializeDesktop = async () => { 17 | console.log("initializeDesktop()"); 18 | 19 | return { 20 | DeviceInfo: (await import("./device-info")).default, 21 | mockPlayer: (await import("./__mocks__/@brightsign/raptor")).info, 22 | }; 23 | }; 24 | 25 | export interface BrightSignPlayer { 26 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 27 | BSDeviceInfo: any; 28 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 29 | registry?: any; 30 | } 31 | 32 | export async function bsPlayer() { 33 | const player = {}; 34 | 35 | if (config.isDesktop) { 36 | const playerModule = await initializeDesktop(); 37 | player.BSDeviceInfo = new playerModule.DeviceInfo( 38 | playerModule.mockPlayer 39 | ); 40 | } else { 41 | const playerModule = await initializePlayer(); 42 | player.BSDeviceInfo = new playerModule.DeviceInfo(); 43 | player.registry = new playerModule.RegistryClass(); 44 | } 45 | return player; 46 | } 47 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | BrightSign Test App 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | BrightSign Test App 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 📝 Description 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | **Issue**: [Url to Github Issue](https://github.com/brightsign/dev-cookbook/issues/-1) 10 | 11 | ## 📋 List of Changes 12 | 13 | 14 | 15 | - Please provide a concise list of the main changes. 16 | 17 | ## 🧪 Steps to Test 18 | 19 | 20 | 1. Step 1 21 | 22 | ## Notes to the Reviewer 23 | 24 | 25 | 26 | ## 📸 Screenshots 27 | 28 | 29 | 30 | 31 | ## ✔️ Dev Complete Checklist 32 | 33 | - [ ] PR template filled out 34 | - [ ] Change is tested by submitter 35 | - [ ] PR follows all linting and coding standards 36 | - [ ] Github Issue exists (if applicable) 37 | - [ ] Team member has been assigned 38 | - [ ] At least one commit message is in [Conventional Commit](https://www.conventionalcommits.org/) format 39 | 40 | -------------------------------------------------------------------------------- /examples/cec-interface/javascript/index.js: -------------------------------------------------------------------------------- 1 | const CecClass = require('@brightsign/cec'); 2 | const cec1 = new CecClass('HDMI-1'); // Modify this to be 'HDMI-1', 'HDMI-2', 'HDMI-3', or 'HDMI-4' as needed 3 | 4 | cec1.addEventListener("receive", function(packet) { 5 | const frame = packet.data; 6 | console.log("frame from HDMI-1: " + toHexString(frame)); 7 | }); 8 | 9 | const toHexString = (cecBytes) => { 10 | return Array.from(cecBytes) 11 | .map((byte) => byte.toString(16).padStart(2, '0')) 12 | .join(' '); 13 | }; 14 | 15 | async function sendCecCommand(buffer) { 16 | try { 17 | console.log("Sending CEC command ..." + toHexString(buffer)); 18 | await cec1.send(buffer); 19 | } catch (error) { 20 | console.log("Error while sending CEC command: " + JSON.stringify(error)); 21 | } 22 | } 23 | 24 | function main() { 25 | // * Developer Note: Varying display manufacturers require direct addressing. 26 | // * There may be settings to configure direct address or not. 27 | 28 | // Turn display "on" - image's view to on 29 | const bufferImageViewOn = new Uint8Array(2); 30 | bufferImageViewOn[0] = 0x4f; 31 | bufferImageViewOn[1] = 0x0D; 32 | 33 | // Turn display "off". Some displays will go to standby mode. 34 | const bufferStandby = new Uint8Array(2); 35 | bufferStandby[0] = 0x4f; 36 | bufferStandby[1] = 0x36; 37 | 38 | sendCecCommand(Array.from(bufferImageViewOn)); 39 | 40 | // Wait 15 seconds, then turn display off 41 | setTimeout(() => { 42 | sendCecCommand(Array.from(bufferStandby)); 43 | 44 | // Uncomment below to repeat the cycle every 15 seconds 45 | // setTimeout(main, 15000); 46 | }, 15000); 47 | } 48 | 49 | window.main = main; -------------------------------------------------------------------------------- /examples/html-starter/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | ' Create directory to store crash-dumps (optional) 3 | dir = CreateDirectory("SD:/brightsign-dumps") 4 | if not dir then 5 | print "Could not create directory" 6 | end if 7 | 8 | ' This interface is the preferred way for JavaScript content to communicate with its parent application. 9 | ' https://brightsign.atlassian.net/wiki/x/-gAeG 10 | mp = CreateObject("roMessagePort") 11 | 12 | ' Create HTML Widget which is defined below in its own function 13 | widget = CreateHTMLWidget(mp) 14 | widget.Show() 15 | 16 | ' Event Loop to view the events that are being sent from the HTML content. 17 | ' The roHtmlWidgetEvent object is sent to the message port when an event occurs in the HTML content. 18 | while true 19 | msg = wait(0,mp) 20 | print "msg received - type=";type(msg) 21 | if type(msg) = "roHtmlWidgetEvent" then 22 | print "msg: ";msg 23 | end if 24 | end while 25 | end function 26 | 27 | function CreateHTMLWidget(mp as object) as object 28 | ' Get Screen Resolution 29 | ' https://brightsign.atlassian.net/wiki/x/SQUYFg 30 | vidmode = CreateObject("roVideoMode") 31 | width = vidmode.GetResX() 32 | height = vidmode.GetResY() 33 | ' https://brightsign.atlassian.net/wiki/x/HwUYFg 34 | r = CreateObject("roRectangle",0,0,width,height) 35 | 36 | ' Create HTML Widget config 37 | ' https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget#Initialization-Parameters 38 | config = { 39 | nodejs_enabled: true 40 | url: "file:///sd:/index.html" 41 | port: mp 42 | } 43 | 44 | ' Create HTML Widget 45 | ' https://brightsign.atlassian.net/wiki/x/AAUYFg 46 | h = CreateObject("roHtmlWidget",r,config) 47 | return h 48 | end function -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | const isProduction = process.env.NODE_ENV === "production"; 5 | 6 | module.exports = { 7 | entry: { 8 | frontend: "./src/index", 9 | backend: "./src/server/index", 10 | }, 11 | target: "node", 12 | output: { 13 | filename: "[name].js", 14 | path: path.resolve(__dirname, "dist"), 15 | }, 16 | mode: isProduction ? "production" : "development", 17 | plugins: [ 18 | new HtmlWebpackPlugin({ 19 | template: "./public/index.html", 20 | }), 21 | ], 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(js|jsx)$/i, 26 | exclude: /node_modules/, 27 | use: { 28 | loader: "babel-loader", 29 | options: { 30 | presets: ["@babel/preset-react"], 31 | }, 32 | }, 33 | }, 34 | { 35 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, 36 | type: "asset/resource", 37 | }, 38 | { 39 | test: /\.css$/i, 40 | use: ["style-loader", "css-loader"], 41 | }, 42 | ], 43 | }, 44 | resolve: { 45 | extensions: [".js", ".jsx"], 46 | }, 47 | externals: ({ request }, callback) => { 48 | if (/^@brightsign\//.test(request)) { 49 | return callback(null, "commonjs " + request); 50 | } 51 | callback(); 52 | }, 53 | devtool: isProduction ? "source-map" : "eval-source-map", 54 | }; 55 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | const isProduction = process.env.NODE_ENV === "production"; 5 | 6 | module.exports = { 7 | entry: { 8 | frontend: "./src/index", 9 | backend: "./src/server/index", 10 | }, 11 | target: "node", 12 | output: { 13 | filename: "[name].js", 14 | path: path.resolve(__dirname, "dist"), 15 | }, 16 | mode: isProduction ? "production" : "development", 17 | plugins: [ 18 | new HtmlWebpackPlugin({ 19 | template: "./public/index.html", 20 | }), 21 | ], 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(js|jsx)$/i, 26 | exclude: /node_modules/, 27 | use: { 28 | loader: "babel-loader", 29 | options: { 30 | presets: ["@babel/preset-react"], 31 | }, 32 | }, 33 | }, 34 | { 35 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, 36 | type: "asset/resource", 37 | }, 38 | { 39 | test: /\.css$/i, 40 | use: ["style-loader", "css-loader"], 41 | }, 42 | ], 43 | }, 44 | resolve: { 45 | extensions: [".js", ".jsx"], 46 | }, 47 | externals: ({ request }, callback) => { 48 | if (/^@brightsign\//.test(request)) { 49 | return callback(null, "commonjs " + request); 50 | } 51 | callback(); 52 | }, 53 | devtool: isProduction ? "source-map" : "eval-source-map", 54 | }; 55 | -------------------------------------------------------------------------------- /examples/html-starter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Simple Example HTML App 8 | 28 | 29 | 30 | 31 | 32 |
33 | device 34 | 35 |

Congratulations on setting up a simple html application with your BrightSign!

36 | device 37 |
38 | 39 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/provisioning-server/content/autorun.brs: -------------------------------------------------------------------------------- 1 | ' BrightSign Example Autorun Script 2 | ' The main content autorun script, this is the script which runs on BrightSign startup after provisioning is complete. 3 | function main() 4 | ' Create directory to store crash-dumps (optional) 5 | dir = CreateDirectory("SD:/brightsign-dumps") 6 | if not dir then 7 | print "Could not create directory" 8 | end if 9 | 10 | ' This interface is the preferred way for JavaScript content to communicate with its parent application. 11 | ' https://docs.brightsign.biz/developers/romessageport 12 | mp = CreateObject("roMessagePort") 13 | 14 | ' Create HTML Widget which is defined below in its own function 15 | widget = CreateHTMLWidget(mp) 16 | widget.Show() 17 | 18 | ' Event Loop to view the events that are being sent from the HTML content. 19 | ' The roHtmlWidgetEvent object is sent to the message port when an event occurs in the HTML content. 20 | while true 21 | msg = wait(0,mp) 22 | print "msg received - type=";type(msg) 23 | if type(msg) = "roHtmlWidgetEvent" then 24 | print "msg: ";msg 25 | end if 26 | end while 27 | end function 28 | 29 | function CreateHTMLWidget(mp as object) as object 30 | ' Get Screen Resolution 31 | ' https://docs.brightsign.biz/developers/rovideomode 32 | vidmode = CreateObject("roVideoMode") 33 | width = vidmode.GetResX() 34 | height = vidmode.GetResY() 35 | ' https://docs.brightsign.biz/developers/rorectangle 36 | r = CreateObject("roRectangle",0,0,width,height) 37 | 38 | config = { 39 | nodejs_enabled: true 40 | url: "file:///sd:/index.html" 41 | port: mp 42 | } 43 | 44 | ' Create HTML Widget 45 | ' https://docs.brightsign.biz/developers/rohtmlwidget 46 | h = CreateObject("roHtmlWidget",r,config) 47 | return h 48 | end function 49 | -------------------------------------------------------------------------------- /examples/send-plugin-message/README.md: -------------------------------------------------------------------------------- 1 | # How to send a plugin message to your html application 2 | This example demonstrates how to set up a presentation that sends plugin messages to your HTML application. Once received, these messages can be forwarded to your backend for processing. 3 | 4 | The setup consists of three components: 5 | 6 | 1. **Presentation File (`plugin-message-transfer.bpfx`)**: This file supports sending plugin messages from the included widgets. 7 | 2. **Script File (`pluginMessageTransfer.brs`)**: Sends and receives the plugin messages between the BrightAuthor:connected Presentation. 8 | 3. **HTML Application (`pluginMessageApp.html`)**: Receives the messages and handles further processing. 9 | 10 | The plugin message format enforced in the `pluginMessageTransfer.brs` file is: `pluginMessage!!!!`. You can modify this format to suit the information you need to communicate from your presentations to your backend. In this case, the plugin messages are triggered by the TimeoutEvents attached to each example video in the presentation. The messages include the variable serial number of the player and the text that indicates if the 1stVideo or 2ndVideo executed. 11 | 12 | ## How to run and edit the example 13 | The `bpfx` presentation file can be opened in BrightAuthor:connected by clicking the dropdown next to "Presentations" and then selecting "Open..." 14 | Choose this `bpfx` file and if it asks for the location of the `html` and `brs` files, point it to the folder in which they are contained. 15 | 16 | Once open, the widgets can individually be selected and the "advanced" tab on the right side can be opened to view related plugin messages. 17 | 18 | Widgets and plugin messages can be altered in the presentation. Once complete, the presentation should be saved and published to your player. -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | 3 | mp = CreateObject("roMessagePort") 4 | 'Enable lDWS 5 | EnableLDWS() 6 | ' Enable SSH 7 | EnableSSH() 8 | ' Create HTML Widget 9 | widget = CreateHTMLWidget(mp) 10 | widget.Show() 11 | 12 | 'Event Loop 13 | while true 14 | msg = wait(0,mp) 15 | print "msg received - type=";type(msg) 16 | 17 | if type(msg) = "roHtmlWidgetEvent" then 18 | print "msg: ";msg 19 | end if 20 | end while 21 | 22 | end function 23 | 24 | function CreateHTMLWidget(mp as object) as object 25 | ' Enable Web Inspector 26 | reg = CreateObject("roRegistrySection","html") 27 | reg.Write("enable_web_inspector","1") 28 | reg.Flush() 29 | 30 | ' Get Screen Resolution 31 | vidmode = CreateObject("roVideoMode") 32 | width = vidmode.GetResX() 33 | height = vidmode.GetResY() 34 | 35 | r = CreateObject("roRectangle",0,0,width,height) 36 | 37 | ' Create HTML Widget config 38 | config = { 39 | nodejs_enabled: true 40 | inspector_server: { 41 | port: 3000 42 | } 43 | url: "file:///sd:/dist/index.html" 44 | port: mp 45 | } 46 | 47 | ' Create HTML Widget 48 | h = CreateObject("roHtmlWidget",r,config) 49 | return h 50 | 51 | end function 52 | 53 | function EnableLDWS() 54 | 55 | registrySection = CreateObject("roRegistrySection", "networking") 56 | 57 | if type(registrySection) = "roRegistrySection" then 58 | 59 | registrySection.Write("http_server", "80") 60 | 61 | end if 62 | 63 | registrySection.Flush() 64 | 65 | end function 66 | 67 | function EnableSSH() 68 | 69 | regSSH = CreateObject("roRegistrySection", "networking") 70 | 71 | if type(regSSH) = "roRegistrySection" then 72 | 73 | regSSH.Write("ssh","22") 74 | 75 | endif 76 | 77 | n = CreateObject("roNetworkConfiguration", 0) 78 | n.SetLoginPassword("password") 79 | n.Apply() 80 | 81 | regSSH.Flush() 82 | 83 | end function -------------------------------------------------------------------------------- /templates/html5-app-template/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | html5-app-template 7 | 43 | 44 | 45 | 46 |

BrightSign Web App

47 |
48 |
49 | IP Address: 50 | loading... 51 |
52 |
53 | Model: 54 | loading... 55 |
56 |
57 | OS Version: 58 | loading... 59 |
60 |
61 | Serial Number: 62 | loading... 63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /templates/html5-app-template/src-js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | html5-app-template 7 | 43 | 44 | 45 | 46 |

BrightSign Web App

47 |
48 |
49 | IP Address: 50 | loading... 51 |
52 |
53 | Model: 54 | loading... 55 |
56 |
57 | OS Version: 58 | loading... 59 |
60 |
61 | Serial Number: 62 | loading... 63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/local-storage/README.md: -------------------------------------------------------------------------------- 1 | # Local Storage Image Slideshow Example 2 | 3 | ## Introduction 4 | 5 | This example demonstrates how to create an image slideshow application that caches images in the browser's localStorage and displays them in sequence. The application downloads and stores images persistently for smooth playback and implements a looping slideshow that automatically advances to the next image after a specified duration. 6 | 7 | ## How it Works 8 | 9 | 1. **Initial Load**: The application checks localStorage for cached images and displays the first image (cached or streamed) 10 | 2. **Persistent Caching**: Images are downloaded, converted to base64, and stored in browser localStorage 11 | 3. **Background Caching**: While the first image is displayed, other images are downloaded and cached in the background 12 | 4. **Smooth Transitions**: When an image's display timer expires, the next cached image loads instantly from localStorage 13 | 5. **Session Persistence**: Cached images persist across browser sessions and page reloads 14 | 6. **Loop Behavior**: After the last image, the slideshow loops back to the first image 15 | 16 | **Note**: Browser localStorage has a storage limit of approximately 5MB. For caching larger amounts of content, consider using IndexedDB which offers much larger storage capacity. Check out our [IndexedDB caching example](https://github.com/brightsign/dev-cookbook/tree/main/examples/indexeddb-caching-example) for an alternative approach. 17 | 18 | ## How to Run on a Player 19 | 20 | 1. Copy the `autorun.brs` and `index.html` files to the root of an SD card 21 | 2. Insert the SD card into your BrightSign player 22 | 3. Power on the player - it will automatically run the `autorun.brs` file which then loads the HTML/JS application 23 | 24 | ## Files Structure 25 | 26 | - autorun.brs: BrightScript file that initializes the HTML widget 27 | - index.html: Main HTML/JS application with localStorage image caching 28 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/README.md: -------------------------------------------------------------------------------- 1 | ## Using the template 2 | 3 | Ensure that you have the right version of Node.js installed (`14.17.6`) as this is what is best supported by BrightSign devices at the moment. 4 | 5 | This project is a custom `create-react-app` template. Clone the repo and then run the following command in the root of your project directory to instantiate the project. Be sure to set the `file:/` path to the location where `/template` and `/template.json` reside. 6 | 7 | ``` 8 | npx create-react-app bs-app --template file:/path/to/dev-cookbook/cra-template-brightsign-app 9 | ``` 10 | 11 | Once the app is ready, simply follow the instructions in the project `readme` to deploy to your device. When you run the app locally using `yarn start`, mocked `@brightsign/` JavaScript APIs will be used. 12 | 13 | **For more info**, see [/template/README.md](./template/README.md) 14 | 15 | ## Contributing to the template 16 | 17 | To contribute enhancements or fixes to the template, please follow these steps: 18 | 19 | 1. Fork and Clone: Start by forking the `dev-cookbook` repository. Then, clone your fork locally to make changes. 20 | 21 | 2. Make Changes: Navigate to the specific template you wish to improve within the dev-cookbook directory. Apply your changes there. 22 | 23 | 3. Test Locally: Test the template by instantiating using the command mentioned in the "Using the Template" section. This step ensures your changes work as expected within the create-react-app workflow. 24 | 25 | 4. Commit Changes: After testing your changes, commit them to your fork. Ensure your commit messages clearly describe the enhancements or fixes made. Push your commits to GitHub. 26 | 27 | 5. Submit a Pull Request (PR): Submit a pull request to the main dev-cookbook repository. Be sure to fill out the predefined PR sections as fully as possible. 28 | 29 | 6. Code Review: Once your PR is submitted, it will be reviewed by a BrightSign team member. Be open to feedback and ready to make further adjustments based on their suggestions. 30 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/README.md: -------------------------------------------------------------------------------- 1 | ## Using the template 2 | 3 | Ensure that you have the right version of Node.js installed (`14.17.6`) as this is what is best supported by BrightSign devices at the moment. 4 | 5 | This project is a custom `create-react-app` template. Clone the repo and then run the following command in the root of your project directory to instantiate the project. Be sure to set the `file:/` path to the location as where `/template` and `/template.json` reside. 6 | 7 | ``` 8 | npx create-react-app bs-dashboard --template file:/path/to/dev-cookbook/cra-template-brightsign-dashboard 9 | ``` 10 | 11 | Once the app is ready, simply follow the instructions in the project `readme` to deploy to your device. When you run the app locally using `yarn start`, mocked `@brightsign/` JavaScript APIs will be used. 12 | 13 | **For more info**, see [/template/README.md](./template/README.md) 14 | 15 | ## Contributing to the template 16 | 17 | To contribute enhancements or fixes to the template, please follow these steps: 18 | 19 | 1. Fork and Clone: Start by forking the `dev-cookbook` repository. Then, clone your fork locally to make changes. 20 | 21 | 2. Make Changes: Navigate to the specific template you wish to improve within the dev-cookbook directory. Apply your changes there. 22 | 23 | 3. Test Locally: Test the template by instantiating using the command mentioned in the "Using the Template" section. This step ensures your changes work as expected within the create-react-app workflow. 24 | 25 | 4. Commit Changes: After testing your changes, commit them to your fork. Ensure your commit messages clearly describe the enhancements or fixes made. Push your commits to GitHub. 26 | 27 | 5. Submit a Pull Request (PR): Submit a pull request to the main dev-cookbook repository. Be sure to fill out the predefined PR sections as fully as possible. 28 | 29 | 6. Code Review: Once your PR is submitted, it will be reviewed by a BrightSign team member. Be open to feedback and ready to make further adjustments based on their suggestions. 30 | -------------------------------------------------------------------------------- /templates/html5-app-template/src/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | 3 | ' Create directory to store crash-dumps (optional) 4 | dir = CreateDirectory("SD:/brightsign-dumps") 5 | if not dir then 6 | print "Could not create directory" 7 | end if 8 | 9 | mp = CreateObject("roMessagePort") 10 | 'Enable lDWS 11 | EnableLDWS() 12 | ' Enable SSH 13 | EnableSSH() 14 | ' Create HTML Widget 15 | widget = CreateHTMLWidget(mp) 16 | widget.Show() 17 | 18 | 'Event Loop 19 | while true 20 | msg = wait(0,mp) 21 | print "msg received - type=";type(msg) 22 | 23 | if type(msg) = "roHtmlWidgetEvent" then 24 | print "msg: ";msg 25 | end if 26 | end while 27 | 28 | end function 29 | 30 | function CreateHTMLWidget(mp as object) as object 31 | ' Enable Web Inspector 32 | reg = CreateObject("roRegistrySection","html") 33 | reg.Write("enable_web_inspector","1") 34 | reg.Flush() 35 | 36 | ' Get Screen Resolution 37 | vidmode = CreateObject("roVideoMode") 38 | width = vidmode.GetResX() 39 | height = vidmode.GetResY() 40 | 41 | r = CreateObject("roRectangle",0,0,width,height) 42 | 43 | ' Create HTML Widget config 44 | config = { 45 | nodejs_enabled: true 46 | inspector_server: { 47 | port: 3000 48 | } 49 | url: "file:///sd:/dist/index.html" 50 | port: mp 51 | } 52 | 53 | ' Create HTML Widget 54 | h = CreateObject("roHtmlWidget",r,config) 55 | return h 56 | 57 | end function 58 | 59 | function EnableLDWS() 60 | 61 | registrySection = CreateObject("roRegistrySection", "networking") 62 | 63 | if type(registrySection) = "roRegistrySection" then 64 | 65 | registrySection.Write("http_server", "80") 66 | 67 | end if 68 | 69 | registrySection.Flush() 70 | 71 | end function 72 | 73 | function EnableSSH() 74 | 75 | regSSH = CreateObject("roRegistrySection", "networking") 76 | 77 | if type(regSSH) = "roRegistrySection" then 78 | 79 | regSSH.Write("ssh","22") 80 | 81 | endif 82 | 83 | n = CreateObject("roNetworkConfiguration", 0) 84 | n.SetLoginPassword("password") 85 | n.Apply() 86 | 87 | regSSH.Flush() 88 | 89 | end function -------------------------------------------------------------------------------- /examples/node-simple-server/src/app.test.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const main = require("./app.js"); 3 | 4 | describe("HTTP Server Response", () => { 5 | let server; 6 | const port = 13131; 7 | 8 | beforeAll(async () => { 9 | server = await main(); 10 | }); 11 | 12 | afterAll(() => { 13 | if (server) { 14 | server.close(); 15 | } 16 | }); 17 | 18 | it("should respond with JSON containing mocked device info", async () => { 19 | const response = await new Promise((resolve, reject) => { 20 | http.get(`http://localhost:${port}/api/device-info`, (res) => { 21 | expect(res.statusCode).toBe(200); 22 | expect(res.headers["content-type"]).toBe("application/json"); 23 | 24 | let data = ""; 25 | res.on("data", (chunk) => { 26 | data += chunk; 27 | }); 28 | res.on("end", () => { 29 | const receivedData = JSON.parse(data); 30 | 31 | // Verify that the response contains the mocked device info 32 | expect(receivedData.model).toBe("MockModel"); 33 | expect(receivedData.osVersion).toBe("MockOSVersion"); 34 | expect(receivedData.serialNumber).toBe("MockSerialNumber"); 35 | resolve(receivedData); 36 | }); 37 | }).on("error", (err) => { 38 | reject(err); 39 | }); 40 | }); 41 | expect(response).toBeDefined(); 42 | }); 43 | 44 | it("should return 404 for non-existent files", async () => { 45 | await new Promise((resolve, reject) => { 46 | http.get( 47 | `http://localhost:${port}/non-existent-file.txt`, 48 | (res) => { 49 | expect(res.statusCode).toBe(404); 50 | resolve(); 51 | } 52 | ).on("error", (err) => { 53 | reject(err); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | # Templates 2 | 3 | Welcome to the templates directory! This guide will help you understand the purpose of the templates and where to find specific instructions for each template. 4 | 5 | ## Purpose of Templates 6 | 7 | The templates in this directory are designed to provide you with a starting point for various types of applications. They include pre-configured setups and best practices to help you quickly bootstrap your projects. 8 | 9 | ## Available Templates 10 | 11 | ### cra-template-brightsign-app 12 | 13 | This template helps you create a React application tailored for BrightSign devices. 14 | 15 | - **Location**: [`templates/cra-template-brightsign-app`](./cra-template-brightsign-app/) 16 | - **Instructions**: Refer to the README file within the template directory for detailed setup and usage instructions. 17 | 18 | ### cra-template-brightsign-dashboard 19 | 20 | This template helps you create a React application tailored for BrightSign devices. 21 | 22 | - **Location**: [`templates/cra-template-brightsign-dashboard`](./cra-template-brightsign-dashboard/) 23 | - **Instructions**: Refer to the README file within the template directory for detailed setup and usage instructions. 24 | 25 | ### html5-app-template 26 | 27 | This template helps you create an html application on your BrightSign devices. 28 | 29 | - **Location**: [`templates/html5-app-template`](./html5-app-template/) 30 | - **Instructions**: Refer to the README file within the template directory for detailed setup and usage instructions. 31 | 32 | ### Other Templates 33 | 34 | Explore other templates in the `templates` directory based on your application needs. Each template comes with its own README file with specific instructions on how to set up and run the project. 35 | 36 | ## Conclusion 37 | 38 | By exploring the templates directory, you will find various templates tailored for different types of applications. Each template is designed to help you quickly set up and run your projects with minimal configuration. Refer to the README file within each template directory for detailed instructions. 39 | 40 | Happy coding! -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "examples/bluetooth-scan", 5 | "examples/bs-self-updater", 6 | "examples/bs-sqlite-db", 7 | "examples/enable-ldws", 8 | "examples/html-starter", 9 | "examples/htmlwidget-iframes", 10 | "examples/indexeddb-caching", 11 | "examples/local-storage", 12 | "examples/node-simple-server", 13 | "examples/node-starter", 14 | "examples/nodejs-web-app", 15 | "examples/self-signed-certs", 16 | "examples/send-plugin-message", 17 | "templates/**" 18 | ], 19 | "dependencies": { 20 | "@babel/plugin-syntax-jsx": "^7.24.1", 21 | "@babel/preset-env": "^7.24.3", 22 | "@babel/preset-react": "^7.24.1", 23 | "@testing-library/jest-dom": "^6.4.2", 24 | "@testing-library/react": "^14.2.2", 25 | "babel-jest": "^29.7.0", 26 | "jest": "^29.7.0", 27 | "jest-environment-jsdom": "^29.7.0", 28 | "jest-transform-css": "^6.0.1", 29 | "jsdom": "20.0", 30 | "react": "^18.2.0", 31 | "react-dom": "^18.2.0" 32 | }, 33 | "scripts": { 34 | "commit": "cz", 35 | "prepare": "husky install", 36 | "test": "yarn workspaces run test", 37 | "format": "yarn workspaces run format", 38 | "lint": "yarn workspaces run lint" 39 | }, 40 | "license": "SEE LICENSE IN LICENSE.txt", 41 | "devDependencies": { 42 | "commitizen": "^4.3.0", 43 | "cz-conventional-changelog": "^3.3.0", 44 | "eslint": "8.57.0", 45 | "eslint-config-airbnb": "19.0.4", 46 | "eslint-config-prettier": "9.1.0", 47 | "eslint-plugin-import": "2.29.1", 48 | "eslint-plugin-jest": "27.9.0", 49 | "eslint-plugin-jsx-a11y": "6.8.0", 50 | "eslint-plugin-prettier": "4", 51 | "eslint-plugin-react": "7.34.1", 52 | "husky": "8.0.3", 53 | "prettier": "2.8.8" 54 | }, 55 | "config": { 56 | "commitizen": { 57 | "path": "./node_modules/cz-conventional-changelog" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/node-simple-server/src/app.js: -------------------------------------------------------------------------------- 1 | const DIClass = require("@brightsign/deviceinfo"); 2 | const http = require("http"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | function app() { 7 | return new Promise((resolve, reject) => { 8 | const di = new DIClass(); 9 | 10 | const server = http.createServer((req, res) => { 11 | // Serve device info on /api/device-info 12 | if (req.url === "/api/device-info") { 13 | res.setHeader("Content-Type", "application/json"); 14 | const jsonResponse = JSON.stringify(di); 15 | res.end(jsonResponse); 16 | return; 17 | } 18 | 19 | // Serve static files from /storage/sd/ 20 | const filePath = path.join( 21 | "/storage/sd", 22 | req.url === "/" ? "index.html" : req.url 23 | ); 24 | fs.readFile(filePath, (err, content) => { 25 | if (err) { 26 | res.writeHead(404); 27 | res.end("File not found"); 28 | return; 29 | } 30 | 31 | // Set content type based on file extension 32 | const ext = path.extname(filePath); 33 | const contentType = 34 | { 35 | ".html": "text/html", 36 | ".js": "text/javascript", 37 | ".css": "text/css", 38 | ".json": "application/json", 39 | ".png": "image/png", 40 | ".jpg": "image/jpeg", 41 | }[ext] || "text/plain"; 42 | 43 | res.writeHead(200, { "Content-Type": contentType }); 44 | res.end(content); 45 | }); 46 | }); 47 | 48 | const port = 13131; 49 | server.listen(port, () => { 50 | console.log(`Server running at http://localhost:${port}`); 51 | resolve(server); 52 | }); 53 | 54 | server.on("error", reject); 55 | }); 56 | } 57 | 58 | module.exports = app; 59 | -------------------------------------------------------------------------------- /examples/cec-interface/brightscript/autorun.brs: -------------------------------------------------------------------------------- 1 | sub Main() 2 | print "BrightScript CEC Example: Display On/Off" 3 | 4 | mp = createobject("roMessagePort") 5 | cec = CreateObject("roCecInterface", "HDMI-1") ' Modify this to be 'HDMI-1', 'HDMI-2', 'HDMI-3', or 'HDMI-4' as needed 6 | if cec = invalid then 7 | print "Failed to create roCecInterface object." 8 | return 9 | end if 10 | 11 | ' Create a HTML widget to display something on the screen 12 | r = CreateObject("roRectangle", 0, 0, 1920, 1080) 13 | config = { 14 | url: "file:///sd:/index.html", 15 | inspector_server: { 16 | port: 2999 17 | } 18 | } 19 | htmlWidget = CreateObject("roHtmlWidget", r, config) 20 | htmlWidget.SetPort(mp) 21 | htmlWidget.Show() 22 | 23 | cec.SetPort(mp) 24 | 25 | ' Optionally enable debugging for CEC messages 26 | ' cec.EnableCecDebug("SD:/cec_debug.log") 27 | 28 | ' Send Image View On (4f0d) 29 | bufferImageViewOn = CreateObject("roByteArray") 30 | bufferImageViewOn.FromHexString("4f0d") 31 | cec.SendRawMessage(bufferImageViewOn) 32 | print "Sent Image View On: " + bufferImageViewOn.ToHexString() 33 | 34 | ' Wait 15 seconds, then send Standby 35 | sleep(15000) 36 | bufferStandby = CreateObject("roByteArray") 37 | bufferStandby.FromHexString("4f36") 38 | cec.SendRawMessage(bufferStandby) 39 | print "Sent Standby: " + bufferStandby.ToHexString() 40 | 41 | ' Optionally, uncomment the block below to repeat the cycle every 15 seconds 42 | ' while true 43 | ' sleep(15000) 44 | ' cec.SendRawMessage(bufferImageViewOn) 45 | ' print "Sent Image View On: " + bufferImageViewOn.ToHexString() 46 | 47 | ' sleep(15000) 48 | ' cec.SendRawMessage(bufferStandby) 49 | ' print "Sent Standby: " + bufferStandby.ToHexString() 50 | ' end while 51 | 52 | ' Message loop to handle any incoming messages (if needed) 53 | while true 54 | msg = wait(0, mp) 55 | if type(msg) = "roMessagePortEvent" 56 | ? "Message received: ";msg 57 | end if 58 | end while 59 | end sub 60 | -------------------------------------------------------------------------------- /examples/provisioning-server/content/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Example HTML App 7 | 29 | 30 | 31 | 32 | 33 |
34 | device 35 | 36 |

37 | Congratulations on setting up a Provisioning & Recovery server 38 | with BrightSign! 39 |

40 | device 41 |
42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/bs-self-updater/README.md: -------------------------------------------------------------------------------- 1 | # bs-self-updater 2 | 3 | A simple self-updating application written using TypeScript and designed to run on BrightSign players. 4 | 5 | ## Overview 6 | 7 | `bs-app-updater` is a lightweight utility that allows an application running on BrightSign players to update itself by downloading and applying a new `autorun.zip` package. This enables remote updates and maintenance of deployed applications with minimal effort. 8 | 9 | ## Features 10 | 11 | - Calls a configurable server endpoint to download the `autorun.zip` application to run on the player. 12 | - See `SERVER_URL` in `index.ts`. 13 | - Unzips the downloaded package and executes the `autorun.brs` file from the unzipped package. 14 | - Makes periodic calls to the server to check for any updates to the `autorun.zip` file. 15 | 16 | ## Getting Started 17 | 18 | ### Prerequisites 19 | 20 | - Node.js (v18 or later recommended) 21 | - Yarn (for package management) 22 | 23 | ### Installation 24 | 25 | 1. Clone this repository or copy the code to your project directory. 26 | 2. Install dependencies (if any): 27 | ```sh 28 | yarn install 29 | ``` 30 | 31 | ### Build 32 | 33 | Run the following command to compile the TypeScript code into JavaScript: 34 | 35 | ```sh 36 | yarn build 37 | ``` 38 | 39 | ### Deploy and run on player 40 | 41 | 1. Copy the `autorun.brs` file and `dist/index.js` files to the root of an SD card. 42 | - /storage/sd/autorun.brs 43 | - /storage/sd/index.js 44 | 2. Insert the SD card into the BrightSign player. 45 | 3. Boot up the player. 46 | 47 | ## Optional: Local Node.js Server for Testing 48 | 49 | You can run a simple Node.js server locally to serve the `autorun.zip` file expected by the JS app. This is useful for development and testing. 50 | 51 | ### To start the server: 52 | 53 | 1. Navigate to the `server` directory: 54 | ```sh 55 | cd server 56 | ``` 57 | 2. Install dependencies: 58 | ```sh 59 | yarn install 60 | ``` 61 | 3. Start the server: 62 | ```sh 63 | yarn start 64 | ``` 65 | 4. The server will listen on port 7000 by default and has a single endpoint to serve the `autorun.zip` file: 66 | ``` 67 | http://localhost:7000/autorun.zip 68 | ``` 69 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | 3 | ' Create directory to store crash-dumps (optional) 4 | dir = CreateDirectory("SD:/brightsign-dumps") 5 | if not dir then 6 | print "Could not create directory" 7 | end if 8 | 9 | ' Create Message Port 10 | mp = CreateObject("roMessagePort") 11 | ' Enable lDWS 12 | enableLDWS() 13 | ' Enable SSH 14 | enableSSH() 15 | 16 | ' Initialize roNodeJs with path or correct filename, whether webpack is used or not. 17 | node_js = CreateObject("roNodeJs", "sd:/dist/backend.js", {message_port: mp, node_arguments: ["--inspect=0.0.0.0:2999"], arguments: []}) 18 | 19 | ' Create HTML Widget 20 | widget = createHTMLWidget(mp) 21 | widget.Show() 22 | 23 | ' Event Loop 24 | while true 25 | msg = wait(0,mp) 26 | print "msg received - type=";type(msg) 27 | 28 | if type(msg) = "roHtmlWidgetEvent" then 29 | print "msg: ";msg 30 | end if 31 | end while 32 | 33 | end function 34 | 35 | function createHTMLWidget(mp as object) as object 36 | ' Enable Web Inspector 37 | reg = CreateObject("roRegistrySection","html") 38 | reg.Write("enable_web_inspector","1") 39 | reg.Flush() 40 | 41 | ' Get Screen Resolution 42 | vidmode = CreateObject("roVideoMode") 43 | width = vidmode.GetResX() 44 | height = vidmode.GetResY() 45 | 46 | r = CreateObject("roRectangle",0,0,width,height) 47 | 48 | ' Create HTML Widget config 49 | config = { 50 | nodejs_enabled: true 51 | inspector_server: { 52 | port: 3000 53 | } 54 | url: "http://localhost:8020" 55 | port: mp 56 | } 57 | 58 | ' Create HTML Widget 59 | h = CreateObject("roHtmlWidget",r,config) 60 | return h 61 | 62 | end function 63 | 64 | function enableLDWS() 65 | 66 | registrySection = CreateObject("roRegistrySection", "networking") 67 | 68 | if type(registrySection) = "roRegistrySection" then 69 | 70 | registrySection.Write("http_server", "80") 71 | 72 | end if 73 | 74 | registrySection.Flush() 75 | 76 | end function 77 | 78 | function enableSSH() 79 | 80 | regSSH = CreateObject("roRegistrySection", "networking") 81 | 82 | if type(regSSH) = "roRegistrySection" then 83 | 84 | regSSH.Write("ssh","22") 85 | 86 | endif 87 | 88 | n = CreateObject("roNetworkConfiguration", 0) 89 | n.SetLoginPassword("password") 90 | n.Apply() 91 | 92 | regSSH.Flush() 93 | 94 | end function -------------------------------------------------------------------------------- /examples/self-signed-certs/README.md: -------------------------------------------------------------------------------- 1 | # Self-Signed Certificate Handling Example 2 | 3 | ## Introduction 4 | 5 | This example demonstrates how to handle self-signed certificates when communicating with BrightSign players via the local Diagnostic Web Server (DWS). BrightSign players use self-signed certificates for HTTPS communication, which requires configuring your HTTP client to accept these certificates for successful player communication and management. 6 | 7 | ## How it Works 8 | 9 | 1. **Certificate Generation**: BrightSign players automatically generate self-signed certificates for secure HTTPS communication 10 | 2. **Client Configuration**: Standard HTTP clients reject self-signed certificates by default for security reasons 11 | 3. **Agent Setup**: The example creates an HTTPS agent with `rejectUnauthorized: false` to accept self-signed certificates 12 | 4. **API Communication**: Uses the configured agent to make secure requests to the player's DWS endpoints 13 | 14 | ## How to Run the Example 15 | 16 | ### Prerequisites 17 | 18 | 1. **Node.js Environment**: Ensure you have Node.js installed on your development machine 19 | 2. **Network Connection**: Your computer and BrightSign player should be on the same network 20 | 3. **Player IP Address**: Know the IP address of your BrightSign player 21 | 22 | ### Steps to Run 23 | 24 | 1. **Install Dependencies**: 25 | ```bash 26 | npm install undici 27 | ``` 28 | 29 | 2. **Update Player IP**: 30 | - Open `index.js` 31 | - Replace `192.168.1.100` with your player's actual IP address 32 | 33 | 3. **Run the Example**: 34 | ```bash 35 | node index.js 36 | ``` 37 | 38 | 4. **Expected Output**: 39 | - If successful, you'll see the player's status response in JSON format 40 | - Any communication errors will be displayed with descriptive error messages 41 | 42 | ## Files Structure 43 | 44 | - **index.js**: Main example file showing how to configure undici Agent for self-signed certificates 45 | - **README.md**: This documentation file 46 | 47 | ## Important Security Notes 48 | 49 | - Only disable certificate verification (`rejectUnauthorized: false`) for trusted BrightSign player communication 50 | - Never use this configuration for external or untrusted HTTPS endpoints 51 | - This approach is specifically designed for local development and player management scenarios 52 | - Default DWS port is `8443` for HTTPS communication -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/autorun.brs: -------------------------------------------------------------------------------- 1 | function main() 2 | 3 | ' Create directory to store crash-dumps (optional) 4 | dir = CreateDirectory("SD:/brightsign-dumps") 5 | if not dir then 6 | print "Could not create directory" 7 | end if 8 | 9 | mp = CreateObject("roMessagePort") 10 | 11 | 'Enable lDWS 12 | enableLDWS() 13 | 14 | ' Enable SSH 15 | enableSSH() 16 | 17 | ' Initialize roNodeJs with path or correct filename, whether webpack is used or not. 18 | node_js = CreateObject("roNodeJs", "sd:/dist/backend.js", {message_port: mp, node_arguments: ["--inspect=0.0.0.0:2999"], arguments: []}) 19 | 20 | ' Create HTML Widget 21 | widget = createHTMLWidget(mp) 22 | widget.Show() 23 | 24 | 'Event Loop test 25 | while true 26 | msg = wait(0,mp) 27 | print "msg received - type=";type(msg) 28 | 29 | if type(msg) = "roHtmlWidgetEvent" then 30 | print "msg: ";msg 31 | end if 32 | end while 33 | 34 | end function 35 | 36 | 37 | function createHTMLWidget(mp as object) as object 38 | 39 | ' Enable Web Inspector 40 | reg = CreateObject("roRegistrySection","html") 41 | reg.Write("enable_web_inspector","1") 42 | reg.Flush() 43 | 44 | ' Get Screen Resolution 45 | vidmode = CreateObject("roVideoMode") 46 | width = vidmode.GetResX() 47 | height = vidmode.GetResY() 48 | 49 | r = CreateObject("roRectangle",0,0,width,height) 50 | 51 | ' Create HTML Widget config 52 | config = { 53 | nodejs_enabled: true 54 | inspector_server: { 55 | port: 3000 56 | } 57 | url: "http://localhost:8020" 58 | port: mp 59 | } 60 | 61 | ' Create HTML Widget 62 | h = CreateObject("roHtmlWidget",r,config) 63 | return h 64 | 65 | end function 66 | 67 | 68 | function enableLDWS() 69 | 70 | registrySection = CreateObject("roRegistrySection", "networking") 71 | 72 | if type(registrySection) = "roRegistrySection" then 73 | registrySection.Write("http_server", "80") 74 | end if 75 | 76 | registrySection.Flush() 77 | 78 | end function 79 | 80 | 81 | function enableSSH() 82 | regSSH = CreateObject("roRegistrySection", "networking") 83 | 84 | if type(regSSH) = "roRegistrySection" then 85 | regSSH.Write("ssh","22") 86 | endif 87 | 88 | n = CreateObject("roNetworkConfiguration", 0) 89 | n.SetLoginPassword("password") 90 | n.Apply() 91 | 92 | regSSH.Flush() 93 | 94 | end function -------------------------------------------------------------------------------- /templates/html5-app-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bs-html5-app-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.ts", 6 | "scripts": { 7 | "start": "IS_DESKTOP=true NODE_PATH=./dist node dist/bundle.js", 8 | "build:dev": "npm run clean && webpack --mode development", 9 | "build:prod": "npm run clean && webpack --mode production --node-env=production", 10 | "clean": "rm -rf dist", 11 | "reinstall": "npm run clean && rm -rf node_modules && npm install", 12 | "lint": "eslint --no-error-on-unmatched-pattern --config .eslintrc src/**/*.{ts,tsx}", 13 | "lint:fix": "eslint --fix src --ext js,jsx,ts,tsx,json", 14 | "format:check": "prettier 'src/**/*.{js,jsx,ts,tsx,css,md,json}' --check --config ../.prettierrc.js --cache --cache-location=../prettiercache", 15 | "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,md,json}' --config ../.prettierrc.js && npm run lint", 16 | "test": "jest --config jest.config.js src/", 17 | "publish-package": "npm publish --access public" 18 | }, 19 | "repository": { 20 | "url": "https://github.com/brightsign/dev-cookbook.git", 21 | "type": "git", 22 | "directory": "html5-app-template/" 23 | }, 24 | "publishConfig": { 25 | "registry": "https://registry.npmjs.org" 26 | }, 27 | "author": "BrightSign LLC", 28 | "license": "ISC", 29 | "dependencies": { 30 | "digest-fetch": "^3.1.1", 31 | "express": "^4.20.0", 32 | "form-data": "^4.0.0", 33 | "http-status-codes": "^2.3.0", 34 | "lodash": "^4.17.21", 35 | "node-fetch": "^2.7.0", 36 | "socket.io-client": "^4.7.5" 37 | }, 38 | "devDependencies": { 39 | "@babel/cli": "^7.24.5", 40 | "@babel/core": "^7.24.5", 41 | "@babel/preset-typescript": "^7.24.1", 42 | "@types/express": "^4.17.21", 43 | "@types/lodash": "^4.17.0", 44 | "@types/node": "^14.18.63", 45 | "@types/node-fetch": "^2.6.11", 46 | "@typescript-eslint/eslint-plugin": "^5.62.0", 47 | "@typescript-eslint/parser": "^5.62.0", 48 | "eslint": "^7.24.0", 49 | "eslint-config-prettier": "^9.1.0", 50 | "html-webpack-plugin": "^5.6.0", 51 | "jest": "^29.7.0", 52 | "prettier": "^3.2.5", 53 | "ts-loader": "^9.5.1", 54 | "ts-node": "^10.9.2", 55 | "typescript": "5.1.6", 56 | "webpack": "^5.96.1", 57 | "webpack-cli": "^4.10.0" 58 | }, 59 | "engine": { 60 | "node": "14.17.6" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ; 8 | -------------------------------------------------------------------------------- /examples/node-starter/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Starter Node.js HTTP Server on BrightSign Device 3 | 4 | ## Introduction 5 | 6 | This example is the second stepping stone of the starter examples to get familiar with developing with a BrightSign. The intention of this example is to show how to get a simple node application running on your player. Once the server is running, you’ll be able to access a congratulatory message by making a request to the server's `/` endpoint. 7 | 8 | The node application is defined in `index.js` and is a simplified version of the html templates we offer. In addition to the JavaScript file, there is an `autorun.brs` file which is what tells your player to run the application. 9 | 10 | ## Prerequisites 11 | 12 | 1. **Wi-Fi or Ethernet Network**: Both your BrightSign device and the computer should be connected to the same network to fully interact with this example. 13 | 14 | ## Steps to Set Up and Run the Server 15 | 16 | ### 1. Prepare the SD Card 17 | 18 | 1. Insert the SD card into your computer. 19 | 2. Since this example is simplified, there are only two files which you will need to copy to the root of the player's sd card. Copy the `index.js` and `autorun.brs` files from this directory to the root of the SD card. 20 | 21 | ### 2. Set Up the BrightSign Device 22 | 23 | 1. Eject the SD card from your computer and insert it into your BrightSign device. 24 | 2. Power on the BrightSign device and allow it to boot up. 25 | 3. The device will automatically run the `autorun.brs` file which executes the `index.js` node server. 26 | 27 | ### 3. Accessing the Server 28 | 29 | Once the device has booted and the server has started: 30 | 31 | 1. **Find the IP Address**: 32 | - Identify the IP address of your BrightSign device. This can typically be found in the device's network settings or via BrightAuthor:connected or by booting the player without the Micro SD card inserted. 33 | 34 | 2. **Interact with the Server**: 35 | - Open a terminal or command prompt on your computer. 36 | - Use the `curl` command to make a request to the server's `/` endpoint: 37 | ```bash 38 | curl http://:3000/ 39 | ``` 40 | Replace `` with the actual IP address of your BrightSign device. 41 | 42 | - Alternatively, you can open a web browser and navigate to: 43 | ``` 44 | http://:3000/ 45 | ``` 46 | 47 | 3. **Expected Response**: 48 | - If the server is running correctly, you should see the following message in your terminal or browser: 49 | ``` 50 | Congratulations on interacting with your BrightSign running a simple node http application! 51 | ``` 52 | 53 | ### 4. Troubleshooting 54 | 55 | - **No Response**: If you don't receive a response from the server, verify that: 56 | - The BrightSign device is properly connected to the network. 57 | - The IP address is correct. 58 | - The server script is correctly copied to the SD card. 59 | - The device allows incoming connections on port `3000`. 60 | -------------------------------------------------------------------------------- /examples/indexeddb-caching/README.md: -------------------------------------------------------------------------------- 1 | # IndexedDB Video Caching Example 2 | 3 | This example demonstrates how to use IndexedDB for caching video content in a BrightSign HTML5 application. The application creates a video playlist that intelligently caches videos in the background for smooth playback. 4 | 5 | ## File Structure 6 | 7 | - `index.html`: Main HTML application with video player and caching logic 8 | - `autorun.brs`: BrightSign autorun script which loads the HTML application 9 | 10 | ## Usage 11 | 12 | 1. Copy the `autorun.brs` and `index.html` files to the root of an SD card 13 | 2. Insert the SD card in the BrightSign player and power it on 14 | 3. The `autorun.brs` script will automatically launch the HTML application 15 | 16 | ## How It Works 17 | 18 | 1. **Database Setup**: Creates an IndexedDB database (`videos_db`) with an object store (`videos_os`) to store video blobs 19 | 2. **Playlist Loading**: Loads a predefined playlist of sample videos from Google's test video collection 20 | 3. **Smart Caching Strategy**: 21 | - First video: Downloads and plays immediately, then starts background caching 22 | - Subsequent videos: Plays from cache if available, otherwise downloads from network 23 | - Background caching: Downloads remaining videos with 1-second delays between requests 24 | 4. **Automatic Playback**: Videos play continuously with automatic progression to the next video 25 | 26 | ## Configuration Options 27 | 28 | ### BrightSign Configuration (autorun.brs) 29 | - `url`: Path to the HTML file. This can be modified to point to a remote server URL that serves the HTML file. 30 | - `mouse_enabled`: Enable/disable mouse interaction (default: false) 31 | - `javascript_enabled`: Enable/disable JavaScript (default: true) 32 | - `nodejs_enabled`: Enable/disable Node.js (default: false) 33 | - `storage_path`: Directory for local storage cache 34 | - `storage_quota`: Maximum cache size 35 | 36 | See the [roHtmlWidget](https://docs.brightsign.biz/developers/rohtmlwidget#23vJS) page for more details on configuration options. 37 | 38 | ### Video Configuration (index.html) 39 | - Modify the `fetchPlaylist()` function to add your own video URLs 40 | - Adjust the background caching delay by changing the `TIMEOUT_DELAY` value 41 | - Customize video display properties in the `displayVideo()` function 42 | 43 | ## Error Handling 44 | 45 | The application includes comprehensive error handling for: 46 | - Network connectivity issues 47 | - IndexedDB operation failures 48 | - Duplicate cache entries (ConstraintError) 49 | - Video loading and playback errors 50 | 51 | ## Performance Considerations 52 | 53 | - **Storage**: Videos are stored as blobs in IndexedDB, which uses available disk space 54 | - **Network**: Background caching uses staggered requests (1-second delays) to avoid overwhelming the network 55 | - **Memory**: Video blobs are created using `URL.createObjectURL()` for efficient memory usage 56 | 57 | ## Troubleshooting 58 | 59 | 1. **Videos not caching**: Check console for IndexedDB errors 60 | 2. **Network errors**: Verify internet connectivity and video URLs 61 | 3. **Storage full**: Check available disk space and storage quota settings 62 | 4. **Playback issues**: Ensure video formats are supported by the player 63 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import "./App.css"; 3 | 4 | let DeviceInfo; 5 | let os; 6 | 7 | // Import mocked @brightsign modules when developing locally 8 | // When running on a device, these modules are pre-installed globally 9 | // Feel free to modify the mocks as needed for your use case. 10 | if (process.env.NODE_ENV === "development") { 11 | DeviceInfo = require("./__mocks__/@brightsign/deviceinfo"); 12 | os = require("./__mocks__/os"); 13 | } else { 14 | DeviceInfo = require("@brightsign/deviceinfo"); 15 | os = require("os"); 16 | } 17 | 18 | function App() { 19 | const networkInterfaces = os.networkInterfaces() || {}; 20 | const [ipAddress, setIpAddress] = useState(""); 21 | const [header, setHeader] = useState(""); 22 | 23 | useEffect(() => { 24 | const interval = setInterval(async () => { 25 | const port = process.env.REACT_APP_PORT || 8020; 26 | const rawText = await fetch(`http://localhost:${port}/text`); 27 | const { text } = await rawText.json(); 28 | 29 | if (text) { 30 | setHeader(text); 31 | } 32 | 33 | // Get network interface data 34 | Object.keys(networkInterfaces).forEach((interfaceName) => { 35 | networkInterfaces[interfaceName].forEach((interfaceInfo) => { 36 | if (interfaceInfo.family === "IPv4") { 37 | setIpAddress( 38 | `${interfaceName}: ${interfaceInfo.address} ` 39 | ); 40 | console.log( 41 | `Network Interface - ${interfaceName}: ${interfaceInfo.address}` 42 | ); 43 | } 44 | }); 45 | }); 46 | }, 5000); 47 | 48 | return () => clearInterval(interval); 49 | }); 50 | 51 | const deviceInfo = new DeviceInfo(); 52 | const { model, osVersion, serialNumber } = deviceInfo; 53 | 54 | return ( 55 |
56 |

{header || "BrightSign React Web App Example"}

57 |
58 |
59 | IP Address: 60 | {ipAddress || "loading..."} 61 |
62 |
63 | Model: 64 | {model || "loading..."} 65 |
66 |
67 | OS Version: 68 | {osVersion || "loading..."} 69 |
70 |
71 | Serial Number: 72 | 73 | {" "} 74 | {serialNumber || "loading..."} 75 | 76 |
77 |
78 |
79 | ); 80 | } 81 | 82 | export default App; 83 | -------------------------------------------------------------------------------- /examples/bs-sqlite-db/index.js: -------------------------------------------------------------------------------- 1 | // index.js - BrightSign JavaScript app to receive messages from BrightScript via MessagePort 2 | 3 | const MESSAGE_PORT = require("@brightsign/messageport"); 4 | const bsMessage = new MESSAGE_PORT(); 5 | 6 | if (bsMessage) { 7 | bsMessage.addEventListener('bsmessage', function(msg) { 8 | if (msg && typeof msg === 'object') { 9 | switch(msg.action) { 10 | case 'dbCreated': { 11 | console.log('Database created at:', msg.path); 12 | bsMessage.PostBSMessage({ 13 | action: 'create', 14 | command: "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER);" 15 | }); 16 | break; 17 | } 18 | case 'create': { 19 | console.log('Created table:', msg); 20 | bsMessage.PostBSMessage({ 21 | action: 'insert', 22 | command: "INSERT INTO users (name, age) VALUES ('Alice', 25), ('Bob', 30), ('Charlie', 35);" 23 | }); 24 | break; 25 | } 26 | case 'insert': { 27 | console.log('Inserted record:', msg); 28 | bsMessage.PostBSMessage({ 29 | action: 'select', 30 | command: "SELECT id, name, age FROM users;" 31 | }); 32 | break; 33 | } 34 | case 'select': { 35 | console.log('Retrieved records:', msg); 36 | if (typeof msg.result === 'string') { 37 | msg.result = JSON.parse(msg.result); 38 | } 39 | if (Array.isArray(msg.result) && msg.result.length > 0) { 40 | for (const record of msg.result) { 41 | bsMessage.PostBSMessage({ 42 | action: 'delete', 43 | command: `DELETE FROM users WHERE id = ${record.id};` 44 | }); 45 | } 46 | } 47 | break; 48 | } 49 | case 'delete': { 50 | console.log('Deleted record:', msg); 51 | break; 52 | } 53 | default: { 54 | console.log('Unknown message type:', msg); 55 | break; 56 | } 57 | } 58 | } else { 59 | console.log('Received non-object message:', msg); 60 | } 61 | }); 62 | 63 | // Send an initial message to indicate the JS app has started. 64 | // This will be received by the BrightScript side 65 | // which will then create the database. 66 | bsMessage.PostBSMessage({ 67 | action: 'ready', 68 | message: 'SQLite DB Example app has started.' 69 | }); 70 | 71 | // Uncomment the line below to keep the JS app running indefinitely. 72 | // setInterval(function(){}, 10000); 73 | 74 | } else { 75 | console.log('@brightsign/messageport API not available.'); 76 | } 77 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Welcome to the examples directory! This guide will help you get started with the provided examples. Each example is independent and can be used based on your specific needs. 4 | 5 | ## Available Examples 6 | 7 | Note that some starter examples include the creation of a `brightsign-dumps` folder, which is used by the BrightSign OS to store crash information and is very useful to BrightSign Support when troubleshooting. The creation of this folder is an optional but recommended step to help with diagnosing application issues. 8 | 9 | ### HTML & Web Storage Examples 10 | 11 | #### HTML Starter Example 12 | 13 | - **Location**: `examples/html-starter` 14 | - **Features**: Simple HTML application for BrightSign, demonstrates running HTML and displaying images using a static directory. Great for getting started with HTML on BrightSign. 15 | 16 | #### IndexedDB Caching Example 17 | 18 | - **Location**: `examples/indexeddb-caching` 19 | - **Features**: Demonstrates video caching using IndexedDB in a BrightSign HTML5 app. Implements a smart playlist and background caching for smooth video playback. 20 | 21 | #### Local Storage Example 22 | 23 | - **Location**: `examples/local-storage` 24 | - **Features**: Image slideshow that caches images in browser localStorage for persistent, smooth playback and looping. 25 | 26 | ### Node.js Examples 27 | 28 | #### Node Starter Example 29 | 30 | - **Location**: `examples/node-starter` 31 | - **Features**: Minimal Node.js HTTP server for BrightSign. Boots a simple server and responds to requests at the root endpoint. 32 | 33 | #### Node Simple Server Example 34 | 35 | - **Location**: `examples/node-simple-server` 36 | - **Features**: Advanced Node.js server with static file serving, device info REST API, Jest tests, and webpack config. Good for learning about full-featured Node.js deployments on BrightSign. 37 | 38 | ### Device & Plugin Integration Examples 39 | 40 | #### Bluetooth Scan Example 41 | 42 | - **Location**: `examples/bluetooth-scan` 43 | - **Features**: HTML+JS app for scanning Bluetooth devices on BrightSign. Requires a compatible Bluetooth adapter and uses BrightSign's proprietary JS API. 44 | 45 | #### BS Self Updater Example 46 | 47 | - **Location**: `examples/bs-self-updater` 48 | - **Features**: TypeScript utility for self-updating BrightSign apps by downloading and applying new `autorun.zip` packages from a server. 49 | 50 | #### BS SQLite DB Example 51 | 52 | - **Location**: `examples/bs-sqlite-db` 53 | - **Features**: Demonstrates SQLite database usage on BrightSign, including table creation, data insertion, querying, and cleanup via BrightScript and JavaScript communication. 54 | 55 | #### Send Plugin Message Example 56 | 57 | - **Location**: `examples/send-plugin-message` 58 | - **Features**: Shows how to send plugin messages between BrightScript and HTML/JavaScript apps, useful for integrating BrightAuthor:connected presentations with custom logic. 59 | 60 | ## Next Steps 61 | 62 | After exploring these examples, you can: 63 | 64 | 1. Combine concepts from different examples to build more complex applications 65 | 2. Add testing to your applications following the `node-simple-server` example 66 | 3. Implement plugin message communication for advanced BrightScript integration 67 | -------------------------------------------------------------------------------- /scripts/workspace_actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This script runs the specifed yarn commands only on workspaces that have changes 3 | * against the `main` branch. 4 | * 5 | * Usage: node ./workspace_actions.js test format 6 | * 7 | */ 8 | const { execSync } = require("child_process"); 9 | const { existsSync } = require("fs"); 10 | const path = require("path"); 11 | 12 | const mainBranchName = "origin/main"; 13 | const commandsToRun = process.argv.slice(2); // Commands passed as arguments 14 | 15 | const repoRoot = path.resolve(__dirname, ".."); 16 | 17 | function exec(command, captureOutput = false) { 18 | if (captureOutput) { 19 | return execSync(command).toString().trim(); 20 | } else { 21 | execSync(command, { stdio: "inherit" }); 22 | } 23 | } 24 | 25 | // Get a list of all workspaces and their paths 26 | function getWorkspacesInfo() { 27 | const workspacesInfoRaw = exec(`yarn --cwd ${repoRoot} workspaces --json info`, true); 28 | try { 29 | // First, parse the JSON output to get the "data" field. 30 | const parsedOutput = JSON.parse(workspacesInfoRaw); 31 | // Then, parse the "data" field to get the actual workspaces info. 32 | const workspacesInfo = JSON.parse(parsedOutput.data); 33 | return Object.keys(workspacesInfo).reduce((acc, key) => { 34 | acc[key] = path.resolve(repoRoot, workspacesInfo[key].location); 35 | return acc; 36 | }, {}); 37 | } catch (error) { 38 | console.error('Failed to parse workspaces info:', error); 39 | return {}; 40 | } 41 | } 42 | 43 | // Get a list of changed files compared to the main branch 44 | function getChangedFiles() { 45 | return exec(`git -C ${repoRoot} diff --name-only ${mainBranchName}`, true).split('\n'); 46 | } 47 | 48 | // Determine if a path is part of a workspace 49 | function isPathInWorkspace(filePath, workspacePath) { 50 | const fullFilePath = path.resolve(repoRoot, filePath); 51 | const isInWorkspace = fullFilePath.startsWith(workspacePath); 52 | 53 | return isInWorkspace; 54 | } 55 | 56 | // Main function to run configurable commands in changed workspaces 57 | function runCommandsInChangedWorkspaces() { 58 | const workspaces = getWorkspacesInfo(); 59 | const changedFiles = getChangedFiles(); 60 | const changedWorkspaces = {}; 61 | 62 | // Determine which workspaces have changed 63 | for (const [workspace, workspacePath] of Object.entries(workspaces)) { 64 | if ( 65 | changedFiles.some((file) => isPathInWorkspace(file, workspacePath)) 66 | ) { 67 | changedWorkspaces[workspace] = workspacePath; 68 | } 69 | } 70 | 71 | // Run configurable commands in changed workspaces 72 | for (const [workspace, workspacePath] of Object.entries( 73 | changedWorkspaces 74 | )) { 75 | if (existsSync(`${workspacePath}/package.json`)) { 76 | for (const command of commandsToRun) { 77 | try { 78 | exec(`cd ${workspacePath} && yarn ${command}`); 79 | } catch (error) { 80 | console.error( 81 | `Failed to run '${command}' in workspace: ${workspace}\n${error}` 82 | ); 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | runCommandsInChangedWorkspaces(); 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | Thank you for contributing to the BrightSign Dev Cookbook! 4 | 5 | ## Contributing Guidelines 6 | 7 | - **Code Style:** Please adhere to the coding conventions as defined in `.eslintrc`. Run `yarn format` before submitting. 8 | - **Commit Messages:** Write clear, concise commit messages that explain the changes made. Use [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) style messages. 9 | - **Pull Requests:** For substantial changes, it's best to open an issue for discussion before submitting a pull request. 10 | 11 | We look forward to your contributions and suggestions! 12 | 13 | 14 | # How to submit changes 15 | 16 | To contribute enhancements or fixes to `dev-cookbook`, please follow these steps: 17 | 18 | 1. Fork and Clone: Start by forking the `dev-cookbook` repository. Then, clone your fork locally to make changes. 19 | 20 | 2. Make Changes: Navigate to the specific template or example you wish to improve within the `dev-cookbook` directory. Apply your changes there. 21 | 22 | 3. Test Locally: Test the template by instantiating using the command mentioned in the "Using the Template" section. This step ensures your changes work as expected within the `create-react-app` workflow. If it is an `example-*`, test your changes according to the instructions in the example Readme. 23 | 24 | 4. Commit Changes: After testing your changes, commit them to your fork. Ensure your commit messages clearly describe the enhancements or fixes made. Push your commits to GitHub. 25 | 26 | 5. Submit a Pull Request (PR): Submit a pull request to the main dev-cookbook repository. Be sure to fill out the predefined PR sections as fully as possible. 27 | 28 | 6. Code Review: Once your PR is submitted, it will be reviewed by a BrightSign team member. Be open to feedback and ready to make further adjustments based on their suggestions. 29 | 30 | # Modifying cra-template-* examples 31 | 32 | `cra-template-*` examples can be run on your development machine (via `yarn start`) for rapid iteration, with some caveats. The full functionality of `@brightsign` API modules are only available on the device, but we have added partial mock implementations for you to develop with locally. Please refer to the [JavaScript API Documentation](https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370678188/JavaScript+APIs) for further details on each API so that you may extend the existing mocks. 33 | 34 | You can also extend or create new tests (`App.test.js`) and run them with `yarn run test` in each example directory `dev-cookbook`, or in every workspace by running `yarn run test` at the root of the project. 35 | 36 | If you want to contribute improvements back to `dev-cookbook`, either make your changes directly in the repo or copy them from your `create-react-app` project after you have verified them locally and on a device. Be sure to maintain the same directory structure. 37 | 38 | 39 | # Git hooks 40 | 41 | `dev-cookbook` uses [husky](https://typicode.github.io/husky/) to automatically run `eslint` and `prettier` on commit to maintain code quality. There is also a hook to run tests before pushing to a remote branch. If you would like to suggest improvements, take a look at the hooks configuration in the `.husky` directory. 42 | 43 | 44 | # How to report a bug 45 | 46 | Create a new issue. Be sure to describe the bug and provide clear repro steps. 47 | 48 | # How to request new examples 49 | 50 | Create a new issue. Describe the problem you are trying to solve and why a new example would be useful. A BrightSign team member will review. 51 | 52 | # Copyright 53 | 54 | By contributing to this repo, you agree to license any code submitted under the MIT license. For larger contributions, we may request a Contributor License Agreement. -------------------------------------------------------------------------------- /examples/bs-sqlite-db/README.md: -------------------------------------------------------------------------------- 1 | # BrightSign SQLite Database Example 2 | 3 | This example demonstrates how to use SQLite database functionality in a BrightSign application, showing the communication between BrightScript and JavaScript for database operations. 4 | 5 | ## Overview 6 | 7 | The example showcases: 8 | - Creating a SQLite database 9 | - Creating tables with SQL commands 10 | - Inserting records into the database 11 | - Querying records from the database 12 | - Deleting records from the database 13 | - Proper database cleanup and connection management 14 | 15 | ## Files 16 | 17 | - `autorun.brs` - BrightScript application that handles database operations 18 | - `index.js` - JavaScript application that sends SQL commands via MessagePort 19 | 20 | ## How It Works 21 | 22 | 1. **Initialization**: The BrightScript application (`autorun.brs`) starts and creates a Node.js instance running `index.js` 23 | 2. **Database Creation**: When the JavaScript app signals it's ready, BrightScript creates a SQLite database at `SD:/example.db` 24 | 3. **Table Creation**: JavaScript sends a command to create a `users` table with columns for ID, name, and age 25 | 4. **Data Insertion**: Sample user records are inserted into the database 26 | 5. **Data Retrieval**: All records are queried from the database 27 | 6. **Data Deletion**: Each retrieved record is deleted from the database 28 | 7. **Cleanup**: The database connection is properly closed when the application exits 29 | 30 | ## Database Schema 31 | 32 | The example creates a simple `users` table: 33 | 34 | ```sql 35 | CREATE TABLE IF NOT EXISTS users ( 36 | id INTEGER PRIMARY KEY AUTOINCREMENT, 37 | name TEXT, 38 | age INTEGER 39 | ); 40 | ``` 41 | 42 | ## Sample Data 43 | 44 | The example inserts the following sample records: 45 | - Alice, age 25 46 | - Bob, age 30 47 | - Charlie, age 35 48 | 49 | ## Key Features 50 | 51 | ### BrightScript Side (`autorun.brs`) 52 | - **Database Management**: Creates and manages SQLite database connection 53 | - **SQL Statement Execution**: Handles CREATE, INSERT, SELECT, and DELETE operations 54 | - **Result Processing**: Processes query results and formats them for JavaScript consumption 55 | - **Error Handling**: Includes error checking for database operations 56 | - **Resource Cleanup**: Properly closes database connections on exit 57 | 58 | ### JavaScript Side (`index.js`) 59 | - **MessagePort Communication**: Uses `@brightsign/messageport` for BrightScript communication 60 | - **Command Orchestration**: Sends SQL commands in logical sequence 61 | - **Data Processing**: Parses and processes database results 62 | - **Event-Driven Architecture**: Responds to database operation completion events 63 | 64 | ## Running the Example 65 | 66 | 1. Copy the `autorun.brs` and `index.js` files to the root of your BrightSign player's SD card 67 | 2. Power on or restart your BrightSign player 68 | 3. The application will automatically start and demonstrate the database operations 69 | 4. Check the device logs to see the SQL operations being performed 70 | 71 | ## Expected Output 72 | 73 | The application will log information about each database operation: 74 | - Database creation confirmation 75 | - Table creation success 76 | - Record insertion confirmations 77 | - Retrieved records display 78 | - Record deletion confirmations 79 | 80 | ## Notes 81 | 82 | - The database file is created at `SD:/example.db` 83 | - The application demonstrates a complete CRUD (Create, Read, Update, Delete) cycle 84 | - Error handling is included for robust database operations 85 | - The database connection is automatically closed when the Nodejs application exits 86 | - Results from SELECT queries are formatted as JSON strings for JavaScript processing 87 | 88 | This example serves as a foundation for building more complex database-driven BrightSign applications. -------------------------------------------------------------------------------- /examples/bluetooth-scan/README.md: -------------------------------------------------------------------------------- 1 | # Bluetooth scan example on BrightSign players 2 | 3 | ## Introduction 4 | 5 | The intention of this example is to show how to get a Bluetooth scanning application (HTML+JS) running on your BrightSign player. 6 | 7 | The application is defined in `index.html` which also includes a `script` section for the JavaScript bits. There is an `autorun.brs` file which tells the player to run the application. 8 | 9 | Note that this example cannot be ran on a browser as it uses BrightSign's proprietary JavaScript API. 10 | 11 | ## Prerequisites 12 | 13 | 1. **Bluetooth adapter**: The BrightSign player should have a Bluetooth adapter to scan for nearby devices. See [this page](https://www.brightsign.biz/wp-content/uploads/2023/04/Wifi-Modules-Bluetooth-Datasheet.pdf) for more information on BrightSign Wi-Fi and Bluetooth modules. 14 | 15 | 2. [optional] **Wi-Fi or Ethernet Network**: ONLY applicable if using the [Automated Transfer](#2-automated-transfer-using-brightsign-cli-for-dws) method below. Both your BrightSign player and the computer should be connected to the same Wi-Fi or Ethernet network. 16 | 17 | ## Steps to Set Up and Run the Application 18 | 19 | After the application is ready to be deployed, you need to transfer the required files to your BrightSign player. There are 2 ways of doing this: 20 | 21 | ### 1. Manual Transfer 22 | 1. Copy the `autorun.brs` and `index.html` files at the root of the SD card. 23 | 2. Insert the SD card into the player. 24 | 3. Reboot the player. 25 | 26 | ### 2. Automated Transfer Using BrightSign CLI for DWS 27 | BrightSign's player CLI: [player-CLI](https://www.npmjs.com/package/@brightsign/bsc). To deploy this app with the CLI: 28 | 29 | Configure the CLI by choosing a name for your player and passing your player's information: 30 | 31 | `bsc local player --add --player playerName --ip ip-address --user username --pass password --storage sd` 32 | 33 | **Note**: The "username" and "password" are the credentials for the [Local DWS](#3-local-dws-ui) of the BrightSign player. 34 | 35 | This is an example command for pushing files to your player: 36 | 37 | `bsc local file --upload --player playerName --file ./path-to-your-file --destination sd/path-on-player` 38 | 39 | ### 3. Local DWS UI 40 | To access the Local DWS, go to the IP address of your player in a web browser. The default username is `"admin"` and the password is the player serial number. 41 | 42 | **Note**: If you don't know the IP address of the player, you can boot up the player WITHOUT an SD card, and after a few seconds, the splash screen will display it, along with the player's serial number and OS version. 43 | 44 | 1. Once logged in to the DWS, go to the `SD` tab. 45 | 2. Click on the `Browse` button in the `Upload Files` section. 46 | 3. Select the `autorun.brs` and `index.html` files from your computer. 47 | 4. Click on the `Upload` button to transfer the files to the player. 48 | 5. Once the files are uploaded, you can reboot the player to run the application. 49 | 6. You can also reboot the player using the `Reboot` button under the `Control` tab. 50 | 51 | ## Troubleshooting 52 | 53 | - **No HTML content**: If you don't see anything on the display attached to the player, verify that: 54 | - The BrightSign player is connected to a display via an HDMI cable. 55 | - The required files are correctly copied to the root of the SD card. 56 | 57 | - **Bluetooth scanning not working**: If the Bluetooth scanning is not working, verify that: 58 | - The BrightSign player has a Bluetooth adapter attached. 59 | - The player is at least on OS v9.0.199. 60 | 61 | In order to troubleshoot any issues, it is recommended to use the BrightSign Shell. The shell can be accessed by connecting a serial cable to the BrightSign player and using a terminal emulator (e.g. PuTTY) to connect to the player. More details can be found [here](https://docs.brightsign.biz/space/DOC/1988100153/BrightSign+Shell). 62 | 63 | If a serial cable is not available, you can make use of [telnet or SSH](https://docs.brightsign.biz/space/DOC/370673607/Telnet+and+SSH) as well. -------------------------------------------------------------------------------- /docs/node-js-notes.md: -------------------------------------------------------------------------------- 1 | # Using Node.js with your project 2 | 3 | As you work with the sample code in this repo, you will find references to `@brightsign` JavaScript modules. These modules provide BrightSign specific functionality and are built in to the device firmware. These modules can be integrated with your code to provide rich experiences on the device. 4 | 5 | There are two main ways to run Node.js on your Brightsign device. 6 | 7 | # roHtmlWidget 8 | 9 | Create an [roHtmlWidget](https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget) object using BrightScript in your `autorun.brs` with the `nodejs_enabled` flag set to `true`. This creates a Chromium HTML renderer instance that can access the BrightSign JS modules. Set the URL of the widget to be an externally hosted page or to a file on your local filesystem. You can then set the size and position of the widget to be displayed on your device. This essentially creates a floating browser window that displays content of your choice. 10 | 11 | ``` 12 | aa=createobject("roassociativearray") 13 | aa.url="http://test-server/index.html" 14 | aa.nodejs_enabled=true 15 | 16 | r=createobject("rorectangle",0,0,1920,1080) 17 | h=createobject("rohtmlwidget",r,aa) 18 | h.show() 19 | ``` 20 | 21 | Keep in mind that any JavaScript code in your `roHtmlWidget` will only be run as long as the widget is active. You can create and display multiple instances at once. 22 | 23 | If you created a widget and it is not visible, the following could be happening: 24 | 25 | 1. `show()` is `false` 26 | 2. `roRectangle` has x and y values which are off the screen, 27 | 3. Your `index.html` doesn't render anything and contains a "visual passthrough" 28 | 4. Your content is at 100% opacity. 29 | 30 | ![roHtmlWidget](./images/rohtmlwidget.png) 31 | 32 | 33 | # roNodeJs 34 | 35 | Launch a Node.js process in the background in BrightScript using [roNodeJs]( https://brightsign.atlassian.net/wiki/spaces/DOC/pages/404619466/roNodeJs). Supply any arguments you might need. Your code will need to be on your local filesystem. 36 | 37 | ``` 38 | node = CreateObject("roNodeJs", "index.js", {message_port:my_message_port, node_arguments: ["arg"], arguments: ["arg1", "arg2"]}) 39 | ``` 40 | 41 | Unlike `roHtmlWidget`, an `roNodeJs` object will run in the background uninterrupted. You can use this for long running processes like gathering metrics or running a web server. You can even pass messages between this process and code running in an `roHtmlWidget` using an `roNodeJsEvent`. The message would be received in by the OS layer's `roMessagePort` as an `roNodeJsEvent`. To send it to an `roHtmlWidget` you must execute `roMessagePort.SendJsMessage()` in your widget. 42 | 43 | The `cra-template-` examples use an `roNodeJs` to host a static server (see `template/src/server/index.js`) which serves the frontend HTML and other files. In `autorun.brs` you can see how the config is actually pointed at the Node.js process running the server, not an HTML file. 44 | 45 | You can also inspect the running process by setting `"--inspect=0.0.0.0:2999"` in `node_arguments`. In Chrome, open DevTools at `chrome://inspect` and configure your network targets. 46 | 47 | ``` 48 | config = { 49 | nodejs_enabled: true 50 | inspector_server: { 51 | port: 3000 52 | } 53 | url: "http://localhost:8020" 54 | port: mp 55 | } 56 | ``` 57 | 58 | ![roNodeJs](./images/ronodejs.png) 59 | 60 | 61 | # Conclusion 62 | 63 | Use `roNodeJs` if you need a long running background process or have more complex needs. Use `roHtmlWidget` with Node.js enabled for browser-based apps. Both have access to BrightSign [JavaScript APIs](https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370678188/JavaScript+APIs). 64 | 65 | ### References / Further Reading 66 | 67 | roNodeJs: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/404619466/roNodeJs 68 | roHtmlWidget: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget 69 | JavaScript APIs: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370678188/JavaScript+APIs 70 | roNodeJsEvent: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/404621871/roNodeJsEvent 71 | 72 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-dashboard/template/README.md: -------------------------------------------------------------------------------- 1 | ## Intro 2 | 3 | This sample shows how you can run a dashboard that hosts an API and a React frontend on the same port. This displays device information, API data, and can be extended for your use case. 4 | 5 | ## How it works 6 | 7 | The app builds two bundles via Webpack: `frontend.js` and `backend.js`. 8 | 9 | - `backend.js` runs an Express server that serves `index.html` and other static files as well as any API endpoints that you wish to build. This is built from the code in `src/server/index.js`. 10 | - `frontend.js` is built from `src/index.js` and contains all the React dependencies and code. `index.html` loads it from the statically hosted files. 11 | 12 | When the project is built, generated code is placed in the `/dist` directory. This code and everything in the `public` directory needs to be pushed to the device at `/sd/dist` and run by the `autorun.brs` script. 13 | 14 | ## Using the sample 15 | 16 | To deploy your code, you will need the device to be configured for DWS access, the device's IP address, and its serial number. Simply navigate to the root of the directory and run the following command to push the code and restart the device. 17 | 18 | ``` 19 | PLAYER=your.device.ip.address PLAYER_PW=XCG31D001234 npm run put:prod 20 | ``` 21 | 22 | Updating the header text 23 | 24 | ``` 25 | curl -d '{"text": "hello world" }' -H 'Content-Type: application/json' -X POST your.device.ip.address:8020/text 26 | ``` 27 | 28 | ## Debugging 29 | 30 | To debug your web application you can enable the `Inspector Server` allowing the Chrome DevTools to connect over the local network. See the _Debugging Webpages_ section in [HTML Best Practices](https://brightsign.atlassian.net/wiki/x/ngIYFg) for more info. 31 | 32 | ## Bundling 33 | 34 | For most use cases, leveraging a bundling tool like [webpack](https://webpack.js.org/) is recommended to minimize the dependency graph from one or more entry points and many modules, into either 1 or a few entry points. 35 | 36 | ## Deployment for Local Development 37 | 38 | There are many means of deploying software to a BrightSign player. Common methods include: 39 | 40 | 1. Push your software to the Local Diagnostic Web Server (DWS) either through the Local DWS UI or a REST Client Tool. 41 | 1. The `~/scripts/put` shell script could be leveraged. 42 | 2. Coming soon: Improved tooling to push software to the Player independent of BrightAuthor:connected 43 | 44 | ## Deploy through an Authoring Application 45 | 46 | 1. Leverage a CMS to run HTML, CSS, JS and / or Node.js managing the application as content is running. 47 | 2. Author a BrightAuthor:connected Presentation to load your local application(s). 48 | 49 | HTML 5 Widget in a Presentation loads locally provided .html file. The .html file is the entry file for HTML, CSS, JavaScript. To execute within the Node.js runtime, then _Enable Node.js_. 50 | 51 | A Node.js Zone in a Presentation is used to execute within the Node.js runtime when the entry file is JavaScript. 52 | 53 | ## How to check for logs 54 | 55 | Using the BrightSign CLI: 56 | 57 | ``` 58 | bsc getlogs playerName | grep "my message" | tail 59 | ``` 60 | 61 | If you are not using the BrightSign CLI, you can check for `console.log` messages in your device log file. 62 | 63 | 1. Find your device in BrightAuthor: Connected and click the gear icon. 64 | 2. Go to the "LOG" tab and click "Download Log" 65 | 66 | Search for a string like the following: 67 | 68 | `[ 12.858] [INFO] [source file:///sd:/dist/bundle.js:2]: console log message...` 69 | 70 | You can also use SSH to access the device and view log messages in realtime. 71 | 72 | ### Building on Mac M1 73 | 74 | You might see an error like `npm ERR! Error: Cannot find module 'node-bin-darwin-arm64/package.json'` 75 | 76 | Run the following commands 77 | 78 | ``` 79 | > node -v 80 | v14.17.6 81 | > node -p process.arch 82 | arm64 83 | > arch -x86_64 zsh 84 | > nvm remove 14.17.6 && nvm install 14.17.6 85 | ``` 86 | 87 | You might need to do this each time you restart your terminal. 88 | 89 | https://stackoverflow.com/questions/68896696/having-trouble-installing-npm-on-mac-m1 90 | -------------------------------------------------------------------------------- /templates/cra-template-brightsign-app/template/README.md: -------------------------------------------------------------------------------- 1 | ## Intro 2 | 3 | This sample shows how you can run a simple Node.js service that hosts an API and a React frontend on the same port. You can update the displayed text using the `/text` endpoint. 4 | 5 | ## How it works 6 | 7 | The app builds two bundles via Webpack: `frontend.js` and `backend.js`. 8 | 9 | - `backend.js` runs an Express server that serves `index.html` and other static files as well as any API endpoints that you wish to build. This is built from the code in `src/server/index.js`. 10 | - `frontend.js` is built from `src/index.js` and contains all the React dependencies and code. `index.html` loads it from the statically hosted files. 11 | 12 | When the project is built, generated code is placed in the `/dist` directory. This code and everything in the `public` directory needs to be pushed to the device at `/sd/dist` and run by the `autorun.brs` script. 13 | 14 | ## Using the sample 15 | 16 | You can run the React app locally using mocked device data with `yarn start`. Once the code is pushed to the device it will use the built-in @brightsign modules for real data. 17 | 18 | To deploy your code, you will need the device to be configured for DWS access, the device's IP address, and its serial number. Simply navigate to the root of the directory and run the following command to push the code and restart the device. 19 | 20 | ``` 21 | PLAYER=your.device.ip.address PLAYER_PW=XCG31D001234 npm run put:prod 22 | ``` 23 | 24 | Updating the header text 25 | 26 | ``` 27 | curl -d '{"text": "hello world" }' -H 'Content-Type: application/json' -X POST your.device.ip.address:8020/text 28 | ``` 29 | 30 | ## Debugging 31 | 32 | To debug your web application, you can enable the`Inspector Server`, allowing the Chrome DevTools to connect over the local network. See the _Debugging Webpages_ section in [HTML Best Practices](https://brightsign.atlassian.net/wiki/x/ngIYFg) for more info. 33 | 34 | ## Bundling 35 | 36 | For most use cases, leveraging a bundling tool like [webpack](https://webpack.js.org/) is recommended to minimize the dependency graph from one or more entry points and many modules, into either 1 or a few entry points. 37 | 38 | ## Deployment for Local Development 39 | 40 | There are many means of deploying software to a BrightSign player. Common methods include: 41 | 42 | 1. Push your software to the Local Diagnostic Web Server (DWS) either through the Local DWS UI or a REST Client Tool. 43 | 1. The `~/scripts/put` shell script could be leveraged. 44 | 2. Coming soon: Improved tooling to push software to the Player independent of BrightAuthor:connected 45 | 46 | ## Deploy through an Authoring Application 47 | 48 | 1. Leverage a CMS to run HTML, CSS, JS and / or Node.js managing the application as content running. 49 | 2. Author a BrightAuthor:connected Presentation to load your local application(s). 50 | 51 | HTML 5 Widget in a Presentation loads locally provided .html file. The .html file is the entry file for HTML, CSS, JavaScript. To execute within the Node.js runtime, the _Enable Node.js_. 52 | 53 | A Node.js Zone in a Presentation is used to execute within the Node.js runtime when the entry file is JavaScript. 54 | 55 | ## How to check for logs 56 | 57 | Using the BrightSign CLI: 58 | 59 | ``` 60 | bsc getlogs playerName | grep "my message" | tail 61 | ``` 62 | 63 | If you are not using the BrightSign CLI, you can check for `console.log` messages in your device log file. 64 | 65 | 1. Find your device in BrightAuthor: Connected and click the gear icon. 66 | 2. Go to the "LOG" tab and click "Download Log" 67 | 68 | Search for a string like the following: 69 | 70 | `[ 12.858] [INFO] [source file:///sd:/dist/bundle.js:2]: console log message...` 71 | 72 | You can also use SSH to access the device and view log messages in realtime. 73 | 74 | ### Building on Mac M1 75 | 76 | You might see an error like `npm ERR! Error: Cannot find module 'node-bin-darwin-arm64/package.json'` 77 | 78 | Run the following commands 79 | 80 | ``` 81 | > node -v 82 | v14.17.6 83 | > node -p process.arch 84 | arm64 85 | > arch -x86_64 zsh 86 | > nvm remove 14.17.6 && nvm install 14.17.6 87 | ``` 88 | 89 | You might need to do this each time you restart your terminal. 90 | 91 | https://stackoverflow.com/questions/68896696/having-trouble-installing-npm-on-mac-m1 92 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.2.20](https://github.com/brightsign/dev-cookbook/compare/v1.2.19...v1.2.20) (2024-06-26) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **package.json:** rename publish command ([#55](https://github.com/brightsign/dev-cookbook/issues/55)) ([11b6f73](https://github.com/brightsign/dev-cookbook/commit/11b6f7398cf086537b35399045b143e0ab96610e)) 9 | 10 | ## [1.2.19](https://github.com/brightsign/dev-cookbook/compare/v1.2.18...v1.2.19) (2024-06-26) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * renaming due to package name conflict ([#53](https://github.com/brightsign/dev-cookbook/issues/53)) ([6c940de](https://github.com/brightsign/dev-cookbook/commit/6c940de7e2c57d16248293e2c75b080dff78a77c)) 16 | 17 | ## [1.2.18](https://github.com/brightsign/dev-cookbook/compare/v1.2.17...v1.2.18) (2024-06-26) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * .npmrc token overrides env var token ([#51](https://github.com/brightsign/dev-cookbook/issues/51)) ([9d70660](https://github.com/brightsign/dev-cookbook/commit/9d7066052c342a8816099d78be42fb08618459e5)) 23 | 24 | ## [1.2.17](https://github.com/brightsign/dev-cookbook/compare/v1.2.16...v1.2.17) (2024-06-26) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * set public package publishing flag ([#49](https://github.com/brightsign/dev-cookbook/issues/49)) ([b223e09](https://github.com/brightsign/dev-cookbook/commit/b223e09a34e40f0ff3e7f09809ccccab2d6f4841)) 30 | 31 | ## [1.2.16](https://github.com/brightsign/dev-cookbook/compare/v1.2.15...v1.2.16) (2024-06-11) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * add publishconfig and repository info for npm package publish ([c64f728](https://github.com/brightsign/dev-cookbook/commit/c64f728b7dc6f92b3464833350f079d7084d9a6e)) 37 | 38 | ## [1.2.15](https://github.com/brightsign/dev-cookbook/compare/v1.2.14...v1.2.15) (2024-06-10) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * rename packages for npm publishing ([9347f66](https://github.com/brightsign/dev-cookbook/commit/9347f66ff3aa02a1d5c34b4789a4c2925b84a8c9)) 44 | 45 | ## [1.2.14](https://github.com/brightsign/dev-cookbook/compare/v1.2.13...v1.2.14) (2024-06-07) 46 | 47 | 48 | ### Miscellaneous Chores 49 | 50 | * release 1.2.14 ([c555268](https://github.com/brightsign/dev-cookbook/commit/c555268edba2a443cff6486c4dbfeb3655c64f3b)) 51 | 52 | ## [1.2.7](https://github.com/brightsign/dev-cookbook/compare/v1.2.6...v1.2.7) (2024-05-31) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * quick publish fix ([2cc4a65](https://github.com/brightsign/dev-cookbook/commit/2cc4a65ef4fbb1e1f671aa890e37f5ae62f96faf)) 58 | 59 | ## [1.2.0](https://github.com/brightsign/dev-cookbook/compare/v1.1.2...v1.2.0) (2024-05-08) 60 | 61 | 62 | ### Features 63 | 64 | * Husky Pre-commit hooks ([#18](https://github.com/brightsign/dev-cookbook/issues/18)) ([de35ee0](https://github.com/brightsign/dev-cookbook/commit/de35ee051e119fe0fa8f133abfcc83e6a72840b1)) 65 | 66 | ## [1.1.2](https://github.com/brightsign/dev-cookbook/compare/v1.1.1...v1.1.2) (2024-04-19) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * update publish config and release PAT ([7985339](https://github.com/brightsign/dev-cookbook/commit/79853392c84093c5d449affdf828fc1f3b2a6fa1)) 72 | 73 | ## [1.1.0](https://github.com/brightsign/dev-cookbook/compare/v1.0.2...v1.1.0) (2024-04-16) 74 | 75 | 76 | ### Features 77 | 78 | * publish to github package registry ([aef07e5](https://github.com/brightsign/dev-cookbook/commit/aef07e5f2cd9a154baff02ea3b2d9f9f28eb0868)) 79 | 80 | ## 1.0.0 (2024-04-16) 81 | 82 | ### Features 83 | 84 | * Add tests for templates and nodejs examples ([#10](https://github.com/brightsign/dev-cookbook/issues/10)) ([3fe286c](https://github.com/brightsign/dev-cookbook/commit/3fe286c92a32e841b1900403d3f783565a1e9407)) 85 | * issue templates ([4c1f93f](https://github.com/brightsign/dev-cookbook/commit/4c1f93f52069aa52bf7fadd44dfa3d85b415c56c)) 86 | 87 | 88 | ### Bug Fixes 89 | 90 | * change from port 80 to 8020 ([f3c0196](https://github.com/brightsign/dev-cookbook/commit/f3c019603be8d13660f02d02b228382d35c6e999)) 91 | * remove console ([19b507c](https://github.com/brightsign/dev-cookbook/commit/19b507c67686ffffd90f98bea36021b3881b1b4b)) 92 | * template updates ([f515e9b](https://github.com/brightsign/dev-cookbook/commit/f515e9b57efa6df97daa1d8f5c3b568c9db62fe6)) 93 | * update the directory path for static files ([375c879](https://github.com/brightsign/dev-cookbook/commit/375c8793fb1f77020d7b4e6f85383ac88f97ff13)) 94 | -------------------------------------------------------------------------------- /examples/send-plugin-message/pluginMessageTransfer.brs: -------------------------------------------------------------------------------- 1 | ' Note: The plugin's filename can be anything, as long as the presentation the plugin is added to has the 2 | ' appropriate filename (e.g. 'pluginMessageTransfer.brs') assigned. 3 | 4 | ' Note: the name of the plugin must be defined by the suffix '_Initialize', for example, pluginMessage 5 | ' is the name of the plugin defined by the function, pluginMessage_Initialize 6 | Function pluginMessage_Initialize(msgPort As Object, userVariables As Object, bsp as Object) 7 | 'no spaces in names 8 | 9 | print "=== pluginMessage_Initialize() - entry" 10 | pluginMessage = newPluginMessage(msgPort, userVariables, bsp) 11 | print "=== pluginMessage_Initialize() - exit" 12 | 13 | return pluginMessage 14 | End Function 15 | 16 | 17 | Function newPluginMessage(msgPort As Object, userVariables As Object, bsp as Object) 18 | print "=== newPluginMessage() - entry" 19 | 20 | s = {} 21 | s.version = 0.1 22 | s.msgPort = msgPort 23 | s.userVariables = userVariables 24 | s.bsp = bsp 25 | s.ProcessEvent = pluginMessage_ProcessEvent 26 | s.objectName = "pluginMessage_object" 27 | s.systemLog = CreateObject("roSystemLog") 28 | 29 | print "=== newPluginMessage() - exit" 30 | return s 31 | End Function 32 | 33 | ' Note: the suffix '_ProcessEvent', for example, pluginMessage 34 | ' is the name of the plugin defined by the function, pluginMessage_ProcessEvent 35 | Function pluginMessage_ProcessEvent(event As Object) as boolean 36 | 37 | ' retval should be false if the autorun should handle the event, even if the plugin also handled the event 38 | ' retval should be true if the autorun should NOT handle the event, the plugin instead handled the event 39 | retval = false 40 | m.systemLog.SendLine("=== pluginMessage_ProcessEvent() - event:") 41 | m.systemLog.SendLine(type(event)) 42 | if type(event) = "roAssociativeArray" then ' Receive a message from BA 43 | if type(event["EventType"]) = "roString" 44 | if event["EventType"] = "SEND_PLUGIN_MESSAGE" then 45 | if event["PluginName"] = "pluginMessage" then 46 | pluginMessage$ = event["PluginMessage"] 47 | retval = ParsePluginMessage(pluginMessage$, m) 48 | end if 49 | end if 50 | end if 51 | else if type(event) = "roHtmlWidgetEvent" then 52 | payload = event.GetData() 53 | if payload.reason = "message" then 54 | m.systemLog.SendLine("=== Received Node message: " + payload.message.result) 55 | end if 56 | end if 57 | return retval 58 | End Function 59 | 60 | 61 | Function ParsePluginMessage(origMsg as String, h as Object) as boolean 62 | 63 | retval = false 64 | msg = lcase(origMsg) 65 | h.systemLog.SendLine("=== Received Plugin message: " + msg) 66 | r = CreateObject("roRegex", "^plugin", "i") 67 | match = r.IsMatch(msg) 68 | if match then 69 | retval = true 70 | ' split the string, !! is the field seperator 71 | ' pluginMessage!!!! 72 | r2 = CreateObject("roRegex", "!!", "i") 73 | fields = r2.split(msg) 74 | command = fields[0] 75 | 76 | if h.html = invalid then 77 | h.html = FindHTMLWidget(h.bsp) 78 | if h.html = invalid then 79 | return true 80 | end if 81 | end if 82 | 83 | if command = "pluginMessage" then 84 | serialNumber = fields[1] 85 | filename = fields[2] 86 | h.systemLog.SendLine("=== File " + filename + " showing ended and reported from player " + serialNumber) 87 | h.html.PostJsMessage({ serialNumber: serialNumber, filename: filename }) 88 | end if 89 | if command = "plugin" then 90 | serialNumber = fields[2] 91 | filename = fields[3] 92 | h.systemLog.SendLine("=== File " + filename + " showing ended and reported from player 222 " + serialNumber) 93 | h.html.PostJsMessage({ serialNumber: serialNumber, filename: filename }) 94 | end if 95 | end if 96 | 97 | return retval 98 | End Function 99 | 100 | Function ParseNodeMessage(origMsg as Object, h as Object) as boolean 101 | retval = false 102 | if origMsg.reason = "message" then 103 | h.systemLog.SendLine("=== Received Node message complete: " + origMsg.message.complete + " ; message: " + origMsg.message.result) 104 | end if 105 | return retval 106 | End Function 107 | 108 | ' Find the HTML widget in the bsp to ensure there is an app to receive messages 109 | Function FindHTMLWidget(bsp) 110 | for each baZone in bsp.sign.zonesHSM 111 | print baZone.loadingHtmlWidget 112 | if baZone.loadingHtmlWidget <> invalid then 113 | print "=== Found HTML!" 114 | return baZone.loadingHtmlWidget 115 | end if 116 | end for 117 | 118 | print "=== Couldn't find htmlwidget" 119 | return invalid 120 | End Function 121 | -------------------------------------------------------------------------------- /examples/node-simple-server/README.md: -------------------------------------------------------------------------------- 1 | # Hosting a Node.js Server Example 2 | 3 | ## Overview 4 | 5 | This example is designed to help you get familiarized with running Node.js on a BrightSign player. It demonstrates how to run a Node.js server directly on the player that can serve both static files and fetch device information. Additionally, it introduces tools and configurations that go beyond the basics covered in starter examples. 6 | 7 | The core Node.js application is defined in `app.js`, which: 8 | - Serves static files from the `/storage/sd/` directory 9 | - Provides device information via a REST API endpoint 10 | - Includes proper content-type handling for common file types 11 | 12 | ## Static File Serving 13 | 14 | This example includes a built-in static file server that serves files from the `/storage/sd/` directory. The server: 15 | - Automatically serves `index.html` when accessing the root URL (`/`) 16 | - Handles common file types with appropriate content-type headers (HTML, CSS, JavaScript, images) 17 | - Returns 404 errors for files that don't exist 18 | 19 | ### Example Usage 20 | 21 | 1. Create an `index.html` file in your SD card root: 22 | ```html 23 | 24 | 25 | 26 | My BrightSign App 27 | 28 | 29 |

Hello from BrightSign!

30 |
31 | 41 | 42 | 43 | ``` 44 | 45 | 2. Access your content: 46 | - Main page: `http://:13131/` 47 | - Individual files: `http://:13131/styles.css`, `http://:13131/images/logo.png`, etc. 48 | 49 | 3. Supported file types: 50 | - HTML (`.html`): `text/html` 51 | - JavaScript (`.js`): `text/javascript` 52 | - CSS (`.css`): `text/css` 53 | - JSON (`.json`): `application/json` 54 | - Images (`.png`, `.jpg`): `image/png`, `image/jpeg` 55 | - Other files: `text/plain` 56 | 57 | ## Building and Running the Application 58 | 59 | ### Step 1: Install Dependencies 60 | Ensure that Node.js is installed on your machine. From your project directory, install the necessary dependencies by running the following command: 61 | ```bash 62 | npm install 63 | ``` 64 | 65 | Next, build and bundle the application: 66 | ```bash 67 | npm run build 68 | ``` 69 | 70 | ### Step 2: Transfer Files to the Player 71 | 72 | #### Manual Transfer 73 | After the application is bundled, you need to transfer the required files to your BrightSign player: 74 | 1. Copy the `dist` folder to the SD card 75 | 2. Place the `autorun.brs` file at the root of the SD card 76 | 3. Place any static files (HTML, CSS, images) you want to serve in the root of the SD card 77 | 4. Insert the SD card into the player 78 | 5. Reboot the player 79 | 80 | #### Automated Transfer Using BrightSign CLI for DWS 81 | BrightSign's player CLI: [player-CLI](https://www.npmjs.com/package/@brightsign/bsc). To deploy this app with the CLI: 82 | 83 | Configure the CLI by choosing a name for your player and passing your player's information: 84 | ```sh 85 | bsc local player --add --player playerName --ip ip-address --user username --pass password --storage sd 86 | ``` 87 | 88 | This is an example command for pushing files to your player: 89 | ```sh 90 | bsc local file --upload --player playerName --file ./path-to-your-file --destination sd/path-on-player 91 | ``` 92 | 93 | Alternatively, there is a script that can be run that is configured for this application: 94 | ```sh 95 | npm run upload --playerName=playerName 96 | ``` 97 | 98 | ### Step 3: Access the Server 99 | 100 | Once the player is running, you can access the Node.js server from a web browser. Make sure your computer is connected to the same network as the BrightSign player. 101 | 102 | #### Available Endpoints 103 | 104 | 1. Static Files: 105 | ``` 106 | http://:13131/ 107 | ``` 108 | Serves files from the `/storage/sd/` directory. The root URL (`/`) will serve `index.html` if present. 109 | 110 | 2. Device Info API: 111 | ``` 112 | http://:13131/api/device-info 113 | ``` 114 | Returns JSON with device information like model, OS version, and serial number. 115 | 116 | ## Testing and mocking 117 | 118 | This example is equipped with basic mocking to make testing easier with local development. In the `__mocks__` directory, there is a `deviceinfo.js` file which defines mock values when the application is run in a development environment. 119 | 120 | To run the test(s) located in `App.test.js`, execute the following: 121 | ```bash 122 | npm run test -------------------------------------------------------------------------------- /examples/bs-self-updater/index.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import decompress from "decompress"; 3 | import md5File from "md5-file"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | 7 | // @ts-ignore 8 | import { System } from "@brightsign/system"; 9 | 10 | // Configurable values 11 | const CHECK_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes 12 | const SERVER_URL = "http://localhost:7000/autorun.zip"; // Update to your server URL 13 | const STORAGE_PATH = "/storage/sd"; 14 | const TMP_PATH = "/storage/tmp"; 15 | const extensionsToCheck = [".brs", ".html", ".js", ".json"]; 16 | 17 | async function sleep(ms: number) { 18 | return new Promise((resolve) => setTimeout(resolve, ms)); 19 | } 20 | 21 | async function doesFileExist(filePath: string): Promise { 22 | try { 23 | await fs.promises.access(filePath, fs.constants.F_OK); 24 | return true; 25 | } catch { 26 | return false; 27 | } 28 | } 29 | 30 | async function downloadAndUnzipFile( 31 | url: string, 32 | dest: string 33 | ): Promise { 34 | try { 35 | const res = await fetch(url); 36 | if (res.status === 200) { 37 | const buffer = Buffer.from(await res.arrayBuffer()); 38 | await decompress(buffer, dest); 39 | console.log(`Downloaded zip and unzipped contents to ${dest}`); 40 | return true; 41 | } else { 42 | const err = await res.json(); 43 | console.error(`Server error: ${JSON.stringify(err)}`); 44 | return false; 45 | } 46 | } catch (e) { 47 | console.error(`Download failed: ${e}`); 48 | return false; 49 | } 50 | } 51 | 52 | async function backupFiles(storagePath: string): Promise { 53 | const filesToBackup = await findFiles(storagePath, extensionsToCheck); 54 | for (const file of filesToBackup) { 55 | const filename = file.replace(/^.*[\\/]/, ""); 56 | const backupFile = path.join(TMP_PATH, filename); 57 | await fs.promises.copyFile(file, backupFile); 58 | console.log(`Backed up ${file} to ${backupFile}`); 59 | } 60 | } 61 | 62 | async function restoreFiles(storagePath: string): Promise { 63 | const filesToRestore = await findFiles(TMP_PATH, extensionsToCheck); 64 | for (const file of filesToRestore) { 65 | const filename = file.replace(/^.*[\\/]/, ""); 66 | const originalFile = path.join(storagePath, filename); 67 | await fs.promises.copyFile(file, originalFile); 68 | console.log(`Restored ${file} to ${originalFile}`); 69 | } 70 | } 71 | 72 | async function findFiles(dir: string, exts: string[]): Promise { 73 | let results: string[] = []; 74 | const files = await fs.promises.readdir(dir); 75 | for (const file of files) { 76 | const fullPath = path.join(dir, file); 77 | const stat = await fs.promises.stat(fullPath); 78 | if (stat.isDirectory()) { 79 | results = results.concat(await findFiles(fullPath, exts)); 80 | } else if (exts.some((ext) => file.endsWith(ext))) { 81 | results.push(fullPath); 82 | } 83 | } 84 | return results; 85 | } 86 | 87 | async function checksum(filePath: string): Promise { 88 | try { 89 | return await md5File(filePath); 90 | } catch { 91 | return null; 92 | } 93 | } 94 | 95 | async function reboot() { 96 | try { 97 | console.log("Rebooting device..."); 98 | new System().reboot(); 99 | } catch (e: any) { 100 | console.error("Failed to reboot:", e.message); 101 | } 102 | } 103 | 104 | async function processUpdate() { 105 | await backupFiles(STORAGE_PATH); 106 | 107 | const downloaded = await downloadAndUnzipFile(SERVER_URL, STORAGE_PATH); 108 | if (!downloaded) return; 109 | 110 | const autorunPath = path.join(STORAGE_PATH, "autorun.brs"); 111 | if (!(await doesFileExist(autorunPath))) { 112 | console.error( 113 | "No autorun.brs script found after unzip. Restoring backup and rebooting." 114 | ); 115 | await restoreFiles(STORAGE_PATH); 116 | await reboot(); 117 | return; 118 | } 119 | 120 | const tmpFiles = await findFiles(TMP_PATH, extensionsToCheck); 121 | for (const file of tmpFiles) { 122 | const filename = file.replace(/^.*[\\/]/, ""); 123 | const newFile = path.join(STORAGE_PATH, filename); 124 | if (await doesFileExist(newFile)) { 125 | const oldSum = await checksum(file); 126 | const newSum = await checksum(newFile); 127 | if (newSum !== oldSum) { 128 | console.log(`${file} changed. Rebooting.`); 129 | await reboot(); 130 | return; 131 | } 132 | } 133 | } 134 | } 135 | 136 | async function main() { 137 | while (true) { 138 | try { 139 | await processUpdate(); 140 | } catch (e) { 141 | console.error("Error in update loop:", e); 142 | } 143 | await sleep(CHECK_INTERVAL_MS); 144 | } 145 | } 146 | 147 | main(); 148 | -------------------------------------------------------------------------------- /examples/bluetooth-scan/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bluetooth Device Scanning App 8 | 23 | 24 | 25 | 26 |
27 |

Success!

28 |

29 | Scanning for Bluetooth devices... 30 | Found 0 unique devices. 31 | Found 0 unique advertising reports. 32 |

33 |
34 | 35 | 140 | 141 | 142 | 143 | --------------------------------------------------------------------------------