Node.js quickstart
A Node.js program that refreshes a token, lists your screens and puts a playlist on a screen. The blocks on this page form one file.
1 Get a token
Open the CMS at Settings, Connected apps and click Generate token. A member without admin rights does not see this tab.
Then choose the organization, which decides the screens the program sees, and click Allow. The next page shows the client ID and the refresh token, once. Copy both.
In your terminal, set the client ID and the path of the token file, then write the refresh token to that file. read -rs waits without showing anything: paste the refresh token and press Enter, so it stays out of the shell history:
export GIBEON_CLIENT_ID=CLIENT_ID export GIBEON_REFRESH_TOKEN_FILE=./gibeon-refresh-token touch "$GIBEON_REFRESH_TOKEN_FILE" chmod 600 "$GIBEON_REFRESH_TOKEN_FILE" read -rs GIBEON_REFRESH_TOKEN && printf '%s' "$GIBEON_REFRESH_TOKEN" > "$GIBEON_REFRESH_TOKEN_FILE"
The program needs Node.js 18 or newer, for the built-in fetch. There is nothing to install. Put the blocks below in order in gibeon.mjs and run node gibeon.mjs.
2 Refresh the token
An access token lasts 60 minutes. Every refresh also returns a new refresh token, so a value in the environment is stale after one run.
The program writes the new token to a temp file in the same directory, sets chmod 600 and renames it over the file in GIBEON_REFRESH_TOKEN_FILE. Only then does it use the access token. A process that dies while using it keeps a working refresh token.
Every request checks the status: on an error, check throws, which stops the program with a non-zero exit code.
import { randomUUID } from 'node:crypto';
import { chmod, readFile, rename, writeFile } from 'node:fs/promises';
// An uncaught error stops the program with a non-zero exit code.
function check(response) {
if (!response.ok) {
throw new Error(`${response.status} from ${response.url}`);
}
}
const tokenFile = process.env.GIBEON_REFRESH_TOKEN_FILE;
const refreshToken = (await readFile(tokenFile, 'utf8')).trim();
let response = await fetch('https://id.gibeon.io/auth/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: process.env.GIBEON_CLIENT_ID,
refresh_token: refreshToken,
}),
signal: AbortSignal.timeout(30_000),
});
check(response);
const tokens = await response.json();
// Store the new refresh token before anything uses the access token.
const tmpPath = `${tokenFile}.${randomUUID()}.tmp`;
await writeFile(tmpPath, tokens.refresh_token, { mode: 0o600 });
await chmod(tmpPath, 0o600);
await rename(tmpPath, tokenFile);
const headers = { Authorization: `Bearer ${tokens.access_token}` }; 3 List your screens
This returns every screen in your tenant. The program prints the id, name and status of each screen, and never a token.
response = await fetch('https://www.gibeon.io/v1/players', {
headers,
signal: AbortSignal.timeout(30_000),
});
check(response);
for (const player of (await response.json()).data) {
console.log(player.id, player.name, player.status);
} 4 Assign a playlist and publish
This step runs only when GIBEON_PLAYER_ID and GIBEON_PLAYLIST_ID are set. The screen id is in the list from the previous step. The playlist id is in the CMS address bar when you open a playlist: /playlists/PLAYLIST_ID/content. Export both and run the program again.
A screen with zones plays those; otherwise its sequence; otherwise its planning; otherwise its own playlist. The body clears the sequence and the planning, so the playlist plays unless the screen has zones.
The PATCH alone changes nothing on the screen: until you publish, it keeps showing the old assignment. Publishing builds a new snapshot and tells the screen right away.
const playerId = process.env.GIBEON_PLAYER_ID;
const playlistId = process.env.GIBEON_PLAYLIST_ID;
if (playerId && playlistId) {
response = await fetch(`https://www.gibeon.io/v1/players/${playerId}`, {
method: 'PATCH',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({ playlist_id: playlistId, sequence_id: null, planning_id: null }),
signal: AbortSignal.timeout(30_000),
});
check(response);
const player = (await response.json()).data;
response = await fetch(`https://www.gibeon.io/v1/players/${playerId}/publish`, {
method: 'POST',
headers,
signal: AbortSignal.timeout(30_000),
});
check(response);
console.log(player.id, player.name, player.status);
}