node-red-contrib-smartnora
Version:
Google Smart Home integration via Smart Nora https://smart-nora.eu/
116 lines (115 loc) • 4.49 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpError = void 0;
exports.publishReplayRefCountWithDelay = publishReplayRefCountWithDelay;
exports.retryWithBackoff = retryWithBackoff;
exports.scanWithFactory = scanWithFactory;
exports.rateLimitSlidingWindow = rateLimitSlidingWindow;
exports.singleton = singleton;
exports.getHash = getHash;
exports.cloneDeep = cloneDeep;
const crypto_1 = require("crypto");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
function publishReplayRefCountWithDelay(delay) {
return (0, operators_1.share)({
connector: () => new rxjs_1.ReplaySubject(1),
resetOnComplete: true,
resetOnError: true,
resetOnRefCountZero: () => (0, rxjs_1.timer)(delay),
});
}
function retryWithBackoff(config) {
const { initialInterval = 100, maxRetryCount = 6, logError, shouldRetry } = config !== null && config !== void 0 ? config : {};
return (0, operators_1.retry)({
delay: (error, retryCount) => {
var _a;
logError === null || logError === void 0 ? void 0 : logError(error);
const attemptRetry = (_a = shouldRetry === null || shouldRetry === void 0 ? void 0 : shouldRetry(error)) !== null && _a !== void 0 ? _a : true;
if (attemptRetry) {
return (0, rxjs_1.timer)(Math.pow(2, retryCount) * initialInterval);
}
throw error;
},
count: maxRetryCount,
resetOnSuccess: true,
});
}
function scanWithFactory(next, factory) {
return source => (0, rxjs_1.defer)(() => (0, operators_1.scan)(next, factory())(source));
}
const NO_EVENT = Symbol('no-event');
function rateLimitSlidingWindow(windowSizeMiliseconds, numberOfEvents, mergeEvents) {
return source => source.pipe((0, operators_1.scan)((ctx, msg) => {
const now = new Date().getTime();
ctx.ticks = ctx.ticks.filter(t => t > now - windowSizeMiliseconds);
if (ctx.ticks.length === numberOfEvents) {
ctx.overflow = ctx.overflow !== NO_EVENT ? mergeEvents(msg, ctx.overflow) : msg;
ctx.fwdMessage = NO_EVENT;
}
else {
ctx.fwdMessage = ctx.overflow !== NO_EVENT ? mergeEvents(msg, ctx.overflow) : msg;
ctx.overflow = NO_EVENT;
ctx.ticks.push(now);
}
return ctx;
}, {
overflow: NO_EVENT,
fwdMessage: NO_EVENT,
ticks: [],
getNextFree() {
const eventsThatWereSentInTheWindow = this.ticks.slice(-numberOfEvents - 1);
const nextFreeEvent = new Date(eventsThatWereSentInTheWindow[0] + windowSizeMiliseconds + 500);
return nextFreeEvent;
},
}), (0, operators_1.switchMap)(ctx => {
const forward$ = ctx.fwdMessage === NO_EVENT ? rxjs_1.EMPTY : (0, rxjs_1.of)(ctx.fwdMessage);
const overflow$ = ctx.overflow === NO_EVENT ? rxjs_1.EMPTY :
(0, rxjs_1.timer)(ctx.getNextFree()).pipe((0, operators_1.map)(_ => {
const value = ctx.overflow;
if (value !== NO_EVENT) {
ctx.overflow = NO_EVENT;
ctx.ticks.push(new Date().getTime());
}
return value;
}), (0, operators_1.filter)(c => c !== NO_EVENT));
return (0, rxjs_1.concat)(forward$, overflow$);
}));
}
function singleton() {
return source => source.pipe((0, operators_1.share)({
connector: () => new rxjs_1.ReplaySubject(1),
resetOnRefCountZero: true,
}));
}
class HttpError extends Error {
constructor(statusCode, content) {
super(`HTTP response (${statusCode} ${content})`);
this.statusCode = statusCode;
this.content = content;
}
}
exports.HttpError = HttpError;
function getHash(input) {
if (typeof input !== 'string') {
input = JSON.stringify(flattenObject(input));
}
return (0, crypto_1.createHash)('sha256').update(input).digest('base64');
}
function flattenObject(input) {
switch (typeof input) {
case 'string':
case 'boolean':
case 'number':
return input;
}
if (Array.isArray(input)) {
return input;
}
const entries = Object.entries(input);
entries.sort((a, b) => a[0].localeCompare(b[0]));
return entries.map(([key, value]) => [key, flattenObject(value)]);
}
function cloneDeep(input) {
return JSON.parse(JSON.stringify(input));
}