A TypeScript/JavaScript library for interfacing web applications with the Revel Digital player. This SDK provides a unified API for communication between your web content and the Revel Digital digital signage platform.
- 🎯 Event Handling: Listen for player events (start, stop, commands)
- 🔄 Two-way Communication: Send commands and callbacks to the player
- 🌍 Device Information: Access device timezone, language, and location data
- 📊 Analytics: Track custom events with AdHawk analytics
- 🎛️ Preferences: Access user preferences via the Gadgets API
- ⚛️ Framework Support: Works with React, Angular, Vue, and vanilla JavaScript
- 🧪 Testable: Run and test gadgets with no player attached via
createMockPlayer()
npm install @reveldigital/client-sdkimport { createPlayerClient, EventType } from "@reveldigital/client-sdk";
const client = createPlayerClient();
// Listen for player events
client.on(EventType.START, () => {
console.log('Player started');
});
// Get device information
const deviceTime = await client.getDeviceTime();
const deviceKey = await client.getDeviceKey();
// Send a callback to the player
client.callback('hello', 'world');- Framework Integration
- API Reference
- Testing
- Best Practices
- Deployment with GitHub Actions
- TypeScript Support
- Development
- Resources
// hooks/usePlayerClient.ts
import { useEffect, useState, useCallback } from 'react';
import { createPlayerClient, EventType, PlayerClient } from '@reveldigital/client-sdk';
export const usePlayerClient = () => {
const [client] = useState<PlayerClient>(() => createPlayerClient());
const [isPlayerActive, setIsPlayerActive] = useState(false);
const [deviceInfo, setDeviceInfo] = useState<{
key?: string;
timezone?: string;
language?: string;
}>({});
useEffect(() => {
// Set up event listeners
client.on(EventType.START, () => {
setIsPlayerActive(true);
});
client.on(EventType.STOP, () => {
setIsPlayerActive(false);
});
client.on(EventType.COMMAND, (data) => {
console.log('Received command:', data);
});
// Load device information
const loadDeviceInfo = async () => {
try {
const [key, timezone, language] = await Promise.all([
client.getDeviceKey(),
client.getDeviceTimeZoneName(),
client.getLanguageCode()
]);
setDeviceInfo({
key: key || undefined,
timezone: timezone || undefined,
language: language || undefined
});
} catch (error) {
console.error('Failed to load device info:', error);
}
};
loadDeviceInfo();
// Cleanup on unmount
return () => {
client.off(EventType.START);
client.off(EventType.STOP);
client.off(EventType.COMMAND);
};
}, [client]);
const sendCallback = useCallback((...args: any[]) => {
client.callback(...args);
}, [client]);
const trackEvent = useCallback((eventName: string, properties?: any) => {
client.track(eventName, properties);
}, [client]);
return {
client,
isPlayerActive,
deviceInfo,
sendCallback,
trackEvent
};
};// components/PlayerAwareComponent.tsx
import React, { useEffect } from 'react';
import { usePlayerClient } from '../hooks/usePlayerClient';
export const PlayerAwareComponent: React.FC = () => {
const { isPlayerActive, deviceInfo, sendCallback, trackEvent } = usePlayerClient();
useEffect(() => {
// Track component mount
trackEvent('component_mounted', { component: 'PlayerAwareComponent' });
}, [trackEvent]);
const handleButtonClick = () => {
sendCallback('button_clicked', new Date().toISOString());
trackEvent('user_interaction', { action: 'button_click' });
};
return (
<div className="player-component">
<h2>Player Status: {isPlayerActive ? 'Active' : 'Inactive'}</h2>
<div className="device-info">
<h3>Device Information</h3>
<p>Device Key: {deviceInfo.key || 'Unknown'}</p>
<p>Timezone: {deviceInfo.timezone || 'Unknown'}</p>
<p>Language: {deviceInfo.language || 'Unknown'}</p>
</div>
<button onClick={handleButtonClick}>
Send Callback to Player
</button>
</div>
);
};// context/PlayerContext.tsx
import React, { createContext, useContext, ReactNode } from 'react';
import { PlayerClient } from '@reveldigital/client-sdk';
import { usePlayerClient } from '../hooks/usePlayerClient';
interface PlayerContextType {
client: PlayerClient;
isPlayerActive: boolean;
deviceInfo: any;
sendCallback: (...args: any[]) => void;
trackEvent: (eventName: string, properties?: any) => void;
}
const PlayerContext = createContext<PlayerContextType | undefined>(undefined);
export const PlayerProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const playerData = usePlayerClient();
return (
<PlayerContext.Provider value={playerData}>
{children}
</PlayerContext.Provider>
);
};
export const usePlayer = () => {
const context = useContext(PlayerContext);
if (!context) {
throw new Error('usePlayer must be used within a PlayerProvider');
}
return context;
};// services/player.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { createPlayerClient, EventType, PlayerClient } from '@reveldigital/client-sdk';
interface DeviceInfo {
key?: string;
timezone?: string;
language?: string;
}
@Injectable({
providedIn: 'root'
})
export class PlayerService implements OnDestroy {
private client: PlayerClient;
private isPlayerActiveSubject = new BehaviorSubject<boolean>(false);
private deviceInfoSubject = new BehaviorSubject<DeviceInfo>({});
public isPlayerActive$: Observable<boolean> = this.isPlayerActiveSubject.asObservable();
public deviceInfo$: Observable<DeviceInfo> = this.deviceInfoSubject.asObservable();
constructor() {
this.client = createPlayerClient();
this.setupEventListeners();
this.loadDeviceInfo();
}
private setupEventListeners(): void {
this.client.on(EventType.START, () => {
this.isPlayerActiveSubject.next(true);
});
this.client.on(EventType.STOP, () => {
this.isPlayerActiveSubject.next(false);
});
this.client.on(EventType.COMMAND, (data) => {
console.log('Received command:', data);
});
}
private async loadDeviceInfo(): Promise<void> {
try {
const [key, timezone, language] = await Promise.all([
this.client.getDeviceKey(),
this.client.getDeviceTimeZoneName(),
this.client.getLanguageCode()
]);
this.deviceInfoSubject.next({
key: key || undefined,
timezone: timezone || undefined,
language: language || undefined
});
} catch (error) {
console.error('Failed to load device info:', error);
}
}
public sendCallback(...args: any[]): void {
this.client.callback(...args);
}
public trackEvent(eventName: string, properties?: any): void {
this.client.track(eventName, properties);
}
public async getDeviceTime(date?: Date): Promise<string | null> {
return this.client.getDeviceTime(date);
}
public sendCommand(name: string, arg: string): void {
this.client.sendCommand(name, arg);
}
ngOnDestroy(): void {
this.client.off(EventType.START);
this.client.off(EventType.STOP);
this.client.off(EventType.COMMAND);
}
}// components/player-aware.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { PlayerService } from '../services/player.service';
@Component({
selector: 'app-player-aware',
template: `
<div class="player-component">
<h2>Player Status: {{ isPlayerActive ? 'Active' : 'Inactive' }}</h2>
<div class="device-info">
<h3>Device Information</h3>
<p>Device Key: {{ deviceInfo?.key || 'Unknown' }}</p>
<p>Timezone: {{ deviceInfo?.timezone || 'Unknown' }}</p>
<p>Language: {{ deviceInfo?.language || 'Unknown' }}</p>
</div>
<button (click)="handleButtonClick()">
Send Callback to Player
</button>
</div>
`
})
export class PlayerAwareComponent implements OnInit, OnDestroy {
public isPlayerActive = false;
public deviceInfo: any = {};
private subscriptions = new Subscription();
constructor(private playerService: PlayerService) {}
ngOnInit(): void {
this.subscriptions.add(
this.playerService.isPlayerActive$.subscribe(
active => this.isPlayerActive = active
)
);
this.subscriptions.add(
this.playerService.deviceInfo$.subscribe(
info => this.deviceInfo = info
)
);
this.playerService.trackEvent('component_mounted', {
component: 'PlayerAwareComponent'
});
}
handleButtonClick(): void {
this.playerService.sendCallback('button_clicked', new Date().toISOString());
this.playerService.trackEvent('user_interaction', { action: 'button_click' });
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
}// composables/usePlayerClient.ts
import { ref, onMounted, onUnmounted } from 'vue';
import { createPlayerClient, EventType, PlayerClient } from '@reveldigital/client-sdk';
export const usePlayerClient = () => {
const client: PlayerClient = createPlayerClient();
const isPlayerActive = ref(false);
const deviceInfo = ref({
key: undefined as string | undefined,
timezone: undefined as string | undefined,
language: undefined as string | undefined,
});
const setupEventListeners = () => {
client.on(EventType.START, () => {
isPlayerActive.value = true;
});
client.on(EventType.STOP, () => {
isPlayerActive.value = false;
});
client.on(EventType.COMMAND, (data) => {
console.log('Received command:', data);
});
};
const loadDeviceInfo = async () => {
try {
const [key, timezone, language] = await Promise.all([
client.getDeviceKey(),
client.getDeviceTimeZoneName(),
client.getLanguageCode()
]);
deviceInfo.value = {
key: key || undefined,
timezone: timezone || undefined,
language: language || undefined
};
} catch (error) {
console.error('Failed to load device info:', error);
}
};
const sendCallback = (...args: any[]) => {
client.callback(...args);
};
const trackEvent = (eventName: string, properties?: any) => {
client.track(eventName, properties);
};
onMounted(() => {
setupEventListeners();
loadDeviceInfo();
});
onUnmounted(() => {
client.off(EventType.START);
client.off(EventType.STOP);
client.off(EventType.COMMAND);
});
return {
client,
isPlayerActive,
deviceInfo,
sendCallback,
trackEvent
};
};<!-- components/PlayerAwareComponent.vue -->
<template>
<div class="player-component">
<h2>Player Status: {{ isPlayerActive ? 'Active' : 'Inactive' }}</h2>
<div class="device-info">
<h3>Device Information</h3>
<p>Device Key: {{ deviceInfo.key || 'Unknown' }}</p>
<p>Timezone: {{ deviceInfo.timezone || 'Unknown' }}</p>
<p>Language: {{ deviceInfo.language || 'Unknown' }}</p>
</div>
<button @click="handleButtonClick">
Send Callback to Player
</button>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { usePlayerClient } from '../composables/usePlayerClient';
const { isPlayerActive, deviceInfo, sendCallback, trackEvent } = usePlayerClient();
onMounted(() => {
trackEvent('component_mounted', { component: 'PlayerAwareComponent' });
});
const handleButtonClick = () => {
sendCallback('button_clicked', new Date().toISOString());
trackEvent('user_interaction', { action: 'button_click' });
};
</script>// plugins/playerClient.ts
import { App } from 'vue';
import { createPlayerClient } from '@reveldigital/client-sdk';
export default {
install(app: App) {
const client = createPlayerClient();
app.config.globalProperties.$playerClient = client;
app.provide('playerClient', client);
}
};Creates a new player client instance.
on(eventType: EventType, callback: Function)- Listen for events. Multiple listeners per event type are supported; registering the same callback twice is a no-op.off(eventType: EventType, callback?: Function)- Remove a specific listener, or every listener for the event type whencallbackis omitted.
getDeviceKey(): Promise<string | null>- Get unique device identifiergetDeviceTime(date?: Date): Promise<string | null>- Get device time in ISO8601getDeviceTimeZoneName(): Promise<string | null>- Get timezone namegetDeviceTimeZoneID(): Promise<string | null>- Get timezone IDgetDeviceTimeZoneOffset(): Promise<number | null>- Get timezone offsetgetLanguageCode(): Promise<string | null>- Get device language
callback(...args: any[]): void- Send callback to playersendCommand(name: string, arg: string): void- Send command to playersendRemoteCommand(deviceKeys: string[], name: string, arg: string): void- Send command to remote devices
track(eventName: string, properties?: IEventProperties): void- Track analytics eventgetPrefs(): IPrefs- Access user preferences. Never throws and never returns undefined; falls back to an in-memory mock when no player is attached.
enum EventType {
START = 'Start', // Player started
STOP = 'Stop', // Player stopped
COMMAND = 'Command', // Command received
CONFIG = 'Config', // Config editor opened
POSTMESSAGE = 'PostMessage' // postMessage received from the player
}getPrefs() returns an IPrefs object: the Gadgets API Prefs surface (getString, getBool, getInt, getFloat, getArray, set, …) plus existence-aware accessors.
const prefs = client.getPrefs();
prefs.getString('language'); // '' when unset
prefs.getBool('kenBurns'); // false when unsetBecause an unset preference reads back as '', false, or 0, those getters can't tell "not set" from a deliberate falsy value — which matters when honoring a default_value: true from gadget.yaml. Use has() or the *OrNull getters instead:
// A designer's deliberate `false` is preserved; only an unset pref gets the default.
const kenBurns = prefs.getBoolOrNull('kenBurns') ?? true;
prefs.has('language'); // false when unset
prefs.getStringOrNull('language') // null when unset
prefs.getIntOrNull('maxItems') // null when unset
prefs.getFloatOrNull('ratio') // null when unset
prefs.getArrayOrNull('categories')// null when unsetNote: the Gadgets API exposes no existence check, so with a real player attached
has()is a probe — a preference reads as present when it is a non-empty string. A preference explicitly set to''is therefore reported as absent. Preferences backed bycreateMockPlayer()track existence exactly.
Gadgets run in three contexts: the player, the CMS preview, and a local dev server. Every method degrades gracefully when no player is attached — nothing throws, so a gadget renders standalone with no defensive code.
| API | Behavior with no player attached |
|---|---|
getPrefs() |
Returns a mock IPrefs backed by memory. Unset preferences read as '' / false / 0 / []. Logs "Gadgets API not available, falling back to mock prefs" once. |
on() / off() |
Work normally. Events are delivered over window, so createMockPlayer() can drive them. |
getDeviceTime(date?) |
Resolves to the current time as an ISO8601 string. The date argument is ignored, since there is no device timezone to translate into. |
getWidth() / getHeight() / getDuration() |
Resolve to null. |
getDevice() / getDeviceKey() / getLanguageCode() |
Resolve to null. |
getDeviceTimeZoneName() / getDeviceTimeZoneID() / getDeviceTimeZoneOffset() |
Resolve to null. |
getRevelRoot() |
Resolves to null. |
getCommandMap() |
Resolves to an empty map ({}). |
getSdkVersion() |
Resolves to the SDK version. Never depends on a player. |
track() / timeEvent() / newEventSession() |
No-op. |
sendCommand() / sendRemoteCommand() / callback() / finish() |
No-op. |
isPreviewMode() |
Resolves to true. |
applyConfig(prefs) |
Posts the config to the parent/opener window, which is how CMS preview applies it. |
createDataTable() / createDataTableFromPref() |
Throw if the data table library is not loaded. Data tables require a player. |
The first time a Client API method is called with no player attached, the SDK logs "Client API not available, falling back to mock API". This is expected in dev and preview, not an error.
createMockPlayer() stands in for a real player, making lifecycle handling — usually the least-tested part of a gadget — testable without one. It installs the same globals the player provides, so the client takes its normal code path, and emit() dispatches through the genuine event path rather than a stub.
import { createPlayerClient, EventType } from '@reveldigital/client-sdk';
import { createMockPlayer } from '@reveldigital/client-sdk/testing';
const player = createMockPlayer({
prefs: { rotationSeconds: 2, kenBurns: false },
width: 1920,
height: 1080
});
// Create the client *after* the mock player, since it resolves the Client API on first use.
const client = createPlayerClient();
client.on(EventType.STOP, () => pause());
client.on(EventType.START, () => resume());
player.emit(EventType.STOP); // assert animations frozen
player.emit(EventType.START); // assert resumed, not restarted
expect(await client.getWidth()).toBe(1920);
expect(client.getPrefs().getBoolOrNull('kenBurns')).toBe(false);
player.dispose(); // restores any globals that were present beforehandOptions — all optional; anything unset behaves as it does with no player attached:
| Option | Backs |
|---|---|
prefs |
getPrefs(). Values are coerced to strings, as in the player; arrays are joined with |. |
width / height / duration |
getWidth() / getHeight() / getDuration() |
device |
getDevice(), and getDeviceKey() / getLanguageCode() / getDeviceTimeZoneName() |
timeZoneID / timeZoneOffset |
getDeviceTimeZoneID() / getDeviceTimeZoneOffset() |
revelRoot / commandMap |
getRevelRoot() / getCommandMap() |
lang / country / moduleId |
getPrefs().getLang() / .getCountry() / .getModuleId() |
The returned IMockPlayer:
emit(eventType, detail?)— dispatch a lifecycle event. ForEventType.COMMAND,detailis{ name, arg }.prefs— the backingMockPrefs, writable mid-test viaplayer.prefs.set('key', 'value').commands,remoteCommands,trackedEvents,timedEvents,callbacks,finishCount— everything the gadget sent to the player, for assertions.dispose()— uninstall. Call between tests to avoid leaking state.
Because the client dispatches outbound calls through a promise, let them settle before asserting:
client.sendCommand('volume', '11');
await vi.waitFor(() => expect(player.commands).toHaveLength(1));
expect(player.commands[0]).toEqual({ name: 'volume', arg: '11' });SDK methods don't throw when no player is attached — they return sensible defaults instead (see Behavior With No Player Attached). So handle null, and skip the defensive try/catch:
import { createPlayerClient } from "@reveldigital/client-sdk";
const client = createPlayerClient();
// Resolves to null in dev and CMS preview rather than throwing.
const deviceKey = await client.getDeviceKey();
if (deviceKey) {
console.log('Device key:', deviceKey);
} else {
console.warn('Device key not available');
}The exceptions worth guarding are the data table methods, which throw if the data table library isn't loaded:
try {
const dt = client.createDataTable('tbl_menu_items');
const result = await dt.getRows();
} catch (error) {
console.error('Data tables unavailable:', error);
}- Create the client instance once and reuse it
- Clean up event listeners when components unmount
- Use debouncing for frequent callback calls
- Cache device information when possible
- Device information should be treated as sensitive
- Validate all data received from player events
- Use HTTPS for any external API calls from your content
There are two deployment options available depending on your project type:
The Revel Digital Webapp Deploy Action makes it easy to automatically deploy your web applications to your Revel Digital account whenever you push code to your repository.
-
Get your API Key: First, obtain your Revel Digital API key from your account settings.
-
Add API Key to GitHub Secrets:
- Go to your repository settings
- Navigate to "Secrets and variables" → "Actions"
- Create a new secret named
REVEL_API_KEYwith your API key as the value
-
Create the workflow file: Add
.github/workflows/deploy.ymlto your repository:
Gadgets are deployed to GitHub Pages and use the Gadgetizer tool for packaging. This approach doesn't require a Revel Digital API key and leverages GitHub's built-in Pages hosting.
-
Enable GitHub Pages:
- Go to your repository settings
- Navigate to "Pages"
- Set the source to "GitHub Actions"
-
Create the workflow file: Add
.github/workflows/deploy.ymlto your repository:
name: Deploy Gadget to GitHub Pages
on:
push:
branches:
- main # or master, depending on your default branch
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js (if applicable for building)
uses: actions/setup-node@v4
with:
node-version: '20.x' # Adjust as needed for your project
- name: Install dependencies (if applicable)
run: npm install # Or yarn install, etc.
- name: Build static site (if applicable)
run: npm run build # Or your specific build command
- name: Gadgetizer
run: npx @reveldigital/gadgetizer@latest --build-only
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist # Or your output directory, e.g., ./dist, ./publicname: Deploy to Revel Digital
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build application
run: npm run build
- name: Deploy to Revel Digital
uses: RevelDigital/webapp-action@v1.0.11
with:
api-key: ${{ secrets.REVEL_API_KEY }}
environment: ${{ github.ref_name }}name: Deploy React App
on:
push:
branches: [ main, develop ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build React app
run: npm run build
- name: Deploy to Revel Digital
uses: RevelDigital/webapp-action@v1.0.11
with:
api-key: ${{ secrets.REVEL_API_KEY }}
name: "My React Signage App"
version: ${{ github.sha }}
environment: ${{ github.ref_name == 'main' && 'Production' || 'Development' }}
distribution-location: './build'
tags: 'react,interactive'name: Deploy Angular App
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install Angular CLI
run: npm install -g @angular/cli
- name: Install dependencies
run: npm install
- name: Build Angular app
run: ng build --configuration production
- name: Deploy to Revel Digital
uses: RevelDigital/webapp-action@v1.0.11
with:
api-key: ${{ secrets.REVEL_API_KEY }}
name: "My Angular Signage App"
environment: "Production"
distribution-location: './dist'
tags: 'angular,enterprise'name: Deploy Vue App
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build Vue app
run: npm run build
- name: Deploy to Revel Digital
uses: RevelDigital/webapp-action@v1.0.11
with:
api-key: ${{ secrets.REVEL_API_KEY }}
name: "My Vue Signage App"
environment: ${{ github.event_name == 'push' && 'Production' || 'Development' }}
distribution-location: './dist'
group-name: 'signage-.*'| Input | Required | Description | Default |
|---|---|---|---|
api-key |
✅ | Your Revel Digital API key (use GitHub secrets) | - |
name |
❌ | Name for the webapp | From package.json |
version |
❌ | Version of the webapp | From package.json |
environment |
❌ | Deployment environment | Production |
distribution-location |
❌ | Folder containing built assets | From package.json |
tags |
❌ | Extra tags for smart scheduling (comma-delimited) | - |
group-name |
❌ | Group name as regex pattern | - |
For gadgets, the deployment process is simpler as it uses GitHub Pages:
- No API Key Required: Gadgets don't need a Revel Digital API key
- GitHub Pages Hosting: Content is automatically hosted on GitHub Pages
- Gadgetizer Processing: The
@reveldigital/gadgetizertool processes your content for optimal display - Output Directory: Ensure your
publish_dirmatches your build output (commonly./dist,./build, or./public) - Branch Protection: Consider protecting your main branch to prevent accidental deployments
You can deploy to different environments based on your branch strategy:
# Deploy to Development for feature branches
# Deploy to Production for main branch
environment: ${{ github.ref_name == 'main' && 'Production' || 'Development' }}
# Or use custom logic
environment: ${{
github.ref_name == 'main' && 'Production' ||
github.ref_name == 'staging' && 'Staging' ||
'Development'
}}name: Deploy to Multiple Environments
on:
push:
branches: [ main, staging, develop ]
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- branch: main
environment: Production
tags: 'production,stable'
- branch: staging
environment: Staging
tags: 'staging,testing'
- branch: develop
environment: Development
tags: 'development,experimental'
if: github.ref_name == matrix.branch
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup and Build
# ... build steps ...
- name: Deploy to Revel Digital
uses: RevelDigital/webapp-action@v1.0.11
with:
api-key: ${{ secrets.REVEL_API_KEY }}
environment: ${{ matrix.environment }}
tags: ${{ matrix.tags }}name: Conditional Deploy
on:
push:
branches: [ main ]
paths:
- 'src/**'
- 'public/**'
- 'package.json'
jobs:
deploy:
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[deploy]') || github.ref == 'refs/heads/main'
steps:
# ... deployment steps- Use Semantic Versioning: Let the action pull version from
package.jsonor use Git tags - Environment Separation: Use different environments for different branches
- Tag Management: Use meaningful tags for content organization and smart scheduling
- Build Optimization: Ensure your build process creates optimized, production-ready assets
- Security: Always use GitHub Secrets for API keys, never commit them to your repository
- GitHub Pages Setup: Ensure GitHub Pages is properly configured in your repository settings
- Build Output: Verify your build process outputs to the correct directory specified in
publish_dir - Gadgetizer Compatibility: Test that your content works correctly with the Gadgetizer tool
- Branch Strategy: Use branch protection rules to control when deployments occur
- Asset Optimization: Optimize images and other assets for signage display performance
- Build Failures: Ensure your build command works locally before deploying
- Permission Issues: Verify your API key has the necessary permissions in Revel Digital
- Path Issues: Check that
distribution-locationpoints to the correct build output folder - Environment Issues: Confirm the environment name exists in your Revel Digital account
- GitHub Pages Not Enabled: Verify GitHub Pages is enabled in repository settings
- Build Failures: Test your build process locally and ensure it outputs to the correct directory
- Gadgetizer Errors: Check that your content structure is compatible with the Gadgetizer tool
- Deployment Path Issues: Ensure
publish_dirmatches your actual build output directory - Permission Issues: Verify the
GITHUB_TOKENhas proper permissions for Pages deployment
This library is written in TypeScript and includes full type definitions. Import types as needed:
import {
PlayerClient,
EventType,
IEventProperties,
IOptions
} from '@reveldigital/client-sdk';# Install dependencies
npm install
# Run tests
npm test
# Build the library
npm run build
# Generate documentation
npm run gen:docsThis project is licensed under the MIT License - see the LICENSE file for details.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software.
Copyright (c) 2025 Revel Digital
For the full license text, please refer to the LICENSE file in this repository.