Created
October 1, 2018 23:13
-
-
Save freaktechnik/9fa0e1eda076ade929d30d215506c8fc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class PollEndpoint { | |
constructor(aOpts = {}) { | |
this.poll = aOpts.poll; | |
this.state = Object.assign({ | |
isVisible: false, // timeline visibility (as in, are individual tweets visible) | |
isFocused: false, // IM tab focus | |
rateLimit: Infinity, | |
remaining: Infinity, | |
resetTime: Infinity, | |
requestsPerPoll: 1, | |
hasUnread: false, | |
}, aOpts.state); | |
this.lastPoll = 0; | |
this.lastInterval = 0; | |
this.isInterval = false; | |
this.stopWhenInvisible = aOpts.stopWhenInvisible || false; | |
this.stopWhenNotFocused = aOpts.stopWhenNotFocused || this.stopWhenInvisible; | |
this.stopWhenUnreadAndNotFocused = aOpts.stopWhenUnreadAndNotFocused || this.stopWhenNotFocused; | |
this.startPolling(); | |
} | |
getSecondsUntilNextAllowedRequest() { | |
const rateLimitSet = !isNaN(this.state.rateLimit) && Number.isFinite(this.state.rateLimit); | |
// Ignore any rate limit info if it's not actual info. | |
if (rateLimitSet) { | |
const secondsUntilReset = (this.state.resetTime - Date.now()) / 1000; | |
// Ignore stored rate limit info if it is expired | |
if (secondsUntilReset < 0) | |
return 0; | |
return this.state.requestsPerPoll * Math.ceil(secondsUntilReset / this.state.remaining); | |
} | |
return 0; | |
} | |
getPollInterval() { | |
const secondsUntilNextAllowedRequest = this.getSecondsUntilNextAllowedRequest(); | |
// Determine minimum poll rate from state. | |
let minSeconds = 600; // 10 mins. | |
if (this.state.isFocused) { | |
if (this.state.isVisible) { | |
minSeconds = 10; // 10 seconds | |
} else { | |
minSeconds = 60; // 3 mins | |
} | |
} | |
// This tries to never go above the rate limit by spacing out requests until the rate limit resets. | |
return Math.max(minSeconds, secondsUntilNextAllowedRequest) * 1000; | |
} | |
doPoll() { | |
this.lastPoll = Date.now(); | |
if (this.poll) { | |
this.state.remaining--; | |
this.poll(); | |
} | |
} | |
clearTimeout() { | |
if (this.timeout) { | |
if (this.isInterval) { | |
clearInterval(this.timeout); | |
} else { | |
clearTimeout(this.timeout); | |
} | |
} | |
} | |
setTimeout(aInterval, aDelay = 0) { | |
dump("setting timeout for "+aInterval+" with a delay of "+aDelay+"\n"); | |
this.lastInterval = aInterval; | |
this.isInterval = aDelay === 0; | |
if (this.isInterval) { | |
this.timeout = setInterval(() => this.doPoll(), this.lastInterval); | |
} else { | |
this.timeout = setTimeout(() => this.doPoll(), aDelay); | |
} | |
} | |
startPolling() { | |
const newInterval = this.getPollInterval(); | |
if (newInterval !== this.lastInterval || !this.isInterval) { | |
this.clearTimeout(); | |
this.setTimeout(newInterval); | |
} | |
} | |
updateState(aNewState) { | |
// Ensure integrity of new state | |
if (aNewState.isVisible) { | |
aNewState.hasUnread = false; | |
if (!aNewState.isFocused) | |
aNewState.isFocused = aNewState.isVisible; | |
} | |
else if (aNewState.hasOwnProperty('isFocused') && !aNewState.isFocused) | |
aNewState.isVisible = false; | |
// Update state, allow partial updates. | |
Object.assign(this.state, aNewState); | |
const newInterval = this.getPollInterval(); | |
if (newInterval !== this.lastInterval) { | |
let delay = undefined; | |
// Wait until rate limit has refreshed if the bucket is exhausted | |
if (this.state.remaining == 0) { | |
delay = this.state.resetTime - Date.now(); | |
} | |
// Immediately shorten the poll interval | |
if (this.lastPoll > 0) { | |
delay = Math.max(delay ? delay : 0, newInterval - Date.now() + this.lastPoll, 1); | |
} | |
this.clearTimeout(); | |
if ((!this.state.hasUnread || this.state.isFocused || !this.stopWhenUnreadAndNotFocused) && | |
(this.state.isFocused || !this.stopWhenNotFocused) && | |
(this.state.isVisible || !this.stopWhenInvisible)) | |
this.setTimeout(newInterval, delay); | |
else | |
dump("Stopped polling\n"); | |
} | |
} | |
updateStateFromRequest(aRequest) { | |
this.updateState({ | |
rateLimit: parseInt(aRequest.getResponseHeader('x-rate-limit-limit'), 10), | |
remaining: parseInt(aRequest.getResponseHeader('x-rate-limit-remaining'), 10), | |
resetTime: parseInt(aRequest.getResponseHeader('x-rate-limit-reset'), 10) * 1000, | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment