update dependencies and handle rate limits
This commit is contained in:
@@ -79,3 +79,8 @@ Presets are available for **Start Timer** and **Stop Timer**.
|
||||
|
||||
- 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)
|
||||
|
||||
### Version 2.1.3
|
||||
|
||||
- update dependencies (fix CVE-2025-58754)
|
||||
- handle rate limiting of toggl
|
||||
|
||||
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "toggl-track",
|
||||
"version": "2.1.2",
|
||||
"version": "2.1.3",
|
||||
"main": "dist/main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -24,20 +24,20 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@companion-module/base": "~1.11.3",
|
||||
"toggl-track": "^0.8.0"
|
||||
"@companion-module/base": "~1.13.4",
|
||||
"toggl-track": "https://github.com/krombel/toggl-track#v0.8.0-2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@companion-module/tools": "~2.3.0",
|
||||
"@types/node": "^22.14.1",
|
||||
"@companion-module/tools": "~2.4.2",
|
||||
"@types/node": "^22.18.12",
|
||||
"@types/ws": "^8",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint": "^9.38.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.5.1",
|
||||
"prettier": "^3.5.3",
|
||||
"lint-staged": "^16.2.6",
|
||||
"prettier": "^3.6.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1"
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.46.2"
|
||||
},
|
||||
"prettier": "@companion-module/tools/.prettierrc.json",
|
||||
"lint-staged": {
|
||||
|
||||
68
src/main.ts
68
src/main.ts
@@ -8,7 +8,7 @@ import UpdatePresets from './presets.js'
|
||||
import UpdateVariableDefinitions from './variables.js'
|
||||
import UpgradeScripts from './upgrades.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 { timecodeSince } from './utils.js'
|
||||
|
||||
@@ -23,8 +23,8 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
clients?: { id: number; label: string }[]
|
||||
currentEntry?: ITimeEntry
|
||||
|
||||
intervalId?: NodeJS.Timeout
|
||||
currentTimerUpdaterIntervalId?: NodeJS.Timeout
|
||||
intervalId?: NodeJS.Timeout // used for time entry poller and to postpone init on ratelimit
|
||||
currentTimerUpdaterIntervalId?: NodeJS.Timeout // used to update the timer duration variable every second
|
||||
|
||||
constructor(internal: unknown) {
|
||||
super(internal)
|
||||
@@ -52,6 +52,14 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
this.updatePresets()
|
||||
|
||||
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()
|
||||
|
||||
@@ -75,6 +83,7 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
const workSpaceDefaultChanged: boolean = this.config.workspaceName != config.workspaceName
|
||||
const timeEntryPollerChanged: boolean = this.config.startTimerPoller != config.startTimerPoller
|
||||
|
||||
const oldConfig = this.config
|
||||
this.config = config
|
||||
|
||||
if (apiTokenChanged) {
|
||||
@@ -87,6 +96,19 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
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 (this.config.startTimerPoller) {
|
||||
this.startTimeEntryPoller()
|
||||
@@ -128,11 +150,23 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
},
|
||||
})
|
||||
|
||||
const resp = await this.toggl.me.logged()
|
||||
if (resp !== '') {
|
||||
this.log('warn', 'error during token check: ' + resp)
|
||||
try {
|
||||
const resp = await this.toggl.me.logged()
|
||||
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.updateStatus(InstanceStatus.AuthenticationFailure, resp)
|
||||
this.updateStatus(InstanceStatus.AuthenticationFailure, (e as Error).message)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -205,6 +239,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
this.log('warn', 'Not authorized')
|
||||
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()
|
||||
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')
|
||||
return
|
||||
}
|
||||
if (this.toggl.getRateLimitedUntil() > 0) {
|
||||
this.log('warn', 'Ratelimited. Skipping get current timer')
|
||||
return
|
||||
}
|
||||
await this.getWorkspace()
|
||||
await this.getProjects()
|
||||
await this.getClients()
|
||||
@@ -237,6 +279,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
this.log('warn', 'Not authorized')
|
||||
return
|
||||
}
|
||||
if (this.toggl.getRateLimitedUntil() > 0) {
|
||||
this.log('warn', 'Ratelimited. Skipping get current timer')
|
||||
return
|
||||
}
|
||||
|
||||
// reset
|
||||
this.workspaceId = undefined
|
||||
@@ -362,6 +408,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
this.log('error', 'toggle not initialized. Do not start time')
|
||||
return
|
||||
}
|
||||
if (this.toggl.getRateLimitedUntil() > 0) {
|
||||
this.log('warn', 'Ratelimited. Skipping get current timer')
|
||||
return
|
||||
}
|
||||
|
||||
const currentId = await this.getCurrentTimer()
|
||||
let newEntry: ITimeEntry
|
||||
@@ -389,6 +439,10 @@ export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||
this.log('error', 'toggle not initialized. Do not start time')
|
||||
return
|
||||
}
|
||||
if (this.toggl.getRateLimitedUntil() > 0) {
|
||||
this.log('warn', 'Ratelimited. Skipping get current timer')
|
||||
return
|
||||
}
|
||||
const currentId = await this.getCurrentTimer()
|
||||
this.log('info', 'Trying to stop current timer id: ' + currentId)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user