Some testing & cleanup

This commit is contained in:
2024-02-09 20:34:55 +01:00
parent 1be97536ef
commit fcb3642ac5
19 changed files with 957 additions and 161 deletions

View File

@@ -10,8 +10,7 @@ atv.onAppEntry = function () {
fetchSettings(function() {
overrideConsoleLog();
console.log("Successfully overwritten console log, sending logs to JCATHOST now.")
console.log("Received ATVCSETTINGS From JCATHOST.");
console.log("Starting JellyCAT-JS on AppleTV...")
console.log("Received ATVCSETTINGS From JCATHOST. And starting JellyCAT-APP-JS on AppleTV...");
jcatMain();
});
}
@@ -22,16 +21,16 @@ atv.onAppExit = function() {
// ***************************************************
// JellyCAT Main | Main JS app function
// Help
// Help I'm even worse at JS
function jcatMain(){
atvutils.loadURL("https://" + atv.jcathost.SigHost + "/xml/home.xml");
atvutils.loadURL("https://" + atv.jcathost.SigHost + "/xml/devtools.xml");
}
// ***************************************************
// JellyCAT Logger | Jclogger
// Function to override console.log, console.error, and console.warn and send logs to the JellyCAT stHack server
// We shall never send any sensitive information!!
function overrideConsoleLog() {
var originalConsoleLog = console.log;
var originalConsoleError = console.error;
@@ -81,7 +80,7 @@ function logToServer(logData) {
// ***************************************************
// JellyCAT Host fetcher
// Function to fetch information we need from the host server
// Function to fetch JSON from the HTTP URL using XMLHttpRequest
// fetch JSON from the HTTP URL using XMLHttpRequest
function fetchSettings(callback) {
var xhr = new XMLHttpRequest();

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
app/assets/img/jctools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,56 +0,0 @@
// /\_/|
// { ' ' } JellyCAT
// \____\
overrideConsoleLog();
console.log("Successfully overwritten console log, sending logs to JCATHOST now.")
// ***************************************************
// JellyCAT Logger | Jclogger
// Function to override console.log, console.error, and console.warn and send logs to the JellyCAT stHack server
// We shall never send any sensitive information!!
function overrideConsoleLog() {
var originalConsoleLog = console.log;
var originalConsoleError = console.error;
var originalConsoleWarn = console.warn;
console.log = function () {
// Call the original console.log
originalConsoleLog.apply(console, arguments);
// Send the log to the server
logToServer("LOG: " + JSON.stringify(arguments));
};
console.error = function () {
// Call the original console.error
originalConsoleError.apply(console, arguments);
// Send the error to the server
logToServer("ERROR: " + JSON.stringify(arguments));
};
console.warn = function () {
// Call the original console.warn
originalConsoleWarn.apply(console, arguments);
// Send the warning to the server
logToServer("WARNING: " + JSON.stringify(arguments));
};
}
// Function to log console information to the server
function logToServer(logData) {
var logEndpoint = "http://jcathost.dns/log"; // insecure for now
var logRequest = new XMLHttpRequest();
logRequest.open("POST", logEndpoint, true);
logRequest.setRequestHeader("Content-Type", "application/json");
var logPayload = {
timestamp: new Date().toISOString(),
logData: logData
};
logRequest.send(JSON.stringify(logPayload));
}

562
app/js/jcm.js Normal file
View File

@@ -0,0 +1,562 @@
// /\_/|
// { ' ' } JellyCAT
// \____\
fetchSettings(function() {
overrideConsoleLog();
console.log("Successfully retrieved settings & overwritten console log (JCM), sending logs to JCATHOST now.")
});
// ***************************************************
// JellyCAT Logger | Jclogger
// Function to override console.log, console.error, and console.warn and send logs to the JellyCAT stHack server
function overrideConsoleLog() {
var originalConsoleLog = console.log;
var originalConsoleError = console.error;
var originalConsoleWarn = console.warn;
console.log = function () {
// Call the original console.log
originalConsoleLog.apply(console, arguments);
// Send the log to the server
logToServer("LOG: " + JSON.stringify(arguments));
};
console.error = function () {
// Call the original console.error
originalConsoleError.apply(console, arguments);
// Send the error to the server
logToServer("ERROR: " + JSON.stringify(arguments));
};
console.warn = function () {
// Call the original console.warn
originalConsoleWarn.apply(console, arguments);
// Send the warning to the server
logToServer("WARNING: " + JSON.stringify(arguments));
};
}
// Function to log console information to the server
// This however opens up a POI for attackers, so only leave this exposed in a dev environment. #todo: add in RM.MD
function logToServer(logData) {
var logEndpoint = "https://" + atv.jcathost.SigHost + "/log";
var logRequest = new XMLHttpRequest();
logRequest.open("POST", logEndpoint, true);
logRequest.setRequestHeader("Content-Type", "application/json");
var logPayload = {
timestamp: new Date().toISOString(),
logData: logData
};
logRequest.send(JSON.stringify(logPayload));
}
// ***************************************************
// JellyCAT Host fetcher
// Function to fetch information we need from the host server
// fetch JSON from the HTTP URL using XMLHttpRequest
function fetchSettings(callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://jcathost.dns/atvcsettings', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
var data = JSON.parse(xhr.responseText);
// Store all properties in atv.jcathost
atv.jcathost = {
SigHost: data.sig_host,
SigHostPort: data.sig_host_p,
HostIP: data.host_ip,
System: data.system,
Version: data.version,
HelloMessage: data.hello,
// Add other properties as needed
};
// Execute the callback after setting atv.jcathost
callback();
} catch (jsonError) {
console.error('Error parsing JSON:', jsonError);
}
} else {
console.error('Error fetching settings. Status:', xhr.status);
}
}
};
xhr.send();
}
// ***************************************************
// ATVUtils - a JavaScript helper library for Apple TV
// Copied & Edited in full from:
// https://kortv.com/appletv/js/application.js
// https://github.com/wahlmanj/sample-aTV/blob/master/js/application.js
var atvutils = ATVUtils = {
makeRequest: function(url, method, headers, body, callback) {
if ( !url ) {
throw "loadURL requires a url argument";
}
var method = method || "GET",
headers = headers || {},
body = body || "";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
try {
if (xhr.readyState == 4 ) {
if ( xhr.status == 200) {
callback(xhr.responseXML);
} else {
console.log("makeRequest received HTTP status " + xhr.status + " for " + url);
callback(null);
}
}
} catch (e) {
console.error('makeRequest caught exception while processing request for ' + url + '. Aborting. Exception: ' + e);
xhr.abort();
callback(null);
}
}
xhr.open(method, url, true);
for(var key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
xhr.send();
return xhr;
},
makeErrorDocument: function(message, description) {
if ( !message ) {
message = "";
}
if ( !description ) {
description = "";
}
var errorXML = '<?xml version="1.0" encoding="UTF-8"?> \
<atv> \
<body> \
<dialog id="com.sample.error-dialog"> \
<title><![CDATA[' + message + ']]></title> \
<description><![CDATA[' + description + ']]></description> \
</dialog> \
</body> \
</atv>';
return atv.parseXML(errorXML);
},
siteUnavailableError: function() {
// TODO: localize
return this.makeErrorDocument("JellyCAT is currently unavailable. Try again later.", "Check JCHOST for log information.");
},
loadError: function(message, description) {
atv.loadXML(this.makeErrorDocument(message, description));
},
loadAndSwapError: function(message, description) {
atv.loadAndSwapXML(this.makeErrorDocument(message, description));
},
loadURLInternal: function(url, method, headers, body, loader) {
var me = this,
xhr,
proxy = new atv.ProxyDocument;
proxy.show();
proxy.onCancel = function() {
if ( xhr ) {
xhr.abort();
}
};
xhr = me.makeRequest(url, method, headers, body, function(xml) {
try {
loader(proxy, xml);
} catch(e) {
console.error("Caught exception in for " + url + ". " + e);
loader(me.siteUnavailableError());
}
});
},
loadURL: function( options ) { //url, method, headers, body, processXML) {
var me = this;
if( typeof( options ) === "string" ) {
var url = options;
} else {
var url = options.url,
method = options.method || null,
headers = options.headers || null,
body = options.body || null,
processXML = options.processXML || null;
}
this.loadURLInternal(url, method, headers, body, function(proxy, xml) {
if(typeof(processXML) == "function") processXML.call(this, xml);
try {
proxy.loadXML(xml, function(success) {
if ( !success ) {
console.log("loadURL failed to load " + url);
proxy.loadXML(me.siteUnavailableError());
}
});
} catch (e) {
console.log("loadURL caught exception while loading " + url + ". " + e);
proxy.loadXML(me.siteUnavailableError());
}
});
},
// loadAndSwapURL can only be called from page-level JavaScript of the page that wants to be swapped out.
loadAndSwapURL: function( options ) { //url, method, headers, body, processXML) {
var me = this;
if( typeof( options ) === "string" ) {
var url = options;
} else {
var url = options.url,
method = options.method || null,
headers = options.headers || null,
body = options.body || null,
processXML = options.processXML || null;
}
this.loadURLInternal(url, method, headers, body, function(proxy, xml) {
if(typeof(processXML) == "function") processXML.call(this, xml);
try {
proxy.loadXML(xml, function(success) {
if ( success ) {
atv.unloadPage();
} else {
console.log("loadAndSwapURL failed to load " + url);
proxy.loadXML(me.siteUnavailableError(), function(success) {
if ( success ) {
atv.unloadPage();
}
});
}
});
} catch (e) {
console.error("loadAndSwapURL caught exception while loading " + url + ". " + e);
proxy.loadXML(me.siteUnavailableError(), function(success) {
if ( success ) {
atv.unloadPage();
}
});
}
});
},
/**
* Used to manage setting and retrieving data from local storage
*/
data: function(key, value) {
if(key && value) {
try {
atv.localStorage.setItem(key, value);
return value;
} catch(error) {
console.error('Failed to store data element: '+ error);
}
} else if(key) {
try {
return atv.localStorage.getItem(key);
} catch(error) {
console.error('Failed to retrieve data element: '+ error);
}
}
return null;
},
deleteData: function(key) {
try {
atv.localStorage.removeItem(key);
} catch(error) {
console.error('Failed to remove data element: '+ error);
}
},
/**
* @params options.name - string node name
* @params options.text - string textContent
* @params options.attrs - array of attribute to set {"name": string, "value": string, bool}
* @params options.children = array of childNodes same values as options
* @params doc - document to attach the node to
* returns node
*/
createNode: function(options, doc) {
var doc = doc || document;
options = options || {};
if(options.name && options.name != '') {
var newElement = doc.makeElementNamed(options.name);
if(options.text) newElement.textContent = options.text;
if(options.attrs) {
options.attrs.forEach(function(e, i, a) {
newElement.setAttribute(e.name, e.value);
}, this);
}
if(options.children) {
options.children.forEach(function(e,i,a) {
newElement.appendChild( this.createNode( e, doc ) );
}, this)
}
return newElement;
}
},
validEmailAddress: function( email ) {
var emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
isValid = email.search( emailRegex );
return ( isValid > -1 );
},
softwareVersionIsAtLeast: function( version ) {
var deviceVersion = atv.device.softwareVersion.split('.'),
requestedVersion = version.split('.');
// We need to pad the device version length with "0" to account for 5.0 vs 5.0.1
if( deviceVersion.length < requestedVersion.length ) {
var difference = requestedVersion.length - deviceVersion.length,
dvl = deviceVersion.length;
for( var i = 0; i < difference; i++ ) {
deviceVersion[dvl + i] = "0";
};
};
// compare the same index from each array.
for( var c = 0; c < deviceVersion.length; c++ ) {
var dv = deviceVersion[c],
rv = requestedVersion[c] || "0";
if( parseInt( dv ) > parseInt( rv ) ) {
return true;
} else if( parseInt( dv ) < parseInt( rv ) ) {
return false;
};
};
// If we make it this far the two arrays are identical, so we're true
return true;
},
shuffleArray: function( arr ) {
var tmp, current, top = arr.length;
if(top) {
while(--top) {
current = Math.floor(Math.random() * (top + 1));
tmp = arr[current];
arr[current] = arr[top];
arr[top] = tmp;
};
};
return arr;
},
loadTextEntry: function( textEntryOptions ) {
var textView = new atv.TextEntry;
textView.type = textEntryOptions.type || "emailAddress";
textView.title = textEntryOptions.title || "";
textView.image = textEntryOptions.image || null;
textView.instructions = textEntryOptions.instructions || "";
textView.label = textEntryOptions.label || "";
textView.footnote = textEntryOptions.footnote || "";
textView.defaultValue = textEntryOptions.defaultValue || null;
textView.defaultToAppleID = textEntryOptions.defaultToAppleID || false;
textView.onSubmit = textEntryOptions.onSubmit,
textView.onCancel = textEntryOptions.onCancel,
textView.show();
},
log: function ( message , level ) {
var debugLevel = atv.sessionStorage.getItem( "DEBUG_LEVEL" ),
level = level || 0;
if( level <= debugLevel ) {
console.log( message );
}
},
accessibilitySafeString: function ( string ) {
var string = unescape( string );
string = string
.replace( /&amp;/g, 'and' )
.replace( /&/g, 'and' )
.replace( /&lt;/g, 'less than' )
.replace( /\</g, 'less than' )
.replace( /&gt;/g, 'greater than' )
.replace( /\>/g, 'greater than' );
return string;
}
};
// Extend atv.ProxyDocument to load errors from a message and description.
if( atv.ProxyDocument ) {
atv.ProxyDocument.prototype.loadError = function(message, description) {
var doc = atvutils.makeErrorDocument(message, description);
this.loadXML(doc);
}
}
// atv.Document extensions
if( atv.Document ) {
atv.Document.prototype.getElementById = function(id) {
var elements = this.evaluateXPath("//*[@id='" + id + "']", this);
if ( elements && elements.length > 0 ) {
return elements[0];
}
return undefined;
}
}
// atv.Element extensions
if( atv.Element ) {
atv.Element.prototype.getElementsByTagName = function(tagName) {
return this.ownerDocument.evaluateXPath("descendant::" + tagName, this);
}
atv.Element.prototype.getElementByTagName = function(tagName) {
var elements = this.getElementsByTagName(tagName);
if ( elements && elements.length > 0 ) {
return elements[0];
}
return undefined;
}
}
// Simple Array Sorting methods
Array.prototype.sortAsc = function() {
this.sort(function( a, b ){
return a - b;
});
};
Array.prototype.sortDesc = function() {
this.sort(function( a, b ){
return b - a;
});
};
// Date methods and properties
Date.lproj = {
"DAYS": {
"en": {
"full": ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
"abbrv": ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
},
"en_GB": {
"full": ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
"abbrv": ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
}
},
"MONTHS": {
"en": {
"full": ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
"abbrv": ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
},
"en_GB": {
"full": ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
"abbrv": ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
}
}
}
Date.prototype.getLocaleMonthName = function( type ) {
var language = atv.device.language,
type = ( type === true ) ? "abbrv" : "full",
MONTHS = Date.lproj.MONTHS[ language ] || Date.lproj.MONTHS[ "en" ];
return MONTHS[ type ][ this.getMonth() ];
};
Date.prototype.getLocaleDayName = function( type ) {
var language = atv.device.language,
type = ( type === true ) ? "abbrv" : "full",
DAYS = Date.lproj.DAYS[ language ] || Date.lproj.DAYS[ "en" ];
return DAYS[ type ][ this.getDay() ];
};
Date.prototype.nextDay = function( days ) {
var oneDay = 86400000,
days = days || 1;
this.setTime( new Date( this.valueOf() + ( oneDay * days ) ) );
};
Date.prototype.prevDay = function( days ) {
var oneDay = 86400000,
days = days || 1;
this.setTime( new Date( this.valueOf() - ( oneDay * days ) ) );
};
// String Trim methods
String.prototype.trim = function ( ch )
{
var ch = ch || '\\s',
s = new RegExp( '^['+ch+']+|['+ch+']+$','g');
return this.replace(s,'');
};
String.prototype.trimLeft = function ( ch )
{
var ch = ch || '\\s',
s = new RegExp( '^['+ch+']+','g');
return this.replace(s,'');
};
String.prototype.trimRight = function ( ch )
{
var ch = ch || '\\s',
s = new RegExp( '['+ch+']+$','g');
return this.replace(s,'');
};
String.prototype.xmlEncode = function()
{
var string = unescape( this );
string = string
.replace( /&/g, '&amp;' )
.replace( /\</g, '&lt;' )
.replace( /\>/g, '&gt;' );
return string;
};
console.log('Reached EOF!');

205
app/js/jsvtestpage.js Normal file
View File

@@ -0,0 +1,205 @@
/*
* https://github.com/wahlmanj/sample-aTV/blob/dba73806c21183fb35d6edca94b960691d8e5d66/js/views/text-view/text-view.js
* text-view.js - Demonstrates atv.TextView
* TextView's have an attributedString parameter that is an Object with two required properties: string and attributes.
* - string is the string displayed in the text view on screen.
* - attributes is an Object with two required properties (pointSize and color) and optional properties (alignment, weight, and breakMode).
*
* attribute Object properties
* pointSize: Required. A number that is the size of the font in points
* color: Required. An object with 3 required properties (red, green, and blue) and one optional property (alpha). Each of the color components is a number from 0 to 1.
* alignment: Optional. left, right, center, or justify
* weight: Optional. light, normal, or heavy
* breakMode: Optional. clip, word-wrap, truncate-head, truncate-tail, or truncate-middle.
clip: the text will be displayed on a single line and cut off at the end of the text view
word-wrap: the text will word wrap until there is no more space, at which point it is clipped to the text view
truncate-head: the text will be displayed on a single line with an ellipsis at the beginning if the string would be clipped otherwise
truncate-tail: the text will be displayed on a single line with an ellipsis at the end if the string would be clipped otherwise
truncate-middle: the text will be displayed on a single line with an ellipsis in the middle if the string would be clipped otherwise
*/
var loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut arcu ut lectus pellentesque ultrices. Sed eu neque et nisl feugiat interdum. Praesent rutrum magna in massa consectetur semper. Nullam gravida odio sit amet purus mattis porta. Sed in est suscipit ipsum imperdiet dapibus. Curabitur cursus fermentum lectus, quis aliquam magna convallis vitae. Maecenas egestas molestie rhoncus. Morbi erat neque, egestas quis condimentum quis, mattis a est. Maecenas vel ipsum a dui posuere tincidunt. Suspendisse tincidunt posuere pretium. Sed in quam eget nibh ultricies dictum. Aliquam est lorem, vestibulum nec congue id, tincidunt vel nunc. Nulla in sem massa, a fermentum purus. In hac habitasse platea dictumst. Vestibulum euismod iaculis justo id rhoncus.";
var screenFrame = atv.device.screenFrame;
var containerView = new atv.View();
var exampleTextViews = [];
containerView.frame = screenFrame;
var startY = screenFrame.height * 0.05;
var currentY = startY;
var hPadding = screenFrame.width * 0.03;
var currentX = hPadding;
var height = screenFrame.height * 0.12;
var width = screenFrame.width * 0.25;
function addTextView () {
var textView = new atv.TextView();
textView.backgroundColor = { red: 0.2, blue: 0.2, green: 0.2 };
var vSpacing = screenFrame.height * 0.01;
textView.frame = {
x: currentX,
y: currentY,
width: width,
height: height
};
currentY += height + vSpacing;
if ( currentY + height + vSpacing > containerView.frame.height )
{
currentY = startY;
currentX += width + hPadding;
}
exampleTextViews.push(textView);
return textView;
}
//
// TextView using only the required attributes
//
var onlyRequiredAttributesView = addTextView();
onlyRequiredAttributesView.attributedString = {
string: "Only Required Attributes: " + loremIpsum,
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 }
}
};
//
// TextView's demonstrating alignments
//
var rightAlignmentView = addTextView();
rightAlignmentView.attributedString = {
string: "Right Alignment",
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
alignment: "right"
}
};
var centerAlignmentView = addTextView();
centerAlignmentView.attributedString = {
string: "Center Alignment",
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
alignment: "center"
}
};
var leftAlignmentView = addTextView();
leftAlignmentView.attributedString = {
string: "Left Alignment",
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
alignment: "left"
}
};
//
// TextView's demonstrating weights
//
var normalWeight = addTextView();
normalWeight.attributedString = {
string: "Normal Weight",
attributes: {
// Required attributes
pointSize: 40.0,
color: { red: 1, blue: 1, green: 1 },
weight: "normal"
}
};
var heavyWeight = addTextView();
heavyWeight.attributedString = {
string: "Heavy Weight",
attributes: {
// Required attributes
pointSize: 40.0,
color: { red: 1, blue: 1, green: 1 },
weight: "heavy"
}
};
var lightWeight = addTextView();
lightWeight.attributedString = {
string: "Light Weight",
attributes: {
// Required attributes
pointSize: 40.0,
color: { red: 1, blue: 1, green: 1 },
weight: "light"
}
};
//
// TextView's demonstrating breakModes
//
var clip = addTextView();
clip.attributedString = {
string: "clip: " + loremIpsum,
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
breakMode: "clip"
}
};
var wordWrap = addTextView();
wordWrap.attributedString = {
string: "word-wrap: " + loremIpsum,
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
breakMode: "word-wrap"
}
};
var truncateHead = addTextView();
truncateHead.attributedString = {
string: loremIpsum + " truncate-head",
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
breakMode: "truncate-head"
}
};
var truncateTail = addTextView();
truncateTail.attributedString = {
string: "truncate-tail: " + loremIpsum,
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
breakMode: "truncate-tail"
}
};
var truncateMiddle = addTextView();
truncateMiddle.attributedString = {
string: "truncate-middle: " + loremIpsum,
attributes: {
// Required attributes
pointSize: 20.0,
color: { red: 1, blue: 1, green: 1 },
breakMode: "truncate-middle"
}
};
containerView.subviews = exampleTextViews;
controller.view = containerView;

45
app/js/storagetest.js Normal file
View File

@@ -0,0 +1,45 @@
// /\_/|
// { ' ' } JellyCAT
// \____\
function printSessionStorage() {
var keys = ['test', 'exampleKey2', 'exampleKey3'];
keys.forEach(function(key) {
var value = atv.sessionStorage.getItem(key);
if (value !== null) {
console.log(key + ": " + value);
} else {
console.log("No value found for key: " + key);
}
});
}
function printLocalStorage() {
var keys = ['test', 'exampleKey2', 'exampleKey3'];
keys.forEach(function(key) {
var value = atv.localStorage.getItem(key);
if (value !== null) {
console.log(key + ": " + value);
} else {
console.log("No value found for key: " + key);
}
});
}
function setTestSessionStorageItem() {
// Set an example item in sessionStorage
atv.sessionStorage.setItem('test', 'exampleValueForSessionStorage');
console.log('Test item set in sessionStorage.');
}
function setTestLocalStorageItem() {
// Set an example item in sessionStorage
atv.localStorage.setItem('test', 'exampleValueForLocalStorage');
console.log('Test item set in localStorage.');
}

43
app/xml/devtools.xml Normal file
View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<atv>
<head>
<script src="http://jcathost.dns/js/jcm.js"/>
</head>
<body>
<optionDialog id="com.jellycat.devtools">
<header>
<simpleHeader accessibilityLabel="Dialog with Options">
<title>JellyCAT Debug/Dev/Test Tools</title>
<image required="true">http://jcathost.dns/assets/img/jctools.png</image>
</simpleHeader>
</header>
<description>These options are intended for testing your development environment only. It is crucial to refrain from utilizing these test functions on server hosts that are not under your management, as doing so may inadvertently expose sensitive data to the server's administrator.</description>
<menu>
<initialSelection>
<row>4</row>
</initialSelection>
<sections>
<menuSection>
<items>
<oneLineMenuItem id="list_0" accessibilityLabel="Option 1" onSelect="console.log('log information sent');">
<label>Send Test log</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_1" accessibilityLabel="Option 2" onSelect="console.log('loading test-page'); atvutils.loadURL('https://'+ atv.jcathost.SigHost +'/xml/dvt/testpage.xml');">
<label>Load XML Test Page</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_2" accessibilityLabel="Option 3" onSelect="console.log('loading JS-VIEW test-page'); atvutils.loadURL('https://'+ atv.jcathost.SigHost +'/xml/dvt/jsvtestpage.xml');">
<label>Load JS-VIEW Test Page</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_3" accessibilityLabel="Option 4" onSelect="console.log('loading storage-test-page'); atvutils.loadURL('https://'+ atv.jcathost.SigHost +'/xml/dvt/storagetest.xml');">
<label>Load Storage Testing</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_4" accessibilityLabel="Option 5" onSelect="console.log('Unloading page'); atv.unloadPage();">
<label>Unload Page (go back)</label>
</oneLineMenuItem>
</items>
</menuSection>
</sections>
</menu>
</optionDialog>
</body>
</atv>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<atv>
<body>
<scriptView id="com.jellycat.jsctestpage">
<script src="http://jcathost.dns/js/jsvtestpage.js"/>
</scriptView>
</body>
</atv>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<atv>
<head>
<script src="http://jcathost.dns/js/jcm.js"/>
<script src="http://jcathost.dns/js/storagetest.js"/>
</head>
<body>
<optionList id="com.jellycat.storagetestpage" autoSelectSingleItem="false">
<footnote>atv.localstorage || storage data elements || JellyCAT</footnote>
<items>
<oneLineMenuItem id="list_0" accessibilityLabel="Set test session storage key" onSelect="setTestSessionStorageItem();">
<label>Set ATV APP session storage key (test)</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_0" accessibilityLabel="Set test local storage key" onSelect="setTestLocalStorageItem();">
<label>Set ATV APP local storage key (test)</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_1" accessibilityLabel="Print session storage (log)" onSelect="printSessionStorage();">
<label>Print ATV App session storage (log)</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_2" accessibilityLabel="Print ATV storage (log)" onSelect="printLocalStorage();">
<label>Print ATV App local storage (log)</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_3" accessibilityLabel="Clear ATV Storage and exit" onSelect="console.log('CLEARING ALL APP STORAGE AND EXITING APP'); atv.localStorage.clear(); atv.sessionStorage.clear(); atv.exitApp();">
<label>Clear ATV App Storage and exit App</label>
</oneLineMenuItem>
</items>
</optionList>
</body>
</atv>

9
app/xml/dvt/testpage.xml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<atv>
<body>
<dialog id="com.jellycat.xmltestpage">
<title>XML Test-Page Not Implemented</title>
<description>The feed for this page has not been written. (but works)</description>
</dialog>
</body>
</atv>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<atv>
<head>
<script src="http://jcathost.dns/js/jclog.js"/>
</head>
<body>
<optionDialog id="com.sample.error-dialog">
<header>
<simpleHeader accessibilityLabel="Dialog with Options">
<title>JellyCAT Debug/Dev Tools</title>
</simpleHeader>
</header>
<description>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</description>
<menu>
<initialSelection>
<row>1</row>
</initialSelection>
<sections>
<menuSection>
<items>
<oneLineMenuItem id="list_0" accessibilityLabel="Option 1" onSelect="console.log('log information sent');">
<label>Send Test log</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_0" accessibilityLabel="Option 2" onSelect="console.log('Option 2 selected'); atv.unloadPage();">
<label>Unload Page</label>
</oneLineMenuItem>
</items>
</menuSection>
</sections>
</menu>
</optionDialog>
</body>
</atv>