update dependencies and handle rate limits

This commit is contained in:
2025-10-29 14:22:02 +01:00
parent b9060b683f
commit 8cd7127af8
4 changed files with 363 additions and 551 deletions

View File

@@ -79,3 +79,8 @@ Presets are available for **Start Timer** and **Stop Timer**.
- Update node runtime to node22 - Update node runtime to node22
- make polling interval configurable as toggl is updating their [API usage limits](https://support.toggl.com/en/articles/11484112-api-webhook-limits) - make polling interval configurable as toggl is updating their [API usage limits](https://support.toggl.com/en/articles/11484112-api-webhook-limits)
### Version 2.1.3
- update dependencies (fix CVE-2025-58754)
- handle rate limiting of toggl

View File

@@ -1,6 +1,6 @@
{ {
"name": "toggl-track", "name": "toggl-track",
"version": "2.1.2", "version": "2.1.3",
"main": "dist/main.js", "main": "dist/main.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -24,20 +24,20 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@companion-module/base": "~1.11.3", "@companion-module/base": "~1.13.4",
"toggl-track": "^0.8.0" "toggl-track": "https://github.com/krombel/toggl-track#v0.8.0-2"
}, },
"devDependencies": { "devDependencies": {
"@companion-module/tools": "~2.3.0", "@companion-module/tools": "~2.4.2",
"@types/node": "^22.14.1", "@types/node": "^22.18.12",
"@types/ws": "^8", "@types/ws": "^8",
"eslint": "^9.24.0", "eslint": "^9.38.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^15.5.1", "lint-staged": "^16.2.6",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"typescript": "~5.8.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.30.1" "typescript-eslint": "^8.46.2"
}, },
"prettier": "@companion-module/tools/.prettierrc.json", "prettier": "@companion-module/tools/.prettierrc.json",
"lint-staged": { "lint-staged": {

View File

@@ -8,7 +8,7 @@ import UpdatePresets from './presets.js'
import UpdateVariableDefinitions from './variables.js' import UpdateVariableDefinitions from './variables.js'
import UpgradeScripts from './upgrades.js' import UpgradeScripts from './upgrades.js'
import { UpdateFeedbacks } from './feedbacks.js' import { UpdateFeedbacks } from './feedbacks.js'
import { Toggl, ITimeEntry, IWorkspaceProject, IClient } from 'toggl-track' import { Toggl, ITimeEntry, IWorkspaceProject, IClient, isRatelimitError } from 'toggl-track'
import { togglGetWorkspaces } from './toggl-extend.js' import { togglGetWorkspaces } from './toggl-extend.js'
import { timecodeSince } from './utils.js' import { timecodeSince } from './utils.js'
@@ -23,8 +23,8 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
clients?: { id: number; label: string }[] clients?: { id: number; label: string }[]
currentEntry?: ITimeEntry currentEntry?: ITimeEntry
intervalId?: NodeJS.Timeout intervalId?: NodeJS.Timeout // used for time entry poller and to postpone init on ratelimit
currentTimerUpdaterIntervalId?: NodeJS.Timeout currentTimerUpdaterIntervalId?: NodeJS.Timeout // used to update the timer duration variable every second
constructor(internal: unknown) { constructor(internal: unknown) {
super(internal) super(internal)
@@ -52,6 +52,14 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
this.updatePresets() this.updatePresets()
await this.initToggleConnection() await this.initToggleConnection()
if (this.toggl && this.toggl.getRateLimitedUntil() > 0) {
this.log('warn', 'Ratelimited. Retry init in ' + this.toggl.getRateLimitedUntil() + ' seconds')
this.intervalId = setTimeout(() => {
void this.init(this.config)
}, this.toggl.getRateLimitedUntil() * 1000)
// skip further init until ratelimit is over
return
}
await this.loadStaticData() await this.loadStaticData()
@@ -75,6 +83,7 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
const workSpaceDefaultChanged: boolean = this.config.workspaceName != config.workspaceName const workSpaceDefaultChanged: boolean = this.config.workspaceName != config.workspaceName
const timeEntryPollerChanged: boolean = this.config.startTimerPoller != config.startTimerPoller const timeEntryPollerChanged: boolean = this.config.startTimerPoller != config.startTimerPoller
const oldConfig = this.config
this.config = config this.config = config
if (apiTokenChanged) { if (apiTokenChanged) {
@@ -87,6 +96,19 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
await this.loadStaticData() await this.loadStaticData()
} }
if (this.toggl && this.toggl.getRateLimitedUntil() > 0) {
this.log('warn', 'Ratelimited. Retry init in ' + this.toggl.getRateLimitedUntil() + ' seconds')
this.config = oldConfig // revert to old config until ratelimit is over
this.intervalId = setTimeout(() => {
// this harms the linter (handle unawaited promise in an non-async context)
void (async () => {
await this.configUpdated(config)
})()
}, this.toggl.getRateLimitedUntil() * 1000)
// skip further init until ratelimit is over
return
}
if (timeEntryPollerChanged) { if (timeEntryPollerChanged) {
if (this.config.startTimerPoller) { if (this.config.startTimerPoller) {
this.startTimeEntryPoller() this.startTimeEntryPoller()
@@ -128,11 +150,23 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
}, },
}) })
const resp = await this.toggl.me.logged() try {
if (resp !== '') { const resp = await this.toggl.me.logged()
this.log('warn', 'error during token check: ' + resp) if (resp !== '') {
this.log('warn', 'error during token check: ' + resp)
this.toggl = undefined
this.updateStatus(InstanceStatus.AuthenticationFailure, resp as string)
return
}
} catch (e: unknown) {
this.log('warn', 'error during token check: ' + (e as Error).message)
if (isRatelimitError(e)) {
this.log('warn', 'ratelimited. Will reset in' + e.resetAfterSeconds)
this.updateStatus(InstanceStatus.ConnectionFailure, `Ratelimited. Retry after ${e.resetAfterSeconds} seconds`)
return
}
this.toggl = undefined this.toggl = undefined
this.updateStatus(InstanceStatus.AuthenticationFailure, resp) this.updateStatus(InstanceStatus.AuthenticationFailure, (e as Error).message)
return return
} }
} }
@@ -205,6 +239,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
this.log('warn', 'Not authorized') this.log('warn', 'Not authorized')
return null return null
} }
if (this.toggl.getRateLimitedUntil() > 0) {
this.log('warn', 'Ratelimited. Skipping get current timer')
return null
}
const entry: ITimeEntry = await this.toggl.timeEntry.current() const entry: ITimeEntry = await this.toggl.timeEntry.current()
this.log('debug', 'response for timer id ' + JSON.stringify(entry)) this.log('debug', 'response for timer id ' + JSON.stringify(entry))
@@ -226,6 +264,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
this.log('warn', 'loadStaticData: toggle connection not set up') this.log('warn', 'loadStaticData: toggle connection not set up')
return return
} }
if (this.toggl.getRateLimitedUntil() > 0) {
this.log('warn', 'Ratelimited. Skipping get current timer')
return
}
await this.getWorkspace() await this.getWorkspace()
await this.getProjects() await this.getProjects()
await this.getClients() await this.getClients()
@@ -237,6 +279,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
this.log('warn', 'Not authorized') this.log('warn', 'Not authorized')
return return
} }
if (this.toggl.getRateLimitedUntil() > 0) {
this.log('warn', 'Ratelimited. Skipping get current timer')
return
}
// reset // reset
this.workspaceId = undefined this.workspaceId = undefined
@@ -362,6 +408,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
this.log('error', 'toggle not initialized. Do not start time') this.log('error', 'toggle not initialized. Do not start time')
return return
} }
if (this.toggl.getRateLimitedUntil() > 0) {
this.log('warn', 'Ratelimited. Skipping get current timer')
return
}
const currentId = await this.getCurrentTimer() const currentId = await this.getCurrentTimer()
let newEntry: ITimeEntry let newEntry: ITimeEntry
@@ -389,6 +439,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
this.log('error', 'toggle not initialized. Do not start time') this.log('error', 'toggle not initialized. Do not start time')
return return
} }
if (this.toggl.getRateLimitedUntil() > 0) {
this.log('warn', 'Ratelimited. Skipping get current timer')
return
}
const currentId = await this.getCurrentTimer() const currentId = await this.getCurrentTimer()
this.log('info', 'Trying to stop current timer id: ' + currentId) this.log('info', 'Trying to stop current timer id: ' + currentId)

821
yarn.lock

File diff suppressed because it is too large Load Diff