Skip to content

Commit

Permalink
Merge branch 'enable_https' into hackathon_5_2
Browse files Browse the repository at this point in the history
# Conflicts:
#	.gitignore
#	addons/vuforia-spatial-core-addon
#	server.js
#	tests/server-remote-operator.test.js
  • Loading branch information
ptc-rdeleeuw committed Feb 5, 2024
2 parents a960d4d + 59c1626 commit dc77f50
Show file tree
Hide file tree
Showing 25 changed files with 403 additions and 279 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Generate self-signed certificate
run: ./scripts/ci_generate_certificate.sh
- run: npm install
- run: npm run test-older
env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ libraries/objectLogs/*
/screenshots
/spatialToolbox
/vuforia-spatial-toolbox-userinterface
/.vscode
/*.pem
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,17 @@ on GitHub. Fork this repository using the button on the top right, make and
commit your changes, then GitHub will prompt you to make a pull request.

### Automated Tests

Note that we do run some automated testing to ensure that our code remains
consistently styled and functional. If you want to see the results of this
testing locally, run the following command in your vuforia-toolbox-server
folder:
testing locally, you can follow
[this Github Actions workflow](https://github.com/ptcrealitylab/vuforia-spatial-edge-server/blob/master/.github/workflows/nodejs.yml).

The most important parts of the workflow are running the commands of
[scripts/ci.sh](https://github.com/ptcrealitylab/vuforia-spatial-edge-server/blob/master/scripts/ci.sh)
to setup (note that some repos may not be available, causing test failures
locally). After this, you can run tests using the following command in your
vuforia-toolbox-server folder:

```bash
npm run test
Expand Down
4 changes: 4 additions & 0 deletions generate_cert.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$openssl = (Join-Path (Split-Path -Parent (Get-Command git).Path) "../mingw64/bin/openssl.exe")

# generate certificate and key
& $openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -sha256 -days 356 -out cert.pem
4 changes: 4 additions & 0 deletions generate_cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

# generate certificate and key
libressl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -sha256 -days 356 -out cert.pem
2 changes: 1 addition & 1 deletion libraries/hardwareInterfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ exports.hasTool = function(object, tool) {
};

exports.getObjectIdFromObjectName = async function (object) {
return await utilities.getObjectIdFromTargetOrObjectFile(object);
return await utilities.getObjectIdFromObjectFile(object);
};

exports.getObjectNameFromObjectId = function (objectId) {
Expand Down
2 changes: 1 addition & 1 deletion libraries/nodeUtilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ exports.deepCopy = utilities.deepCopy;
exports.searchNodeByType = async function (nodeType, objectKey, tool, node, callback) {
let thisObjectKey = objectKey;
if (!(objectKey in objects)) {
thisObjectKey = await utilities.getObjectIdFromTargetOrObjectFile(objectKey);
thisObjectKey = await utilities.getObjectIdFromObjectFile(objectKey);
}
let thisObject = utilities.getObject(objects, thisObjectKey);
if (!thisObject) {
Expand Down
127 changes: 99 additions & 28 deletions libraries/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const dgram = require('dgram'); // UDP Broadcasting library
const path = require('path');
const request = require('request');
const fetch = require('node-fetch');
const DecompressZip = require('decompress-zip');
const ObjectModel = require('../models/ObjectModel.js');
const {objectsPath, beatPort} = require('../config.js');
const {isLightweightMobile} = require('../isMobile.js');
Expand Down Expand Up @@ -185,38 +186,15 @@ exports.uuidTime = function () {
return '_' + stampUuidTime;
};

async function getObjectIdFromTargetOrObjectFile(folderName) {
async function getObjectIdFromObjectFile(folderName) {

if (folderName === 'allTargetsPlaceholder') {
return 'allTargetsPlaceholder000000000000';
}

var xmlFile = objectsPath + '/' + folderName + '/' + identityFolderName + '/target/target.xml';
var jsonFile = objectsPath + '/' + folderName + '/' + identityFolderName + '/object.json';
let jsonFile = objectsPath + '/' + folderName + '/' + identityFolderName + '/object.json';

if (await fileExists(xmlFile)) {
try {
let resultXML = '';
xml2js.Parser().parseString(await fsProm.readFile(xmlFile, 'utf8'),
function (err, result) {
for (var first in result) {
for (var secondFirst in result[first].Tracking[0]) {
resultXML = result[first].Tracking[0][secondFirst][0].$.name;
if (typeof resultXML === 'string' && resultXML.length === 0) {
console.warn('Target file for ' + folderName + ' has empty name, ' +
'and may not function correctly. Delete and re-upload target for best results.');
resultXML = null;
}
break;
}
break;
}
});
return resultXML;
} catch (e) {
console.error('error reading xml file', e);
}
} else if (await fileExists(jsonFile)) {
if (await fileExists(jsonFile)) {
try {
let thisObject = JSON.parse(await fsProm.readFile(jsonFile, 'utf8'));
if (thisObject.hasOwnProperty('objectId')) {
Expand All @@ -230,7 +208,7 @@ async function getObjectIdFromTargetOrObjectFile(folderName) {
}
return null;
}
exports.getObjectIdFromTargetOrObjectFile = getObjectIdFromTargetOrObjectFile;
exports.getObjectIdFromObjectFile = getObjectIdFromObjectFile;

async function getAnchorIdFromObjectFile(folderName) {

Expand All @@ -254,6 +232,99 @@ async function getAnchorIdFromObjectFile(folderName) {
}
exports.getAnchorIdFromObjectFile = getAnchorIdFromObjectFile;

/**
* Given a target folder, unzips the target.dat file within it and retrieves the targetId from config.info
* @param {string} targetFolderPath
* @returns {Promise<string|null>}
*/
exports.getTargetIdFromTargetDat = async function getTargetIdFromTargetDat(targetFolderPath) {
return new Promise((resolve, reject) => {
// unzip the .dat file and read the unique targetId from the config.info file
let unzipperDat = new DecompressZip(path.join(targetFolderPath, 'target.dat'));

unzipperDat.on('error', function (err) {
console.error('.dat Unzipper Error', err);
reject(err);
});

unzipperDat.on('extract', async function () {
let configFilePath = path.join(targetFolderPath, 'config.info');
if (await fileExists(configFilePath)) {
// read the id stored within the config.info file (it's actually structured as XML)
let targetUniqueId = await getTargetIdFromConfigFile(configFilePath);
// TODO: cleanup config.info file instead of leaving it in the folder
resolve(targetUniqueId);
} else {
reject('config.info not found at ' + configFilePath);
}
});

unzipperDat.on('progress', function (_fileIndex, _fileCount) {
// console.log('Extracted dat file ' + (fileIndex + 1) + ' of ' + fileCount);
});

unzipperDat.extract({
path: targetFolderPath,
filter: function (file) {
return file.type !== 'SymbolicLink' && file.filename.endsWith('info');
}
});
});
};

/**
* Parses the file as XML and pulls out the targetId string
* @param {string} filePath
* @returns {Promise<string|null>}
*/
async function getTargetIdFromConfigFile(filePath) {
if (!await fileExists(filePath)) {
return null;
}

let contents;
try {
contents = await fsProm.readFile(filePath, 'utf8');
} catch (err) {
console.error('Unable to read xml file for target ID', err);
return null;
}

try {
return await queryXMLContents(contents, (xml) => {
// the file is structured like <QCARInfo><TargetSet><AreaTarget targetId="58a594ef7e324cf590d09480a77a157e" />...
// this gets the "AreaTarget"/"ImageTarget"/"ModelTarget" tag contents of the XML file
// and extracts the tag's properties, e.g. { version: "5.1", bbox: "...", targetId: "xzy": name: "_WORLD_test_xyz" }
return Object.entries(xml.QCARInfo.TargetSet[0]).find(entry => entry[0] !== '$')[1][0].$.targetId;
});
} catch (err) {
console.error('Error parsing/querying XML contents', err);
return null;
}
}

/**
* Parses the string as XML and searches the structured contents using the provided xmlQuery function
* @param {string} xmlContentsString - file contents
* @param {function} xmlQuery - the function that will be applied to the parsed contents to retrieve certain data
* @returns {Promise<string>}
*/
async function queryXMLContents(xmlContentsString, xmlQuery) {
return new Promise(function (resolve, reject) {
xml2js.Parser().parseString(xmlContentsString, function (parseErr, result) {
try {
if (parseErr) {
throw parseErr;
}
resolve(xmlQuery(result));
} catch (err) {
console.error('error parsing xml', err);
reject(err);
}
});
});
}

/**
*
* @param {string} folderName
Expand Down Expand Up @@ -531,7 +602,7 @@ exports.updateObject = async function updateObject(objectName, objects) {
var objectFolderList = await getObjectFolderList();

for (const objectFolder of objectFolderList) {
const tempFolderName = await getObjectIdFromTargetOrObjectFile(objectFolder);
const tempFolderName = await getObjectIdFromObjectFile(objectFolder);

if (!tempFolderName) {
console.warn(' object ' + objectFolder + ' has no marker yet');
Expand Down
2 changes: 1 addition & 1 deletion libraries/webFrontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ exports.printFolder = async function printFolder(objects, objectsPath, _debug, o
let tempKey;
try {
// gets the object id from the xml target file
tempKey = await utilities.getObjectIdFromTargetOrObjectFile(objectKey);
tempKey = await utilities.getObjectIdFromObjectFile(objectKey);
} catch (e) {
console.warn('printFolder getObjectId failed', e);
}
Expand Down
24 changes: 12 additions & 12 deletions libraries/webInterface/gui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ realityServer.getCommonContents = function () {
return this.domObjects.querySelector('#commonContents');
};

let remoteOperatorUrl = 'http://' + realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface] + ':8081';
let remoteOperatorUrl = 'https://' + realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface] + ':8081';
let isRemoteOperatorSupported = false;

realityServer.initialize = function () {
Expand Down Expand Up @@ -462,7 +462,7 @@ realityServer.updateManageObjects = function (thisItem2) {
thisObject.dom.querySelector('.active').classList.add('clickAble');
thisObject.dom.querySelector('.download').classList.add('clickAble');

// let targetUrl = 'http://localhost:8080/obj/' + thisObject.name + '/target/target.jpg';
// let targetUrl = 'https://localhost:8080/obj/' + thisObject.name + '/target/target.jpg';
// thisObject.dom.querySelector(".target").style.backgroundImage = 'url("' + targetUrl + '")';
// thisObject.dom.querySelector(".target").style.backgroundSize = 'cover';

Expand Down Expand Up @@ -512,7 +512,7 @@ realityServer.updateManageObjects = function (thisItem2) {
// only add the icon if it exists
if (thisObject.targetsExist.jpgExists) {
let ipAddress = realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface];
thisObject.dom.querySelector('.objectTargetIcon').src = 'http://' + ipAddress + ':' + realityServer.states.serverPort + '/obj/' + thisObject.name + '/target/target.jpg';
thisObject.dom.querySelector('.objectTargetIcon').src = 'https://' + ipAddress + ':' + realityServer.states.serverPort + '/obj/' + thisObject.name + '/target/target.jpg';
}

} else {
Expand Down Expand Up @@ -619,14 +619,14 @@ realityServer.updateManageObjects = function (thisItem2) {
realityServer.switchClass(thisObject.dom.querySelector('.target'), 'yellow', 'green');
realityServer.switchClass(thisObject.dom.querySelector('.target'), 'targetWidthMedium', 'one');

// let targetUrl = 'http://localhost:8080/obj/' + thisObject.name + '/target/target.jpg';
// let targetUrl = 'https://localhost:8080/obj/' + thisObject.name + '/target/target.jpg';
// thisObject.dom.querySelector(".target").style.backgroundImage = 'url("' + targetUrl + '")';
// thisObject.dom.querySelector(".target").style.backgroundSize = 'cover';

let ipAddress = realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface];
// add Image for Target
if (thisObject.targetsExist.jpgExists) {
thisObject.dom.querySelector('.objectTargetIcon').src = 'http://' + ipAddress + ':' + realityServer.states.serverPort + '/obj/' + thisObject.name + '/target/target.jpg';
thisObject.dom.querySelector('.objectTargetIcon').src = 'https://' + ipAddress + ':' + realityServer.states.serverPort + '/obj/' + thisObject.name + '/target/target.jpg';
} else if (thisObject.isAnchor) {
thisObject.dom.querySelector('.objectTargetIcon').src = '../libraries/gui/resources/anchor.svg';
}
Expand Down Expand Up @@ -727,7 +727,7 @@ realityServer.updateManageObjects = function (thisItem2) {

setTooltipTextForElement(thisFullScreen.querySelector('.fullscreen'),
'Open the screen HMI configured by the vuforia-spatial-screens-addon.' +
' (For this object: http://' + ipAddress + ': ' + thisObject.screenPort + ')');
' (For this object: https://' + ipAddress + ': ' + thisObject.screenPort + ')');

if (!thisObject.screenPort) {
thisFullScreen.querySelector('.fullscreen').classList.remove('purple');
Expand Down Expand Up @@ -763,7 +763,7 @@ realityServer.updateManageObjects = function (thisItem2) {
function addLinkToContent(buttonDiv, frameType) { // eslint-disable-line no-inner-declarations
buttonDiv.addEventListener('click', function () { // put in a closure so it references don't mutate
let ipAddress = realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface];
window.open('http://' + ipAddress + ':' + realityServer.states.serverPort + '/frames/' + frameType + '/index.html', '_blank'); // opens in new tab (instead of window.location.href = )
window.open('https://' + ipAddress + ':' + realityServer.states.serverPort + '/frames/' + frameType + '/index.html', '_blank'); // opens in new tab (instead of window.location.href = )
});
}

Expand Down Expand Up @@ -869,16 +869,16 @@ realityServer.updateManageFrames = function () {
function addLinkToContent(buttonDiv, frameType) { // eslint-disable-line no-inner-declarations
buttonDiv.addEventListener('click', function () {
let ipAddress = realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface];
// window.location.href = 'http://' + ipAddress + ':8080/frames/active/' + frameType + '/index.html';
window.open('http://' + ipAddress + ':' + realityServer.states.serverPort + '/frames/' + frameType + '/index.html', '_blank');
// window.location.href = 'https://' + ipAddress + ':8080/frames/active/' + frameType + '/index.html';
window.open('https://' + ipAddress + ':' + realityServer.states.serverPort + '/frames/' + frameType + '/index.html', '_blank');
});
}

let contentButton = frameInfo.dom.querySelector('.content');
addLinkToContent(contentButton, frameKey);

let ipAddress = realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface];
frameInfo.dom.querySelector('.frameIcon').src = 'http://' + ipAddress + ':' + realityServer.states.serverPort + '/frames/' + frameKey + '/icon.gif';
frameInfo.dom.querySelector('.frameIcon').src = 'https://' + ipAddress + ':' + realityServer.states.serverPort + '/frames/' + frameKey + '/icon.gif';

addZipDownload(frameInfo.dom.querySelector('.download'), frameKey);

Expand Down Expand Up @@ -922,7 +922,7 @@ realityServer.updateManageFrames = function () {

realityServer.selectHardwareInterfaceSettings = function (interfaceName) {
let ipAddress = realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface];
let pathToConfig = 'http://' + ipAddress + ':' + realityServer.states.serverPort + '/hardwareInterface/' + interfaceName + '/config.html';
let pathToConfig = 'https://' + ipAddress + ':' + realityServer.states.serverPort + '/hardwareInterface/' + interfaceName + '/config.html';
let configFrame = document.querySelector('.configFrame');
configFrame.src = pathToConfig;

Expand Down Expand Up @@ -2244,7 +2244,7 @@ realityServer.toggleFullScreen = function (item) {
// if we have set up a screen port, open up that hardware interface application

if (thisScreenPort) {
thisIframe.src = 'http://' + realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface] + ':' + thisScreenPort;
thisIframe.src = 'https://' + realityServer.states.ipAdress.interfaces[realityServer.states.ipAdress.activeInterface] + ':' + thisScreenPort;
} else {
// otherwise just view the target image in fullscreen
thisIframe.src = 'about:blank'; // Clear iframe before loading
Expand Down
10 changes: 9 additions & 1 deletion logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ const colorizedFormat = format.combine(

const logger = createLogger({
level: process.env.LOG_LEVEL || 'debug',
format: isMobile ? monochromeFormat : colorizedFormat,
format: winston.format.combine(
isMobile ? monochromeFormat : colorizedFormat,
winston.format(info => {
if (info.message.includes('Possibly unsupported ZIP platform type')) {
return false; // Ignore logs from decompress-zip structures.js when decompressing target.dat
}
return info;
})()
),
transports: [new transports.Console()]
});

Expand Down
2 changes: 2 additions & 0 deletions models/ObjectModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ function ObjectModel(ip, version, protocol, objectId) {
this.objectId = objectId;
// The name for the object used for interfaces.
this.name = '';
// The UUID used internally by Vuforia for tracking. Keep null for objects without targets (e.g. human pose, avatar)
this.targetId = null;

this.matrix = [];
this.worldId = null; // matrix is relative to this world
Expand Down
2 changes: 1 addition & 1 deletion models/ObjectSocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function ObjectSocket(socket, socketPort, ip) {
// keeps the own IP of an object
this.ip = ip;
// defines where to connect to
this.io = socket.connect('http://' + ip + ':' + socketPort, {
this.io = socket.connect('https://' + ip + ':' + socketPort, {
// defines the timeout for a connection between objects and the reality editor.
'connect timeout': 5000,
// try to reconnect
Expand Down
Loading

0 comments on commit dc77f50

Please sign in to comment.