@skylord123/node-red-pebble-timeline
Version:
Node-RED nodes for interacting with the Pebble Timeline API
172 lines (151 loc) • 6.89 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
module.exports = function(RED) {
function PebbleTimelineListNode(config) {
RED.nodes.createNode(this, config);
const node = this;
// Get the config node
const configNode = RED.nodes.getNode(config.config);
if (!configNode) {
node.error("No Pebble Timeline configuration found");
return;
}
// Make sure storage directory exists
const storageDir = path.join(RED.settings.userDir, 'pebble-timeline');
fs.ensureDirSync(storageDir);
const pinsFile = path.join(storageDir, 'timeline-pins.json');
node.on('input', function(msg, send, done) {
// Backwards compatibility with Node-RED 0.x
send = send || function() { node.send.apply(node, arguments) };
let startTime = null;
let endTime = null;
let apiUrlOverride = null;
let tokenOverride = null;
// Process filter parameters in sequence
Promise.resolve()
.then(() => {
return new Promise((resolve) => {
if (config.apiUrl) {
RED.util.evaluateNodeProperty(config.apiUrl, config.apiUrlType, node, msg, (err, result) => {
if (!err && result) {
apiUrlOverride = result;
}
resolve();
});
} else {
resolve();
}
});
})
.then(() => {
return new Promise((resolve) => {
if (config.token) {
RED.util.evaluateNodeProperty(config.token, config.tokenType, node, msg, (err, result) => {
if (!err && result) {
tokenOverride = result;
}
resolve();
});
} else {
resolve();
}
});
})
.then(() => {
return new Promise((resolve) => {
if (config.startTime) {
RED.util.evaluateNodeProperty(config.startTime, config.startTimeType, node, msg, (err, result) => {
if (!err && result) {
startTime = new Date(result);
if (isNaN(startTime.getTime())) {
node.warn("Invalid start time format");
startTime = null;
}
}
resolve();
});
} else {
resolve();
}
});
})
.then(() => {
return new Promise((resolve) => {
if (config.endTime) {
RED.util.evaluateNodeProperty(config.endTime, config.endTimeType, node, msg, (err, result) => {
if (!err && result) {
endTime = new Date(result);
if (isNaN(endTime.getTime())) {
node.warn("Invalid end time format");
endTime = null;
}
}
resolve();
});
} else {
resolve();
}
});
})
.then(() => {
// Load the pins
let pinsData = {};
try {
if (fs.existsSync(pinsFile)) {
pinsData = JSON.parse(fs.readFileSync(pinsFile, 'utf8'));
}
} catch (error) {
node.warn(`Error loading pins file: ${error.message}`);
}
// Get the timeline token to use
let timelineToken = tokenOverride || configNode.credentials.timelineToken;
// Ensure we have a valid token
if (!timelineToken) {
node.warn("No valid timeline token provided");
timelineToken = "default"; // Use a default key to avoid errors
}
// Convert token to string to ensure it can be used as an object key
timelineToken = String(timelineToken);
// Get pins for this token only
let pins = [];
if (pinsData[timelineToken] && Array.isArray(pinsData[timelineToken])) {
pins = pinsData[timelineToken];
}
// Apply filters
const filteredPins = pins.filter(pin => {
let include = true;
if (startTime !== null) {
const pinTime = new Date(pin.time);
if (pinTime < startTime) {
include = false;
}
}
if (endTime !== null) {
const pinTime = new Date(pin.time);
if (pinTime > endTime) {
include = false;
}
}
return include;
});
// Note: Cleanup of old pins is handled in the add node
// Create output message
msg.payload = filteredPins;
msg.count = filteredPins.length;
node.status({fill: "green", shape: "dot", text: `${filteredPins.length} pins found`});
send(msg);
if (done) done();
})
.catch(error => {
node.error(`Error listing pins: ${error.message}`, msg);
if (done) done(error);
});
});
node.on('close', function() {
// Clean up any resources
});
}
RED.nodes.registerType("pebble-timeline-list", PebbleTimelineListNode, {
credentials: {}
});
};