Created
October 23, 2023 17:57
-
-
Save FabienLavocat/50aedafbfff4081212e017c0b0e05d55 to your computer and use it in GitHub Desktop.
Dolby.io - Real-time Streaming - Create Subscribe Tokens
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Dolby.io Real-time Streaming</title> | |
<link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> | |
<script src="https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.min.js"></script> | |
<script src="https://kit.fontawesome.com/914cd69875.js" crossorigin="anonymous"></script> | |
<script src="https://cdn.jsdelivr.net/npm/jsrender/jsrender.min.js"></script> | |
<style> | |
textarea { | |
resize: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="bg-dark text-white px-4 py-4"> | |
<h1>Dolby.io Real-time Streaming</h1> | |
</div> | |
<div id="div-form" class="container mt-3"> | |
<div class="alert alert-primary" role="alert"> | |
This utility will create a series of subscribe tokens for the list of users and streams that you provide. Reach out to <a href="mailto:fabien.lavocat@dolby.com">Fabien Lavocat</a> if you have any questions. | |
</div> | |
<div class="row mb-3"> | |
<div class="col-3"> | |
<label for="input-account-id" class="form-label"> | |
Dolby.io Account ID | |
<i class="fa-solid fa-circle-info text-primary" title="In your Dolby.io dashboard, locate your Account ID in the API tab of a publisher token (usually 6 random characters)."></i> | |
<i class="fa-solid fa-asterisk text-danger" title="Mandatory"></i> | |
</label> | |
<input id="input-account-id" type="text" class="form-control" placeholder="a0B1c2"> | |
</div> | |
<div class="col-9"> | |
<label for="input-secret" class="form-label"> | |
Dolby.io API Secret | |
<i class="fa-solid fa-circle-info text-primary" title="In your Dolby.io dashboard, locate your API Secret from the Settings section."></i> | |
<i class="fa-solid fa-asterisk text-danger" title="Mandatory"></i> | |
</label> | |
<input id="input-secret" type="text" class="form-control" placeholder="API Secret"> | |
</div> | |
</div> | |
<div class="row mb-3"> | |
<div class="col-6"> | |
<label for="input-stream-names" class="form-label"> | |
Stream names (one per line) | |
<i class="fa-solid fa-asterisk text-danger" title="Mandatory"></i> | |
</label> | |
<textarea class="form-control" id="input-stream-names" rows="8"></textarea> | |
</div> | |
<div class="col-6"> | |
<label for="input-user-names" class="form-label"> | |
User names (one per line) | |
<i class="fa-solid fa-asterisk text-danger" title="Mandatory"></i> | |
</label> | |
<textarea class="form-control" id="input-user-names" rows="8"></textarea> | |
</div> | |
</div> | |
<div class="row mb-3"> | |
<div class="col-6"> | |
<div class="mb-3"> | |
<label for="input-bindIpsOnUsage" class="form-label">Bind IPs On Usage <i class="fa-solid fa-circle-info text-primary" title="If specified will bind the token to the first X IP addresses used with token in requests to Director API, thus restricting the token to those IP addresses without being known beforehand. Mutually exclusive with allowedIpAddresses option."></i></label> | |
<div class="input-group"> | |
<input type="number" id="input-bindIpsOnUsage" class="form-control" value="0"> | |
<button class="btn btn-outline-primary" type="button" onclick="(0)">0</button> | |
<button class="btn btn-outline-primary" type="button" onclick="setBindIpsOnUsage(1)">1</button> | |
<button class="btn btn-outline-primary" type="button" onclick="setBindIpsOnUsage(2)">2</button> | |
</div> | |
</div> | |
<div class="mb-3"> | |
<label for="input-expires" class="form-label">Expires (in seconds)</label> | |
<div class="input-group"> | |
<input id="input-expires" type="number" class="form-control" onchange="onExpiresChange()" value="31536000"> | |
<button class="btn btn-outline-info" type="button" onclick="addToExpires(- 30 * 24 * 60 * 60)">- 30d</button> | |
<button class="btn btn-outline-info" type="button" onclick="addToExpires(- 7 * 24 * 60 * 60)">- 7d</button> | |
<button class="btn btn-outline-info" type="button" onclick="addToExpires(- 24 * 60 * 60)">- 1d</button> | |
<button class="btn btn-outline-primary" type="button" onclick="addToExpires(24 * 60 * 60)">+ 1d</button> | |
<button class="btn btn-outline-primary" type="button" onclick="addToExpires(7 * 24 * 60 * 60)">+ 7d</button> | |
<button class="btn btn-outline-primary" type="button" onclick="addToExpires(30 * 24 * 60 * 60)">+ 30d</button> | |
</div> | |
<label id="text-expires" class="form-label mt-2"> - </label> | |
</div> | |
</div> | |
<div class="col-6"> | |
<label for="input-allowedCountries" class="form-label">Allowed Countries (one per line) <i class="fa-solid fa-circle-info text-primary" title="Optional. Specify the ISO 3166-1 two letter country codes to explicitly allow end-users to view from. If the end-user's location does not match any of the specified countries they will be blocked from viewing stream, else they will be allowed to view stream. This geo-fencing rule works in concert with the IP and domain restrictions as well. Specifying geo restriction rules in a token will override account-wide rules."></i></label> | |
<textarea class="form-control" id="input-allowedCountries" rows="6"></textarea> | |
</div> | |
</div> | |
<div id="actions" class="row"> | |
<div class="col"> | |
<button type="button" class="btn btn-primary btn-lg" onclick="generate()">Generate</button> | |
<button id="btn-clear" type="button" class="btn btn-secondary btn-lg" onclick="clearForm()">Clear</button> | |
</div> | |
</div> | |
</div> | |
<div id="div-results" class="container mt-3 list-group list-group-flush d-none"> | |
<div class="alert alert-success" role="alert"> | |
List of subscribe tokens created: | |
</div> | |
</div> | |
<script> | |
const inputAccountId = document.getElementById('input-account-id'); | |
const inputSecret = document.getElementById('input-secret'); | |
const inputStreamNames = document.getElementById('input-stream-names'); | |
const inputUserNames = document.getElementById('input-user-names'); | |
const inputBindIpsOnUsage = document.getElementById('input-bindIpsOnUsage'); | |
const inputAllowedCountries = document.getElementById('input-allowedCountries'); | |
const inputExpires = document.getElementById('input-expires'); | |
const divResults = document.getElementById('div-results'); | |
function onExpiresChange() { | |
const txtExpires = document.getElementById('text-expires'); | |
try { | |
const value = parseInt(inputExpires.value); | |
const newTime = new Date(new Date().getTime() + (value * 1000)); | |
txtExpires.innerHTML = newTime.toString(); | |
} catch (error) { | |
console.error(error); | |
txtExpires.innerHTML = ''; | |
} | |
} | |
function addToExpires(seconds) { | |
console.log('addToExpires', seconds); | |
try { | |
const value = parseInt(inputExpires.value) + seconds; | |
inputExpires.value = value > 0 ? value : 0; | |
} catch (error) { | |
inputExpires.value = seconds; | |
} | |
onExpiresChange(); | |
} | |
function setBindIpsOnUsage(value) { | |
inputBindIpsOnUsage.value = value; | |
} | |
function loadConfigurationFromLocalStorage() { | |
inputAccountId.value = localStorage.getItem('accountId') ?? ''; | |
inputStreamNames.value = localStorage.getItem('streamNames') ?? ''; | |
inputUserNames.value = localStorage.getItem('userNames') ?? ''; | |
inputBindIpsOnUsage.value = localStorage.getItem('bindIpsOnUsage') ?? 0; | |
inputAllowedCountries.value = localStorage.getItem('allowedCountries') ?? ''; | |
inputExpires.value = localStorage.getItem('expires') ?? 31536000; | |
} | |
function saveConfigurationToLocalStorage() { | |
localStorage.setItem('accountId', inputAccountId.value); | |
localStorage.setItem('streamNames', inputStreamNames.value); | |
localStorage.setItem('userNames', inputUserNames.value); | |
localStorage.setItem('bindIpsOnUsage', inputBindIpsOnUsage.value); | |
localStorage.setItem('allowedCountries', inputAllowedCountries.value); | |
localStorage.setItem('expires', inputExpires.value); | |
} | |
function displayError(errorMessage) { | |
const template = jsrender.templates('<div class="alert alert-danger" role="alert">{{:error}}</div>'); | |
const frag = document.createDocumentFragment(); | |
const temp = document.createElement('div'); | |
temp.innerHTML = template.render({error: errorMessage}); | |
frag.appendChild(temp.firstChild); | |
const elem = document.getElementById("div-results"); | |
elem.appendChild(frag); | |
} | |
async function createSubscribeToken(userName, streamNames) { | |
console.group('Subscribe token'); | |
console.log(`Username: ${userName}`); | |
const label = `Subscriber - ${userName}`; | |
console.log(`Label: ${label}`); | |
console.log('Stream names', streamNames); | |
console.groupEnd(); | |
const tokenRequest = { | |
label: label, | |
streams: streamNames.map(sn => { | |
return { | |
isRegex: false, | |
streamName: sn | |
}; | |
}), | |
}; | |
const bindIpsOnUsage = parseInt(inputBindIpsOnUsage.value); | |
if (!isNaN(bindIpsOnUsage)) { | |
tokenRequest.bindIpsOnUsage = bindIpsOnUsage; | |
} | |
const allowedCountries = inputAllowedCountries.value.split('\n').filter(o=>o); | |
if (allowedCountries.length) { | |
tokenRequest.allowedCountries = allowedCountries; | |
} | |
const expires = parseInt(inputExpires.value); | |
if (!isNaN(expires) && expires > 0) { | |
tokenRequest.expires = expires; | |
} | |
const accountId = inputAccountId.value; | |
const apiSecret = inputSecret.value; | |
// Prefix with try.readme.io to avoir CORS errors | |
const url = 'https://try.readme.io/https://api.millicast.com/api/subscribe_token'; | |
const options = { | |
method: 'POST', | |
headers: { | |
accept: 'application/json', | |
'Content-Type': 'application/json', | |
authorization: `Bearer ${apiSecret}`, | |
}, | |
body: JSON.stringify(tokenRequest), | |
}; | |
const fetchResponse = await fetch(url, options); | |
if (!fetchResponse.ok) { | |
displayError(`${label} -> HTTP ${fetchResponse.status} - ${fetchResponse.statusText}`); | |
return; | |
} | |
const response = await fetchResponse.json(); | |
const token = response.data; | |
const template = jsrender.templates({ | |
markup: ` | |
<div class="list-group-item d-flex gap-3 py-3" aria-current="true"> | |
<i class="fa-solid fa-user-shield fa-xl mt-3 text-success"></i> | |
<div class="w-100 justify-content-between"> | |
<h6 class="ms-2"><b>Label:</b> {{:label}}</h6> | |
<h6 class="ms-2"><b>Subscribe Token:</b> {{:token}}</h6> | |
<table class="table mb-0"> | |
<thead> | |
<tr> | |
<th scope="col">Stream name</th> | |
<th scope="col">Viewer URL</th> | |
</tr> | |
</thead> | |
<tbody> | |
{{for streams}} | |
<tr> | |
{{* streamName = encodeURI(data.streamName); }} | |
<td>{{:streamName}}</td> | |
<td>https://viewer.millicast.com?streamId=${accountId}/{{*:streamName}}&token={{:~root.token}}</td> | |
</tr> | |
{{/for}} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
`, | |
allowCode: true | |
}); | |
const frag = document.createDocumentFragment(); | |
const temp = document.createElement('div'); | |
temp.innerHTML = template.render(token); | |
frag.appendChild(temp.firstChild); | |
const elem = document.getElementById("div-results"); | |
elem.appendChild(temp.firstChild); | |
return token; | |
} | |
async function generate() { | |
document.getElementById('actions').remove(); | |
divResults.classList.remove('d-none'); | |
// Disable the input elements | |
const inputs = [...document.getElementsByTagName('input'), ...document.getElementsByTagName('textarea')]; | |
for (let i = 0; i < inputs.length; i++) { | |
inputs[i].disabled = true; | |
} | |
saveConfigurationToLocalStorage(); | |
const streamNames = inputStreamNames.value.split('\n').filter(o=>o); | |
const userNames = inputUserNames.value.split('\n').filter(o=>o); | |
try { | |
for (let i = 0; i < userNames.length; i++) { | |
const userName = userNames[i]; | |
await createSubscribeToken(userName, streamNames); | |
} | |
} catch (error) { | |
console.error(error); | |
displayError(error.message); | |
} | |
} | |
function clearForm() { | |
inputStreamNames.value = ''; | |
inputUserNames.value = ''; | |
inputBindIpsOnUsage.value = 0; | |
inputAllowedCountries.value = ''; | |
inputExpires.value = 31536000; | |
} | |
loadConfigurationFromLocalStorage(); | |
onExpiresChange(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment