465 lines
No EOL
17 KiB
JavaScript
465 lines
No EOL
17 KiB
JavaScript
// roomScheduleServer.js
|
|
//
|
|
// Created by Mark Brosche on 4/3/2019
|
|
// Handed off to Milad Nazeri on 5-15-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
|
|
|
|
|
|
(function() {
|
|
var CHANNEL = "HiFi.Google.Calendar";
|
|
var TOKEN = 0;
|
|
var EXPIRE_TIME = 1;
|
|
var TIMEZONE_OFFSET = 2;
|
|
var TIMEZONE_NAME = 3;
|
|
var ADDRESS = 4;
|
|
var INTERVAL_FREQUENCY_MS = 60000;
|
|
var EXPIRY_BUFFER_MS = 300000;
|
|
var HOURS_PER_DAY = 24;
|
|
var NOON_HR = 12;
|
|
var SIGN_TEXT_COLOR_AVAILABLE = [168, 255, 168];
|
|
var SIGN_TEXT_COLOR_INUSE = [255, 168, 168];
|
|
var SIGN_BACKGROUND_COLOR_AVAILABLE = [125, 255, 125];
|
|
var SIGN_BACKGROUND_COLOR_INUSE = [255, 125, 125];
|
|
var REFRESH_TIMEOUT = 1920000; // 32 minutes
|
|
var SCRIPT_NAME = "roomScheduleServer.js";
|
|
var that = this;
|
|
|
|
this.remotelyCallable = [
|
|
"refreshToken",
|
|
"secondarySync",
|
|
"updateTimeZoneInfo"
|
|
];
|
|
|
|
|
|
this.preload = function(entityID) {
|
|
that.entityID = entityID;
|
|
that.entityProperties = Entities.getEntityProperties(that.entityID, ['privateUserData', 'userData', 'name']);
|
|
var userData;
|
|
var privateUserData;
|
|
|
|
try {
|
|
if (that.entityProperties.userData.length > 0) {
|
|
userData = JSON.parse(that.entityProperties.userData);
|
|
} else {
|
|
userData = {};
|
|
}
|
|
if (that.entityProperties.privateUserData.length > 0) {
|
|
privateUserData = JSON.parse(that.entityProperties.privateUserData);
|
|
} else {
|
|
privateUserData = {};
|
|
}
|
|
} catch (e) {
|
|
console.log(e, "Could not parse userData");
|
|
return;
|
|
}
|
|
|
|
that.request = Script.require('https://hifi-content.s3.amazonaws.com/Experiences/Releases/modules/request/v1.0/request.js').request;
|
|
|
|
that.room = {
|
|
"eventList": []
|
|
};
|
|
|
|
that.TOKEN_SERVER_ID = userData.tokenServerID;
|
|
that.secondaryRoomSchedule = userData.secondaryRoomSchedule;
|
|
that.roomScheduleID = userData.roomScheduleID;
|
|
that.roomOccupantsListID = userData.roomOccupantsListID;
|
|
that.roomColorID = userData.roomColorID;
|
|
that.roomColorOccupantsID = userData.roomColorOccupantsID;
|
|
that.roomClockID = userData.roomClockID;
|
|
that.roomEntityIDs = [
|
|
that.roomScheduleID,
|
|
that.roomColorID,
|
|
that.roomColorOccupantsID,
|
|
that.roomOccupantsListID
|
|
];
|
|
that.timezoneName = userData.timezoneName;
|
|
that.address = userData.address;
|
|
that.expireTime = userData.expireTime;
|
|
|
|
that.isSecondarySchedule = userData.isSecondarySchedule;
|
|
|
|
that.token = privateUserData.token;
|
|
that.timezoneOffset = +userData.timezoneOffset;
|
|
|
|
that.clearEventList(that.entityID);
|
|
that.updateSignColor(that.roomEntityIDs[1], [true]);
|
|
that.updateSignColor(that.roomEntityIDs[2], [true]);
|
|
|
|
that.sentAlready = false;
|
|
|
|
|
|
if (that.secondaryRoomSchedule){
|
|
Entities.callEntityMethod(that.secondaryRoomSchedule, "updateTimeZoneInfo", [that.timezoneName, that.timezoneOffset ]);
|
|
}
|
|
|
|
if (that.token && !that.isSecondarySchedule) {
|
|
that.googleRequest(that.token);
|
|
}
|
|
|
|
if (!that.isSecondarySchedule) {
|
|
Entities.callEntityMethod(that.roomClockID, "refreshTimezone", [that.timezoneName, that.timezoneOffset]);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
// Make sure the secondary calendar has the correct timezone for it's ui update
|
|
that.updateTimeZoneInfo = function(id, params) {
|
|
var userData = Entities.getEntityProperties(that.entityID, ['userData']).userData;
|
|
try {
|
|
if (userData.length > 0) {
|
|
userData = JSON.parse(userData);
|
|
} else {
|
|
userData = {};
|
|
}
|
|
} catch (e) {
|
|
console.log("trouble parsing userData");
|
|
}
|
|
userData.timezoneName = params[0];
|
|
userData.timezoneOffset = params[1];
|
|
|
|
Entities.editEntity(that.entityID, { userData: JSON.stringify(userData) });
|
|
};
|
|
|
|
|
|
// If this is a secondary display, then this will update the UI without making a call
|
|
this.secondarySync = function(id, params) {
|
|
var events = JSON.parse(params[0]);
|
|
|
|
that.createEvents(events);
|
|
};
|
|
|
|
|
|
// Called when the token server has a new token to give the calendar schedules
|
|
this.refreshToken = function(id, params) {
|
|
if (that.entityID === id) {
|
|
that.entityProperties = Entities.getEntityProperties(that.entityID, ['userData', 'name']);
|
|
|
|
if (that.interval) {
|
|
Script.clearInterval(that.interval);
|
|
that.interval = false;
|
|
}
|
|
|
|
var userData;
|
|
var privateUserData = {};
|
|
if (that.entityProperties.userData.length !== 0) {
|
|
try {
|
|
userData = JSON.parse(that.entityProperties.userData);
|
|
} catch (e) {
|
|
console.log(e, "no userData found");
|
|
}
|
|
} else {
|
|
userData = {};
|
|
}
|
|
|
|
that.token = privateUserData.token = params[TOKEN];
|
|
that.expireTime = userData.expireTime = params[EXPIRE_TIME];
|
|
that.timezoneOffset = userData.timezoneOffset = +params[TIMEZONE_OFFSET];
|
|
that.timezoneName = userData.timezoneName = params[TIMEZONE_NAME];
|
|
that.address = userData.address = params[ADDRESS];
|
|
|
|
Entities.editEntity(that.entityID, {
|
|
userData: JSON.stringify(userData),
|
|
privateUserData: JSON.stringify(privateUserData)
|
|
});
|
|
|
|
that.sentAlready = false;
|
|
|
|
Entities.callEntityMethod(that.roomClockID, "refreshTimezone", [that.timezoneName, that.timezoneOffset]);
|
|
|
|
that.googleRequest(that.token);
|
|
}
|
|
};
|
|
|
|
|
|
// Prepare the API request to google
|
|
this.googleRequest = function(token) {
|
|
var tomorrowMidnight = new Date();
|
|
tomorrowMidnight.setHours(HOURS_PER_DAY, 0, 0, 0);
|
|
that.scheduleURL = encodeURI("https://www.googleapis.com/calendar/v3/calendars/" +
|
|
that.address +
|
|
"/events?&timeMin=" + (new Date()).toISOString() +
|
|
"&timeMax=" + tomorrowMidnight.toISOString() +
|
|
"&showDeleted=" + false +
|
|
"&singleEvents=" + true +
|
|
"&maxResults=2" +
|
|
"&orderBy=startTime" +
|
|
"&access_token=" + token);
|
|
if (!that.interval) {
|
|
that.requestScheduleData(that.scheduleURL);
|
|
} else {
|
|
Script.clearInterval(that.interval);
|
|
that.requestScheduleData(that.scheduleURL);
|
|
}
|
|
};
|
|
|
|
|
|
// Call the google API and get back the list of events to post
|
|
var RETRY_TIMEOUT = 5000;
|
|
this.requestScheduleData = function(scheduleURL) {
|
|
that.request(scheduleURL, function (error, response) {
|
|
if (error) {
|
|
Messages.sendMessage(CHANNEL, JSON.stringify({
|
|
type: "ERROR",
|
|
entityName: that.entityProperties.name,
|
|
errorMessage: error,
|
|
actionAttempted: "Requesting schedule from Google - Initial"
|
|
}));
|
|
if (that.interval) {
|
|
Script.clearInterval(that.interval);
|
|
that.interval = false;
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
var events = response.items;
|
|
that.createEvents(events);
|
|
}
|
|
});
|
|
that.interval = Script.setInterval(function() {
|
|
|
|
if ((new Date()).valueOf() > (that.expireTime - EXPIRY_BUFFER_MS) && !that.sentAlready) {
|
|
that.sentAlready = true;
|
|
Entities.callEntityMethod(that.TOKEN_SERVER_ID, "tokenCheck", [that.token, that.entityID]);
|
|
} else if ((new Date()).valueOf() > (that.expireTime)) {
|
|
Messages.sendMessage(CHANNEL, JSON.stringify({
|
|
type: "ERROR",
|
|
entityName: that.entityProperties.name,
|
|
errorMessage: "Token expired without refreshing",
|
|
actionAttempted: "Requesting refresh token from Token Server"
|
|
}));
|
|
if (that.interval) {
|
|
Script.clearInterval(that.interval);
|
|
that.interval = false;
|
|
}
|
|
return;
|
|
}
|
|
that.request(scheduleURL, function (error, response) {
|
|
if (error) {
|
|
if (that.interval) {
|
|
Script.clearInterval(that.interval);
|
|
that.interval = false;
|
|
}
|
|
Messages.sendMessage(CHANNEL, JSON.stringify({
|
|
type: "ERROR",
|
|
entityName: that.entityProperties.name,
|
|
errorMessage: error,
|
|
actionAttempted: "Requesting schedule from Google - Loop"
|
|
}));
|
|
return;
|
|
} else {
|
|
var events = response.items;
|
|
that.createEvents(events);
|
|
}
|
|
});
|
|
}, INTERVAL_FREQUENCY_MS);
|
|
};
|
|
|
|
|
|
// handle preparing the event post
|
|
this.createEvents = function(events) {
|
|
that.clearEventList(that.entityID);
|
|
if (events.length > 0) {
|
|
for (var i = 0; i < events.length; i++) {
|
|
var event = events[i];
|
|
var start = event.start.dateTime;
|
|
var end = event.end.dateTime;
|
|
var summary = event.summary;
|
|
if (!(start && end)) {
|
|
console.log("Event received without a start and end dateTime!");
|
|
continue;
|
|
}
|
|
var startTimestamp = that.googleDateToUTCDate(start);
|
|
var endTimestamp = that.googleDateToUTCDate(end);
|
|
|
|
that.postEvents(that.entityID, summary, startTimestamp, endTimestamp);
|
|
}
|
|
} else {
|
|
Entities.editEntity(that.entityID, {text: "Nothing on the schedule for this room today."});
|
|
}
|
|
|
|
if (that.secondaryRoomSchedule){
|
|
Entities.callEntityMethod(that.secondaryRoomSchedule, "updateTimeZoneInfo", [that.timezoneName, that.timezoneOffset ]);
|
|
Entities.callEntityMethod(that.secondaryRoomSchedule, "secondarySync", [JSON.stringify(events)]);
|
|
}
|
|
|
|
Entities.callEntityMethod(that.roomClockID, "refreshTimezone", [that.timezoneName, that.timezoneOffset]);
|
|
};
|
|
|
|
|
|
// Format the given google date from the response
|
|
var googleDate = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-]\d{2}):(\d{2})$/;
|
|
var MINS_PER_HOUR = 60;
|
|
this.googleDateToUTCDate = function(d) {
|
|
var m = googleDate.exec(d);
|
|
var year = +m[1];
|
|
var month = +m[2];
|
|
var day = +m[3];
|
|
var hour = +m[4];
|
|
var minute = +m[5];
|
|
var second = +m[6];
|
|
var msec = 0;
|
|
var tzHour = +m[7];
|
|
var tzMin = +m[8];
|
|
var tzOffset = new Date().getTimezoneOffset() + tzHour * MINS_PER_HOUR + tzMin;
|
|
var date = new Date(Date.UTC(year, month - 1, day, hour, minute - tzOffset, second, msec));
|
|
return date;
|
|
};
|
|
|
|
|
|
// Update the calendar UI when we have the latest calendar information
|
|
this.postEvents = function(id, summary, start, end) {
|
|
if (id === that.entityID) {
|
|
var printedSchedule = '';
|
|
|
|
var startTimestamp = start;
|
|
var endTimestamp = end;
|
|
|
|
var tempEvent = {
|
|
summary: summary,
|
|
startTimestamp: startTimestamp,
|
|
endTimestamp: endTimestamp
|
|
};
|
|
|
|
that.room.eventList.push(tempEvent);
|
|
|
|
that.room.eventList.forEach(function(event) {
|
|
var startHours;
|
|
var endHours;
|
|
|
|
var eventStartHour = +event.startTimestamp.getHours();
|
|
var eventEndHour = +event.endTimestamp.getHours();
|
|
if (eventStartHour + that.timezoneOffset <= 0) {
|
|
startHours = eventStartHour + that.timezoneOffset + NOON_HR;
|
|
} else if (eventStartHour + that.timezoneOffset <= (NOON_HR)) {
|
|
startHours = eventStartHour + that.timezoneOffset;
|
|
} else {
|
|
startHours = eventStartHour + that.timezoneOffset - NOON_HR;
|
|
}
|
|
|
|
if (eventEndHour + that.timezoneOffset <= 0) {
|
|
endHours = eventEndHour + that.timezoneOffset + NOON_HR;
|
|
} else if (eventEndHour + that.timezoneOffset <= (NOON_HR)) {
|
|
endHours = eventEndHour + that.timezoneOffset;
|
|
} else {
|
|
endHours = eventEndHour + that.timezoneOffset - NOON_HR;
|
|
}
|
|
|
|
var startAmPm;
|
|
var endAmPm;
|
|
|
|
if (eventStartHour + that.timezoneOffset < 0) {
|
|
startAmPm = "pm";
|
|
} else if (eventStartHour + that.timezoneOffset < (NOON_HR)) {
|
|
startAmPm = "am";
|
|
} else {
|
|
startAmPm = "pm";
|
|
}
|
|
|
|
if (eventEndHour + that.timezoneOffset < 0) {
|
|
endAmPm = "pm";
|
|
} else if (eventEndHour + that.timezoneOffset < (NOON_HR)) {
|
|
endAmPm = "am";
|
|
} else {
|
|
endAmPm = "pm";
|
|
}
|
|
|
|
printedSchedule =
|
|
printedSchedule +
|
|
event.summary +
|
|
"\n" +
|
|
startHours +
|
|
':' +
|
|
(event.startTimestamp.getMinutes() < 10 ?
|
|
"0" + event.startTimestamp.getMinutes() :
|
|
event.startTimestamp.getMinutes()) +
|
|
' ' +
|
|
startAmPm +
|
|
' - ' +
|
|
endHours +
|
|
':' +
|
|
(event.endTimestamp.getMinutes() < 10 ?
|
|
"0" + event.endTimestamp.getMinutes() :
|
|
event.endTimestamp.getMinutes()) +
|
|
' ' +
|
|
endAmPm +
|
|
' ' + that.timezoneName + '\n\n';
|
|
});
|
|
|
|
Entities.editEntity(id, {text: printedSchedule});
|
|
that.setBusyLight();
|
|
}
|
|
};
|
|
|
|
|
|
// Erase the current event list
|
|
this.clearEventList = function(id) {
|
|
that.room.eventList = [];
|
|
Entities.editEntity(id, {text: ""});
|
|
};
|
|
|
|
|
|
// Switch the color occupants the top color of the schedule to show if it is in use or available
|
|
this.setBusyLight = function() {
|
|
var now = new Date();
|
|
var isAvailable = true;
|
|
if (typeof that.room.eventList[0] === "object") {
|
|
var endValue = that.room.eventList[0].endTimestamp;
|
|
var startValue = that.room.eventList[0].startTimestamp;
|
|
if (now <= endValue && now >= startValue) {
|
|
isAvailable = false;
|
|
} else {
|
|
isAvailable = true;
|
|
}
|
|
} else {
|
|
isAvailable = true;
|
|
}
|
|
that.updateSignColor(that.roomEntityIDs[1], [isAvailable]);
|
|
that.updateSignColor(that.roomEntityIDs[2], [isAvailable]);
|
|
};
|
|
|
|
|
|
// Handles whenever the color occupants and the top availabilty/in use color bar need to be changed
|
|
this.updateSignColor = function(id, params) {
|
|
if (id === that.roomEntityIDs[1]) {
|
|
if (params[0] === true) {
|
|
Entities.editEntity(id, {
|
|
"text": 'AVAILABLE',
|
|
"textColor": [0,0,0],
|
|
"textAlpha": 1,
|
|
"backgroundColor": SIGN_BACKGROUND_COLOR_AVAILABLE
|
|
});
|
|
} else {
|
|
Entities.editEntity(id, {
|
|
"text": 'IN USE',
|
|
"textColor": [0,0,0],
|
|
"textAlpha": 1,
|
|
"backgroundColor": SIGN_BACKGROUND_COLOR_INUSE
|
|
});
|
|
}
|
|
} else {
|
|
if (params[0] === true) {
|
|
Entities.editEntity(id, {
|
|
"text": 'occupants',
|
|
"textColor": SIGN_TEXT_COLOR_AVAILABLE
|
|
});
|
|
} else {
|
|
Entities.editEntity(id, {
|
|
"text": 'occupants',
|
|
"textColor": SIGN_TEXT_COLOR_INUSE
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
this.unload = function() {
|
|
if (that.interval) {
|
|
Script.clearInterval(that.interval);
|
|
that.interval = false;
|
|
}
|
|
};
|
|
}); |