content/hifi-content/brosche/dev/googleCalendar/clientScripts/hiFiCalendar_app.js
2022-02-13 21:50:01 +01:00

210 lines
9.2 KiB
JavaScript

// hiFiCalendar_app.js
//
// Created by Mark Brosche on 4-2-2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/*
Here's what I'd expect here:
1. The very first time your HTML UI is open, OR whenever you need to manually kickoff the token process,
you'll get here (the HTML UI's JS will send the "TOKEN" message to the app JS). Included in that message data is:
- The token itself
- The expiration time of the token, and the time until that token will expire
You will need this client script to keep track of the tokens it has sent during the session.
2. Send that data to each of the calendar label entities. You could store the data in userData, OR you could store the
data in the RAM of the ESS (which would be safer, but slightly more error-prone - your call)
3. Tell each calendar label entity to immediately refresh the calendar data (you can do that with Entities.callEntityServerMethod()
after defining some remotelyCallable method on the server script). When this call is received, the server
script will also cancel any "auto-refresh-calendar-events" timer that's currently active.
4. Also tell each calendar label entity to kick off a timer that will expire N seconds before the OAuth token expires. When
that timer expires, the server script will send a message (over the messages mixer, probably) to all clients able
to accept that method, which will hopefully only be you. The message data will contain the OAuth token that the server script
is currently using.
5. When your client script receives this message, it will check to see if it's already tried to refresh that token (see the last
sentence in (1)). If it hasn't, it should use the request module to submit a POST request to `https://www.googleapis.com/oauth2/v4/token`
to renew that token. Once the response from Google is received, it'll send a message to the calendars that requested that THAT
specific token be refreshed. (So, this script will have to keep track of the old version and refreshed version of each token).
6. When this call is received, the server script will immediately refresh calendar data.
It'll also cancel any "auto-refresh-calendar-events" timer that's currently active.
*/
(function() {
// This function decides how to handle web events from the tablet UI.
// used by 'ui' in startup()
var CHANNEL = "HiFi.Google.Calendar";
var MS_TO_SEC = 1000;
var MIN_PER_HR = 60;
var ATLANTIS_LABEL_ID = "{24b7b274-25a2-4dde-a241-f90da21de4c8}";
var JAKKU_LABEL_ID = "{c3f87945-e372-4f85-bde2-9d99d3633125}";
var CAPITOL_LABEL_ID = "{010134dd-8608-4eef-b0a8-8316dcb982d8}";
var FANTASIA_LABEL_ID = "{3aa8bb2e-b008-4d87-9fff-f0a4fda0c9d2}";
var OZ_LABEL_ID = "{3aa8bb2e-b008-4d87-9fff-f0a4fda0c9d2}";
var NARNIA_LABEL_ID = "{cb0571ff-21f9-4d60-8d43-ebc5e80704a3}";
var calendarScheduleIDs = [
ATLANTIS_LABEL_ID,
JAKKU_LABEL_ID,
CAPITOL_LABEL_ID,
FANTASIA_LABEL_ID,
OZ_LABEL_ID,
NARNIA_LABEL_ID
];
var request = Script.require('request').request;
var authCode;
var token;
var tokenLifetime;
var expireTime;
var clientID;
var refreshToken;
var timezone;
var secret;
function onWebMessage(data) {
// EventBridge message from HTML script.
switch (data.type) {
case "EVENT_BRIDGE_OPEN_MESSAGE":
break;
case "AUTHCODE":
console.log(JSON.stringify(data));
authCode = data.authCode;
clientID = data.clientID;
secret = data.secret;
timezone = data.timezone;
var options = {};
options.method = "POST";
options.body = 'code=' + authCode +
'&client_id=' + clientID +
'&client_secret=' + secret +
'&redirect_uri=http://localhost' +
'&grant_type=authorization_code';
options.headers = {
'Content-Type': "application/x-www-form-urlencoded",
'Content-Length': options.body.length
};
options.uri = "https://www.googleapis.com/oauth2/v4/token";
console.log(JSON.stringify(options));
request(options, function(error, response) {
if (error) {
console.log("FAILED REQUEST: ", error, JSON.stringify(response));
return;
} else {
console.log("SUCCESS!!!!: ", error, JSON.stringify(response));
token = response.access_token;
refreshToken = response.refresh_token;
tokenLifetime = response.expires_in * MS_TO_SEC;
expireTime = new Date().valueOf() + tokenLifetime;
calendarScheduleIDs.forEach(function(entityID) {
sendToken(entityID);
});
}
});
break;
}
}
function sendToken(entityID) {
console.log("SENDING TOKEN");
var userData = Entities.getEntityProperties(entityID, ['userData']).userData;
if (userData) {
try {
userData = JSON.parse(userData);
} catch (e) {
console.log(e, "Could not parse userData");
return;
}
} else {
console.log("No userData found, didn't send anything");
return;
}
userData.token = token;
userData.expireTime = expireTime;
userData.timezoneOffset = (new Date().getTimezoneOffset()/MIN_PER_HR),
userData.timezoneName = timezone;
console.log("SENDING USER DATA: ", JSON.stringify(userData), " TO ", entityID);
Entities.editEntity(entityID, {
userData: JSON.stringify(userData)
});
Entities.callEntityServerMethod(entityID, "refreshToken");
}
var newMessageArrivalTime;
var lastMessageArrivalTime = 0;
var messageHandler = function(channel, message, senderUUID, localOnly) {
if (channel !== CHANNEL) {
return;
} else {
try {
message = JSON.parse(message);
} catch (e) {
console.log(e, "Could not parse message");
return;
}
if (message.type === "REFRESH TOKEN") {
newMessageArrivalTime = Date.now();
if (newMessageArrivalTime - lastMessageArrivalTime > (120 * MS_TO_SEC)) {
tokenCheck(message);
lastMessageArrivalTime = newMessageArrivalTime;
}
}
}
};
function tokenCheck(message) {
var returnUUID = message.uuid;
if (message.token === token) {
console.log("SENDING HTTP REQUEST");
var options = {};
options.method = "POST";
options.body = 'client_id=' + clientID +
'&client_secret=' + secret +
'&refresh_token='+ refreshToken +
'&grant_type=refresh_token';
options.headers = {
'Content-Type': "application/x-www-form-urlencoded",
'Content-Length': options.body.length
};
options.uri = "https://www.googleapis.com/oauth2/v4/token";
request(options, function(error, response) {
if (error) {
console.log("FAILED REFRESH REQUEST: ", error, JSON.stringify(response));
return;
} else {
console.log("REFRESH SUCCESS!!!! ", error, JSON.stringify(response));
token = response.access_token;
tokenLifetime = response.expires_in * MS_TO_SEC;
expireTime = new Date().valueOf() + tokenLifetime;
calendarScheduleIDs.forEach(function(entityID) {
sendToken(entityID);
});
}
});
} else {
console.log("SENDING CURRENT TOKEN");
sendToken(returnUUID);
}
}
// This function loads appui and connects to the needed signals
var AppUi = Script.require('appUi');
var ui;
function startup() {
ui = new AppUi({
home: "http://localhost/test.html?v6",
buttonName: "GCAL", // The name of your app
graphicsDirectory: Script.resolvePath("../resources/images/"), // Where your button icons are located
onMessage: onWebMessage
});
Script.scriptEnding.connect(scriptEnding);
Messages.subscribe(CHANNEL);
Messages.messageReceived.connect(messageHandler);
}
startup();
function scriptEnding() {
Messages.unsubscribe(CHANNEL);
Messages.messageReceived.disconnect(messageHandler);
}
})();