iobroker.time-tracker 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -305,6 +305,13 @@ local SQLite file, access is role-based, and every correction is written to an a
305
305
 
306
306
  ### **WORK IN PROGRESS**
307
307
 
308
+ ### 0.2.3 (2026-09-20)
309
+
310
+ - (Alex) fix: a corrected object definition really reaches existing installations now — `ensureObject` merges the
311
+ fields the adapter owns (`name`, `type`, `role`, `read`, `write`, `unit`) instead of only the name. An installation
312
+ created before 0.2.2 kept its old `role: "value"` on `commands.punchUserId`, so the object structure check still
313
+ reported `E1011`
314
+
308
315
  ### 0.2.2 (2026-09-20)
309
316
 
310
317
  - (Alex) object structure check: every object name carries all **eleven languages** now (`E6001`) and the employee-id
@@ -334,13 +341,6 @@ local SQLite file, access is role-based, and every correction is written to an a
334
341
  never reached npm (E2004, its pipeline was red). `npm run version:check` watches both rules from now on: it refuses
335
342
  `common.title` and every version in the news list has to exist on npm
336
343
 
337
- ### 0.1.8 (2026-09-19)
338
-
339
- - (Alex) fix: the pipeline of 0.1.7 failed in the ioBroker package test — `common.license` was added next to the
340
- existing `common.licenseInformation`, and the test refuses both together (“common.license should not exist together
341
- with common.licenseInformation”). The field is removed again, `npm run test:package` is part of the release checklist
342
- from now on, and `docs/entwicklung.md` records the rule
343
-
344
344
  Older entries are kept in [`CHANGELOG_OLD.md`](CHANGELOG_OLD.md).
345
345
 
346
346
  ## Provenance
@@ -68,7 +68,9 @@ function channelObject(name) {
68
68
  async function ensureObject(port, id, object) {
69
69
  var _a;
70
70
  await port.setObjectNotExists(id, object);
71
- await port.extendObject(id, { common: { name: (_a = object.common) == null ? void 0 : _a.name } });
71
+ const common = { ...(_a = object.common) != null ? _a : {} };
72
+ delete common.custom;
73
+ await port.extendObject(id, { common });
72
74
  }
73
75
  function readUserSnapshot(args) {
74
76
  var _a, _b, _c;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/adapter/states.ts"],
4
- "sourcesContent": ["/**\n * States published by the adapter.\n *\n * The adapter exposes a small, stable state tree so that other adapters (and the user's dashboards) can show\n * and trigger time tracking without speaking to the REST API:\n *\n * ```\n * users.<userId>.displayName / present / hasOpenEntry / lastPunch / todayWorkedMinutes / todayBalanceMinutes\n * users.<userId>.openConflicts\n * commands.punchUserId / punch / quickPunch / closeMonth / recalc\n * ```\n *\n * All objects are created with `setObjectNotExists`, so existing instances keep their settings. The logic works\n * on a tiny port interface (`StatePort`) \u2014 the adapter instance in production, a recorder in the tests.\n */\n\nimport type { UsersRepository } from \"../db/repositories/users\";\nimport type { AggregationService } from \"../services/aggregation\";\nimport type { SyncService } from \"../services/sync\";\nimport { STATE_NAMES, type StateName } from \"./stateNames\";\nimport { localDate as resolveLocalDate } from \"../util/time\";\n\n/** Subset of the adapter API the state layer needs. */\nexport interface StatePort {\n\t/**\n\t * Creates an object when it does not exist yet.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param object - object definition\n\t */\n\tsetObjectNotExists(id: string, object: ioBroker.SettableObject): Promise<unknown> | void;\n\t/**\n\t * Merges fields into an object that already exists.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param object - fields to merge\n\t */\n\textendObject(id: string, object: ioBroker.PartialObject): Promise<unknown> | void;\n\t/**\n\t * Writes a state value.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param value - value to write\n\t * @param ack - true for confirmed values\n\t */\n\tsetState(id: string, value: ioBroker.StateValue, ack?: boolean): Promise<unknown> | void;\n}\n\n/** Figures of one employee shown in the state tree. */\nexport interface UserSnapshot {\n\t/** Database id of the employee */\n\tuserId: number;\n\t/** Shown name */\n\tdisplayName: string;\n\t/** True when the last punch of today has no counterpart */\n\thasOpenEntry: boolean;\n\t/** Instant of the last punch of the day, `null` when there is none */\n\tlastPunchUtc: number | null;\n\t/** Net working time of today in minutes */\n\tworkedMinutes: number;\n\t/** Balance of today in minutes */\n\tbalanceMinutes: number;\n\t/** Punches waiting for a decision */\n\topenConflicts: number;\n\t/** Net working time of the current month in minutes */\n\tmonthWorkedMinutes: number;\n\t/** Balance of the current month in minutes */\n\tmonthBalanceMinutes: number;\n\t/** Balance of the current year in minutes */\n\tyearBalanceMinutes: number;\n}\n\n/** Figures of the whole company, published next to the employees. */\nexport interface CompanySnapshot {\n\t/** Employees that are clocked in right now */\n\tpresentCount: number;\n\t/** Names of those employees, separated by a comma (empty when nobody is present) */\n\tpresent: string;\n\t/** Punches of all employees waiting for a decision */\n\topenConflicts: number;\n\t/** Instant of the newest punch of today, `null` when nobody punched */\n\tlastPunchUtc: number | null;\n}\n\n/** Newest event of the instance, mirrored into the state tree. */\nexport interface EventSnapshot {\n\t/** Kind of the event, e.g. `punch` */\n\ttype: string;\n\t/** Instant the event belongs to */\n\tatUtc: number;\n\t/** Name of the employee the event belongs to, empty for instance wide events */\n\tuserName: string;\n\t/** Direction of a punch (`in`/`out`), empty for other events */\n\tdirection: string;\n\t/** Where the change came from, e.g. `web` or `terminal`, empty when unknown */\n\tsource: string;\n}\n\n/** Names of the writable command states. */\nexport const COMMAND_IDS = {\n\t/** Employee the commands apply to */\n\tpunchUserId: \"commands.punchUserId\",\n\t/** Punch in or out for `punchUserId` */\n\tpunch: \"commands.punch\",\n\t/** Punch in or out with the configured quick rounding */\n\tquickPunch: \"commands.quickPunch\",\n\t/** Close a month, value `YYYY-MM` */\n\tcloseMonth: \"commands.closeMonth\",\n\t/** Recalculate a period, value `YYYY-MM` or `YYYY` */\n\trecalc: \"commands.recalc\",\n\t/** Write a backup of the database */\n\tbackup: \"commands.backup\",\n} as const;\n\n/**\n * Builds a state object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @param type - state type\n * @param role - state role\n * @param options - additional flags\n * @param options.read - value can be read (default true)\n * @param options.write - value can be written (default false)\n * @param options.unit - unit of the value\n * @returns object definition\n */\nfunction stateObject(\n\tname: StateName,\n\ttype: ioBroker.CommonType,\n\trole: string,\n\toptions: { read?: boolean; write?: boolean; unit?: string } = {},\n): ioBroker.SettableObject {\n\treturn {\n\t\ttype: \"state\",\n\t\tcommon: {\n\t\t\tname,\n\t\t\ttype,\n\t\t\trole,\n\t\t\tread: options.read !== false,\n\t\t\twrite: options.write === true,\n\t\t\t...(options.unit ? { unit: options.unit } : {}),\n\t\t},\n\t\tnative: {},\n\t};\n}\n\n/**\n * Builds a channel object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @returns object definition\n */\nfunction channelObject(name: StateName): ioBroker.SettableObject {\n\treturn { type: \"channel\", common: { name }, native: {} };\n}\n\n/**\n * Creates an object when it is missing and keeps its name current.\n *\n * `setObjectNotExists` alone would leave the names of existing installations untouched \u2014 the ioBroker object\n * structure check (`E6001`) reads them from the running instance, so the name is refreshed on every start. The\n * merge only touches `common.name`, existing links (for example in `vis`) stay as they are.\n *\n * @param port - state port\n * @param id - full state id (including the instance prefix)\n * @param object - object definition\n */\nasync function ensureObject(port: StatePort, id: string, object: ioBroker.SettableObject): Promise<void> {\n\tawait port.setObjectNotExists(id, object);\n\tawait port.extendObject(id, { common: { name: object.common?.name } });\n}\n\n/**\n * Reads the figures of an employee for the state tree.\n *\n * @param args - data sources and employee\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service (open conflicts)\n * @param args.userId - database id of the employee\n * @param args.now - instant of the snapshot\n * @returns snapshot of the figures\n */\nexport function readUserSnapshot(args: {\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tuserId: number;\n\tnow?: number;\n}): UserSnapshot | null {\n\tconst user = args.users.findById(args.userId);\n\tif (!user) {\n\t\treturn null;\n\t}\n\n\tconst timestamp = args.now ?? Math.floor(Date.now() / 1000);\n\tconst today = resolveLocalDate(timestamp, user.timezone);\n\tconst day = args.aggregation.recalculateDay(user.id, today, { now: timestamp });\n\t// the month is recalculated like the month view does it; the year is read, so the publishing stays cheap\n\tconst year = Number(today.slice(0, 4));\n\tconst month = Number(today.slice(5, 7));\n\tconst monthAggregate = args.aggregation.recalculateMonth(user.id, year, month, { now: timestamp });\n\tconst yearAggregate = args.aggregation.year(user.id, year);\n\n\treturn {\n\t\tuserId: user.id,\n\t\tdisplayName: user.displayName,\n\t\thasOpenEntry: day.hasOpenEntry,\n\t\tlastPunchUtc: Math.max(day.firstInUtc ?? 0, day.lastOutUtc ?? 0) || null,\n\t\tworkedMinutes: day.workedMin,\n\t\tbalanceMinutes: day.balanceMin,\n\t\topenConflicts: args.sync.conflicts(user.id).length,\n\t\tmonthWorkedMinutes: monthAggregate.workedMin,\n\t\tmonthBalanceMinutes: monthAggregate.balanceMin,\n\t\tyearBalanceMinutes: yearAggregate ? yearAggregate.workedMin - yearAggregate.targetMin : 0,\n\t};\n}\n\n/**\n * Publishes the figures of one employee.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishUserSnapshot(port: StatePort, snapshot: UserSnapshot): Promise<void> {\n\tconst id = `users.${snapshot.userId}`;\n\tawait port.setState(`${id}.displayName`, snapshot.displayName, true);\n\tawait port.setState(`${id}.present`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.hasOpenEntry`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.lastPunch`, snapshot.lastPunchUtc ?? 0, true);\n\tawait port.setState(`${id}.todayWorkedMinutes`, snapshot.workedMinutes, true);\n\tawait port.setState(`${id}.todayBalanceMinutes`, snapshot.balanceMinutes, true);\n\tawait port.setState(`${id}.monthWorkedMinutes`, snapshot.monthWorkedMinutes, true);\n\tawait port.setState(`${id}.monthBalanceMinutes`, snapshot.monthBalanceMinutes, true);\n\tawait port.setState(`${id}.yearBalanceMinutes`, snapshot.yearBalanceMinutes, true);\n\tawait port.setState(`${id}.openConflicts`, snapshot.openConflicts, true);\n}\n\n/**\n * Creates the channels of all employees and publishes their current figures.\n *\n * @param args - state port and data sources\n * @param args.port - state port\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service\n * @param args.now - instant of the snapshot\n * @returns the published snapshots\n */\nexport async function publishAllUserStates(args: {\n\tport: StatePort;\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tnow?: number;\n}): Promise<UserSnapshot[]> {\n\tconst snapshots: UserSnapshot[] = [];\n\tfor (const user of args.users.list({ includeInactive: true })) {\n\t\tawait createUserChannel(args.port, user.id);\n\t\tconst snapshot = readUserSnapshot({\n\t\t\taggregation: args.aggregation,\n\t\t\tusers: args.users,\n\t\t\tsync: args.sync,\n\t\t\tuserId: user.id,\n\t\t\tnow: args.now,\n\t\t});\n\t\tif (!snapshot) {\n\t\t\tcontinue;\n\t\t}\n\t\tawait publishUserSnapshot(args.port, snapshot);\n\t\tsnapshots.push(snapshot);\n\t}\n\treturn snapshots;\n}\n\n/**\n * Creates the channel of one employee, including the shared parent channel `users`.\n *\n * The parent channel matters: the ioBroker object structure check (`E3009`) requires every state to have its\n * parents, so `users.<userId>.*` below a missing `users` produced 126 findings in the repository check\n * (PR #6697). `setObjectNotExists` keeps the call idempotent.\n *\n * @param port - state port\n * @param userId - database id of the employee\n * @returns the channel id\n */\nexport async function createUserChannel(port: StatePort, userId: number): Promise<string> {\n\tconst id = `users.${userId}`;\n\tawait ensureObject(port, \"users\", channelObject(STATE_NAMES.usersChannel));\n\tawait ensureObject(port, id, channelObject(STATE_NAMES.employeeChannel));\n\tawait ensureObject(port, `${id}.displayName`, stateObject(STATE_NAMES.displayName, \"string\", \"info.name\"));\n\t// the writable twin of `hasOpenEntry`: a script, a fingerprint reader or a dashboard writes it to say that\n\t// somebody arrived (`true`) or left (`false`) \u2014 see `presence.ts` for what the adapter does with it\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.present`,\n\t\tstateObject(STATE_NAMES.userPresent, \"boolean\", \"switch\", { write: true }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.hasOpenEntry`,\n\t\tstateObject(STATE_NAMES.hasOpenEntry, \"boolean\", \"indicator.working\"),\n\t);\n\tawait ensureObject(port, `${id}.lastPunch`, stateObject(STATE_NAMES.lastPunch, \"number\", \"value.time\"));\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.todayWorkedMinutes`,\n\t\tstateObject(STATE_NAMES.todayWorkedMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.todayBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.todayBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.monthWorkedMinutes`,\n\t\tstateObject(STATE_NAMES.monthWorkedMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.monthBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.monthBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.yearBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.yearBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(port, `${id}.openConflicts`, stateObject(STATE_NAMES.openConflicts, \"number\", \"value\"));\n\treturn id;\n}\n\n/**\n * Creates the writable command states.\n *\n * @param port - state port\n */\nexport async function createCommandStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"commands\", channelObject(STATE_NAMES.commandsChannel));\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.punchUserId,\n\t\tstateObject(STATE_NAMES.punchUserId, \"number\", \"level\", {\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.punch,\n\t\tstateObject(STATE_NAMES.punch, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.quickPunch,\n\t\tstateObject(STATE_NAMES.quickPunch, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.closeMonth,\n\t\tstateObject(STATE_NAMES.closeMonth, \"string\", \"text\", {\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(port, COMMAND_IDS.recalc, stateObject(STATE_NAMES.recalc, \"string\", \"text\", { write: true }));\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.backup,\n\t\tstateObject(STATE_NAMES.backupCommand, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n}\n\n/**\n * Creates the informational states of the instance.\n *\n * @param port - state port\n */\nexport async function createInfoStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"info\", channelObject(STATE_NAMES.infoChannel));\n\tawait ensureObject(port, \"info.version\", stateObject(STATE_NAMES.version, \"string\", \"text\"));\n\tawait ensureObject(port, \"info.schemaVersion\", stateObject(STATE_NAMES.schemaVersion, \"string\", \"text\"));\n\tawait ensureObject(\n\t\tport,\n\t\t\"info.dbSizeBytes\",\n\t\tstateObject(STATE_NAMES.dbSizeBytes, \"number\", \"value\", { unit: \"bytes\" }),\n\t);\n\tawait ensureObject(port, \"info.lastError\", stateObject(STATE_NAMES.lastError, \"string\", \"text\"));\n\tawait ensureObject(port, \"info.lastBackup\", stateObject(STATE_NAMES.lastBackup, \"number\", \"value.time\"));\n}\n\n/**\n * Creates the states of the company.\n *\n * @param port - state port\n */\nexport async function createCompanyStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"company\", channelObject(STATE_NAMES.companyChannel));\n\tawait ensureObject(port, \"company.presentCount\", stateObject(STATE_NAMES.companyPresentCount, \"number\", \"value\"));\n\tawait ensureObject(port, \"company.present\", stateObject(STATE_NAMES.companyPresent, \"string\", \"text\"));\n\tawait ensureObject(port, \"company.openConflicts\", stateObject(STATE_NAMES.openConflicts, \"number\", \"value\"));\n\tawait ensureObject(port, \"company.lastPunch\", stateObject(STATE_NAMES.companyLastPunch, \"number\", \"value.time\"));\n}\n\n/**\n * Reads the company figures out of the figures of the employees.\n *\n * @param snapshots - figures of all employees\n * @returns the company figures\n */\nexport function readCompanySnapshot(snapshots: UserSnapshot[]): CompanySnapshot {\n\tconst present = snapshots.filter(snapshot => snapshot.hasOpenEntry);\n\treturn {\n\t\tpresentCount: present.length,\n\t\tpresent: present.map(snapshot => snapshot.displayName).join(\", \"),\n\t\topenConflicts: snapshots.reduce((sum, snapshot) => sum + snapshot.openConflicts, 0),\n\t\tlastPunchUtc: snapshots.reduce<number | null>(\n\t\t\t(newest, snapshot) =>\n\t\t\t\tsnapshot.lastPunchUtc !== null && (newest === null || snapshot.lastPunchUtc > newest)\n\t\t\t\t\t? snapshot.lastPunchUtc\n\t\t\t\t\t: newest,\n\t\t\tnull,\n\t\t),\n\t};\n}\n\n/**\n * Publishes the company figures.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishCompanySnapshot(port: StatePort, snapshot: CompanySnapshot): Promise<void> {\n\tawait port.setState(\"company.presentCount\", snapshot.presentCount, true);\n\tawait port.setState(\"company.present\", snapshot.present, true);\n\tawait port.setState(\"company.openConflicts\", snapshot.openConflicts, true);\n\tawait port.setState(\"company.lastPunch\", snapshot.lastPunchUtc ?? 0, true);\n}\n\n/**\n * Creates the event states.\n *\n * @param port - state port\n */\nexport async function createEventStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"events\", channelObject(STATE_NAMES.eventsChannel));\n\tawait ensureObject(port, \"events.lastAt\", stateObject(STATE_NAMES.lastAt, \"number\", \"value.time\"));\n\tawait ensureObject(port, \"events.lastType\", stateObject(STATE_NAMES.lastType, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastUser\", stateObject(STATE_NAMES.lastUser, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastDirection\", stateObject(STATE_NAMES.lastDirection, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastSource\", stateObject(STATE_NAMES.lastSource, \"string\", \"text\"));\n}\n\n/**\n * Publishes the newest event.\n *\n * @param port - state port\n * @param snapshot - event to publish\n */\nexport async function publishEventSnapshot(port: StatePort, snapshot: EventSnapshot): Promise<void> {\n\tawait port.setState(\"events.lastAt\", snapshot.atUtc, true);\n\tawait port.setState(\"events.lastType\", snapshot.type, true);\n\tawait port.setState(\"events.lastUser\", snapshot.userName, true);\n\tawait port.setState(\"events.lastDirection\", snapshot.direction, true);\n\tawait port.setState(\"events.lastSource\", snapshot.source, true);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * States published by the adapter.\n *\n * The adapter exposes a small, stable state tree so that other adapters (and the user's dashboards) can show\n * and trigger time tracking without speaking to the REST API:\n *\n * ```\n * users.<userId>.displayName / present / hasOpenEntry / lastPunch / todayWorkedMinutes / todayBalanceMinutes\n * users.<userId>.openConflicts\n * commands.punchUserId / punch / quickPunch / closeMonth / recalc\n * ```\n *\n * All objects are created with `setObjectNotExists`, so existing instances keep their settings. The logic works\n * on a tiny port interface (`StatePort`) \u2014 the adapter instance in production, a recorder in the tests.\n */\n\nimport type { UsersRepository } from \"../db/repositories/users\";\nimport type { AggregationService } from \"../services/aggregation\";\nimport type { SyncService } from \"../services/sync\";\nimport { STATE_NAMES, type StateName } from \"./stateNames\";\nimport { localDate as resolveLocalDate } from \"../util/time\";\n\n/** Subset of the adapter API the state layer needs. */\nexport interface StatePort {\n\t/**\n\t * Creates an object when it does not exist yet.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param object - object definition\n\t */\n\tsetObjectNotExists(id: string, object: ioBroker.SettableObject): Promise<unknown> | void;\n\t/**\n\t * Merges fields into an object that already exists.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param object - fields to merge\n\t */\n\textendObject(id: string, object: ioBroker.PartialObject): Promise<unknown> | void;\n\t/**\n\t * Writes a state value.\n\t *\n\t * @param id - full state id (including the instance prefix)\n\t * @param value - value to write\n\t * @param ack - true for confirmed values\n\t */\n\tsetState(id: string, value: ioBroker.StateValue, ack?: boolean): Promise<unknown> | void;\n}\n\n/** Figures of one employee shown in the state tree. */\nexport interface UserSnapshot {\n\t/** Database id of the employee */\n\tuserId: number;\n\t/** Shown name */\n\tdisplayName: string;\n\t/** True when the last punch of today has no counterpart */\n\thasOpenEntry: boolean;\n\t/** Instant of the last punch of the day, `null` when there is none */\n\tlastPunchUtc: number | null;\n\t/** Net working time of today in minutes */\n\tworkedMinutes: number;\n\t/** Balance of today in minutes */\n\tbalanceMinutes: number;\n\t/** Punches waiting for a decision */\n\topenConflicts: number;\n\t/** Net working time of the current month in minutes */\n\tmonthWorkedMinutes: number;\n\t/** Balance of the current month in minutes */\n\tmonthBalanceMinutes: number;\n\t/** Balance of the current year in minutes */\n\tyearBalanceMinutes: number;\n}\n\n/** Figures of the whole company, published next to the employees. */\nexport interface CompanySnapshot {\n\t/** Employees that are clocked in right now */\n\tpresentCount: number;\n\t/** Names of those employees, separated by a comma (empty when nobody is present) */\n\tpresent: string;\n\t/** Punches of all employees waiting for a decision */\n\topenConflicts: number;\n\t/** Instant of the newest punch of today, `null` when nobody punched */\n\tlastPunchUtc: number | null;\n}\n\n/** Newest event of the instance, mirrored into the state tree. */\nexport interface EventSnapshot {\n\t/** Kind of the event, e.g. `punch` */\n\ttype: string;\n\t/** Instant the event belongs to */\n\tatUtc: number;\n\t/** Name of the employee the event belongs to, empty for instance wide events */\n\tuserName: string;\n\t/** Direction of a punch (`in`/`out`), empty for other events */\n\tdirection: string;\n\t/** Where the change came from, e.g. `web` or `terminal`, empty when unknown */\n\tsource: string;\n}\n\n/** Names of the writable command states. */\nexport const COMMAND_IDS = {\n\t/** Employee the commands apply to */\n\tpunchUserId: \"commands.punchUserId\",\n\t/** Punch in or out for `punchUserId` */\n\tpunch: \"commands.punch\",\n\t/** Punch in or out with the configured quick rounding */\n\tquickPunch: \"commands.quickPunch\",\n\t/** Close a month, value `YYYY-MM` */\n\tcloseMonth: \"commands.closeMonth\",\n\t/** Recalculate a period, value `YYYY-MM` or `YYYY` */\n\trecalc: \"commands.recalc\",\n\t/** Write a backup of the database */\n\tbackup: \"commands.backup\",\n} as const;\n\n/**\n * Builds a state object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @param type - state type\n * @param role - state role\n * @param options - additional flags\n * @param options.read - value can be read (default true)\n * @param options.write - value can be written (default false)\n * @param options.unit - unit of the value\n * @returns object definition\n */\nfunction stateObject(\n\tname: StateName,\n\ttype: ioBroker.CommonType,\n\trole: string,\n\toptions: { read?: boolean; write?: boolean; unit?: string } = {},\n): ioBroker.SettableObject {\n\treturn {\n\t\ttype: \"state\",\n\t\tcommon: {\n\t\t\tname,\n\t\t\ttype,\n\t\t\trole,\n\t\t\tread: options.read !== false,\n\t\t\twrite: options.write === true,\n\t\t\t...(options.unit ? { unit: options.unit } : {}),\n\t\t},\n\t\tnative: {},\n\t};\n}\n\n/**\n * Builds a channel object definition.\n *\n * @param name - name in English and German\n * @param name.en - English name\n * @param name.de - German name\n * @returns object definition\n */\nfunction channelObject(name: StateName): ioBroker.SettableObject {\n\treturn { type: \"channel\", common: { name }, native: {} };\n}\n\n/**\n * Creates an object when it is missing and keeps the definition of an existing one current.\n *\n * `setObjectNotExists` alone would leave existing installations on the definition they were created with \u2014 a later\n * correction (for example the role of `commands.punchUserId`, checker `E1011`) would never arrive. So the fields the\n * adapter owns are merged on every start; `common.custom` is left out, that is where the user configures `history`\n * and similar integrations.\n *\n * @param port - state port\n * @param id - full state id (including the instance prefix)\n * @param object - object definition\n */\nasync function ensureObject(port: StatePort, id: string, object: ioBroker.SettableObject): Promise<void> {\n\tawait port.setObjectNotExists(id, object);\n\tconst common: Record<string, unknown> = { ...(object.common ?? {}) };\n\tdelete common.custom;\n\tawait port.extendObject(id, { common });\n}\n\n/**\n * Reads the figures of an employee for the state tree.\n *\n * @param args - data sources and employee\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service (open conflicts)\n * @param args.userId - database id of the employee\n * @param args.now - instant of the snapshot\n * @returns snapshot of the figures\n */\nexport function readUserSnapshot(args: {\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tuserId: number;\n\tnow?: number;\n}): UserSnapshot | null {\n\tconst user = args.users.findById(args.userId);\n\tif (!user) {\n\t\treturn null;\n\t}\n\n\tconst timestamp = args.now ?? Math.floor(Date.now() / 1000);\n\tconst today = resolveLocalDate(timestamp, user.timezone);\n\tconst day = args.aggregation.recalculateDay(user.id, today, { now: timestamp });\n\t// the month is recalculated like the month view does it; the year is read, so the publishing stays cheap\n\tconst year = Number(today.slice(0, 4));\n\tconst month = Number(today.slice(5, 7));\n\tconst monthAggregate = args.aggregation.recalculateMonth(user.id, year, month, { now: timestamp });\n\tconst yearAggregate = args.aggregation.year(user.id, year);\n\n\treturn {\n\t\tuserId: user.id,\n\t\tdisplayName: user.displayName,\n\t\thasOpenEntry: day.hasOpenEntry,\n\t\tlastPunchUtc: Math.max(day.firstInUtc ?? 0, day.lastOutUtc ?? 0) || null,\n\t\tworkedMinutes: day.workedMin,\n\t\tbalanceMinutes: day.balanceMin,\n\t\topenConflicts: args.sync.conflicts(user.id).length,\n\t\tmonthWorkedMinutes: monthAggregate.workedMin,\n\t\tmonthBalanceMinutes: monthAggregate.balanceMin,\n\t\tyearBalanceMinutes: yearAggregate ? yearAggregate.workedMin - yearAggregate.targetMin : 0,\n\t};\n}\n\n/**\n * Publishes the figures of one employee.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishUserSnapshot(port: StatePort, snapshot: UserSnapshot): Promise<void> {\n\tconst id = `users.${snapshot.userId}`;\n\tawait port.setState(`${id}.displayName`, snapshot.displayName, true);\n\tawait port.setState(`${id}.present`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.hasOpenEntry`, snapshot.hasOpenEntry, true);\n\tawait port.setState(`${id}.lastPunch`, snapshot.lastPunchUtc ?? 0, true);\n\tawait port.setState(`${id}.todayWorkedMinutes`, snapshot.workedMinutes, true);\n\tawait port.setState(`${id}.todayBalanceMinutes`, snapshot.balanceMinutes, true);\n\tawait port.setState(`${id}.monthWorkedMinutes`, snapshot.monthWorkedMinutes, true);\n\tawait port.setState(`${id}.monthBalanceMinutes`, snapshot.monthBalanceMinutes, true);\n\tawait port.setState(`${id}.yearBalanceMinutes`, snapshot.yearBalanceMinutes, true);\n\tawait port.setState(`${id}.openConflicts`, snapshot.openConflicts, true);\n}\n\n/**\n * Creates the channels of all employees and publishes their current figures.\n *\n * @param args - state port and data sources\n * @param args.port - state port\n * @param args.aggregation - aggregation service\n * @param args.users - user storage\n * @param args.sync - synchronisation service\n * @param args.now - instant of the snapshot\n * @returns the published snapshots\n */\nexport async function publishAllUserStates(args: {\n\tport: StatePort;\n\taggregation: AggregationService;\n\tusers: UsersRepository;\n\tsync: SyncService;\n\tnow?: number;\n}): Promise<UserSnapshot[]> {\n\tconst snapshots: UserSnapshot[] = [];\n\tfor (const user of args.users.list({ includeInactive: true })) {\n\t\tawait createUserChannel(args.port, user.id);\n\t\tconst snapshot = readUserSnapshot({\n\t\t\taggregation: args.aggregation,\n\t\t\tusers: args.users,\n\t\t\tsync: args.sync,\n\t\t\tuserId: user.id,\n\t\t\tnow: args.now,\n\t\t});\n\t\tif (!snapshot) {\n\t\t\tcontinue;\n\t\t}\n\t\tawait publishUserSnapshot(args.port, snapshot);\n\t\tsnapshots.push(snapshot);\n\t}\n\treturn snapshots;\n}\n\n/**\n * Creates the channel of one employee, including the shared parent channel `users`.\n *\n * The parent channel matters: the ioBroker object structure check (`E3009`) requires every state to have its\n * parents, so `users.<userId>.*` below a missing `users` produced 126 findings in the repository check\n * (PR #6697). `setObjectNotExists` keeps the call idempotent.\n *\n * @param port - state port\n * @param userId - database id of the employee\n * @returns the channel id\n */\nexport async function createUserChannel(port: StatePort, userId: number): Promise<string> {\n\tconst id = `users.${userId}`;\n\tawait ensureObject(port, \"users\", channelObject(STATE_NAMES.usersChannel));\n\tawait ensureObject(port, id, channelObject(STATE_NAMES.employeeChannel));\n\tawait ensureObject(port, `${id}.displayName`, stateObject(STATE_NAMES.displayName, \"string\", \"info.name\"));\n\t// the writable twin of `hasOpenEntry`: a script, a fingerprint reader or a dashboard writes it to say that\n\t// somebody arrived (`true`) or left (`false`) \u2014 see `presence.ts` for what the adapter does with it\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.present`,\n\t\tstateObject(STATE_NAMES.userPresent, \"boolean\", \"switch\", { write: true }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.hasOpenEntry`,\n\t\tstateObject(STATE_NAMES.hasOpenEntry, \"boolean\", \"indicator.working\"),\n\t);\n\tawait ensureObject(port, `${id}.lastPunch`, stateObject(STATE_NAMES.lastPunch, \"number\", \"value.time\"));\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.todayWorkedMinutes`,\n\t\tstateObject(STATE_NAMES.todayWorkedMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.todayBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.todayBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.monthWorkedMinutes`,\n\t\tstateObject(STATE_NAMES.monthWorkedMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.monthBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.monthBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\t`${id}.yearBalanceMinutes`,\n\t\tstateObject(STATE_NAMES.yearBalanceMinutes, \"number\", \"value\", { unit: \"min\" }),\n\t);\n\tawait ensureObject(port, `${id}.openConflicts`, stateObject(STATE_NAMES.openConflicts, \"number\", \"value\"));\n\treturn id;\n}\n\n/**\n * Creates the writable command states.\n *\n * @param port - state port\n */\nexport async function createCommandStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"commands\", channelObject(STATE_NAMES.commandsChannel));\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.punchUserId,\n\t\tstateObject(STATE_NAMES.punchUserId, \"number\", \"level\", {\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.punch,\n\t\tstateObject(STATE_NAMES.punch, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.quickPunch,\n\t\tstateObject(STATE_NAMES.quickPunch, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.closeMonth,\n\t\tstateObject(STATE_NAMES.closeMonth, \"string\", \"text\", {\n\t\t\twrite: true,\n\t\t}),\n\t);\n\tawait ensureObject(port, COMMAND_IDS.recalc, stateObject(STATE_NAMES.recalc, \"string\", \"text\", { write: true }));\n\tawait ensureObject(\n\t\tport,\n\t\tCOMMAND_IDS.backup,\n\t\tstateObject(STATE_NAMES.backupCommand, \"boolean\", \"button\", {\n\t\t\tread: false,\n\t\t\twrite: true,\n\t\t}),\n\t);\n}\n\n/**\n * Creates the informational states of the instance.\n *\n * @param port - state port\n */\nexport async function createInfoStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"info\", channelObject(STATE_NAMES.infoChannel));\n\tawait ensureObject(port, \"info.version\", stateObject(STATE_NAMES.version, \"string\", \"text\"));\n\tawait ensureObject(port, \"info.schemaVersion\", stateObject(STATE_NAMES.schemaVersion, \"string\", \"text\"));\n\tawait ensureObject(\n\t\tport,\n\t\t\"info.dbSizeBytes\",\n\t\tstateObject(STATE_NAMES.dbSizeBytes, \"number\", \"value\", { unit: \"bytes\" }),\n\t);\n\tawait ensureObject(port, \"info.lastError\", stateObject(STATE_NAMES.lastError, \"string\", \"text\"));\n\tawait ensureObject(port, \"info.lastBackup\", stateObject(STATE_NAMES.lastBackup, \"number\", \"value.time\"));\n}\n\n/**\n * Creates the states of the company.\n *\n * @param port - state port\n */\nexport async function createCompanyStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"company\", channelObject(STATE_NAMES.companyChannel));\n\tawait ensureObject(port, \"company.presentCount\", stateObject(STATE_NAMES.companyPresentCount, \"number\", \"value\"));\n\tawait ensureObject(port, \"company.present\", stateObject(STATE_NAMES.companyPresent, \"string\", \"text\"));\n\tawait ensureObject(port, \"company.openConflicts\", stateObject(STATE_NAMES.openConflicts, \"number\", \"value\"));\n\tawait ensureObject(port, \"company.lastPunch\", stateObject(STATE_NAMES.companyLastPunch, \"number\", \"value.time\"));\n}\n\n/**\n * Reads the company figures out of the figures of the employees.\n *\n * @param snapshots - figures of all employees\n * @returns the company figures\n */\nexport function readCompanySnapshot(snapshots: UserSnapshot[]): CompanySnapshot {\n\tconst present = snapshots.filter(snapshot => snapshot.hasOpenEntry);\n\treturn {\n\t\tpresentCount: present.length,\n\t\tpresent: present.map(snapshot => snapshot.displayName).join(\", \"),\n\t\topenConflicts: snapshots.reduce((sum, snapshot) => sum + snapshot.openConflicts, 0),\n\t\tlastPunchUtc: snapshots.reduce<number | null>(\n\t\t\t(newest, snapshot) =>\n\t\t\t\tsnapshot.lastPunchUtc !== null && (newest === null || snapshot.lastPunchUtc > newest)\n\t\t\t\t\t? snapshot.lastPunchUtc\n\t\t\t\t\t: newest,\n\t\t\tnull,\n\t\t),\n\t};\n}\n\n/**\n * Publishes the company figures.\n *\n * @param port - state port\n * @param snapshot - figures to publish\n */\nexport async function publishCompanySnapshot(port: StatePort, snapshot: CompanySnapshot): Promise<void> {\n\tawait port.setState(\"company.presentCount\", snapshot.presentCount, true);\n\tawait port.setState(\"company.present\", snapshot.present, true);\n\tawait port.setState(\"company.openConflicts\", snapshot.openConflicts, true);\n\tawait port.setState(\"company.lastPunch\", snapshot.lastPunchUtc ?? 0, true);\n}\n\n/**\n * Creates the event states.\n *\n * @param port - state port\n */\nexport async function createEventStates(port: StatePort): Promise<void> {\n\tawait ensureObject(port, \"events\", channelObject(STATE_NAMES.eventsChannel));\n\tawait ensureObject(port, \"events.lastAt\", stateObject(STATE_NAMES.lastAt, \"number\", \"value.time\"));\n\tawait ensureObject(port, \"events.lastType\", stateObject(STATE_NAMES.lastType, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastUser\", stateObject(STATE_NAMES.lastUser, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastDirection\", stateObject(STATE_NAMES.lastDirection, \"string\", \"text\"));\n\tawait ensureObject(port, \"events.lastSource\", stateObject(STATE_NAMES.lastSource, \"string\", \"text\"));\n}\n\n/**\n * Publishes the newest event.\n *\n * @param port - state port\n * @param snapshot - event to publish\n */\nexport async function publishEventSnapshot(port: StatePort, snapshot: EventSnapshot): Promise<void> {\n\tawait port.setState(\"events.lastAt\", snapshot.atUtc, true);\n\tawait port.setState(\"events.lastType\", snapshot.type, true);\n\tawait port.setState(\"events.lastUser\", snapshot.userName, true);\n\tawait port.setState(\"events.lastDirection\", snapshot.direction, true);\n\tawait port.setState(\"events.lastSource\", snapshot.source, true);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolveLocalDate"]
7
7
  }
package/io-package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "time-tracker",
4
- "version": "0.2.2",
4
+ "version": "0.2.3",
5
5
  "nogit": true,
6
6
  "news": {
7
+ "0.2.3": {
8
+ "en": "fix: a corrected object definition reaches existing installations now — `ensureObject` merges the fields the adapter owns (`name`, `type`, `role`, `read`, `write`, `unit`) instead of only the name, so `commands.punchUserId` receives the role `level` on the next start (checker `E1011`)",
9
+ "de": "fix: eine korrigierte Objektdefinition erreicht jetzt auch bestehende Installationen — `ensureObject` mischt die adapter-eigenen Felder (`name`, `type`, `role`, `read`, `write`, `unit`) statt nur den Namen zusammen, deshalb erhält `commands.punchUserId` beim nächsten Start die Rolle `level` (Checker `E1011`)",
10
+ "ru": "исправление: исправленное определение объекта теперь доходит и до существующих установок — `ensureObject` объединяет поля, которыми владеет адаптер (`name`, `type`, `role`, `read`, `write`, `unit`), а не только имя, поэтому `commands.punchUserId` получит роль `level` при следующем запуске (проверка `E1011`)",
11
+ "pt": "correção: uma definição de objeto corrigida chega agora também às instalações existentes — `ensureObject` junta os campos do adaptador (`name`, `type`, `role`, `read`, `write`, `unit`) em vez de apenas o nome, por isso `commands.punchUserId` recebe a função `level` no próximo arranque (verificação `E1011`)",
12
+ "nl": "fix: een gecorrigeerde objectdefinitie bereikt nu ook bestaande installaties — `ensureObject` voegt de velden van de adapter samen (`name`, `type`, `role`, `read`, `write`, `unit`) in plaats van alleen de naam, daarom krijgt `commands.punchUserId` bij de volgende start de rol `level` (controle `E1011`)",
13
+ "fr": "correctif : une définition d'objet corrigée parvient désormais aussi aux installations existantes — `ensureObject` fusionne les champs propres à l'adaptateur (`name`, `type`, `role`, `read`, `write`, `unit`) au lieu du seul nom, `commands.punchUserId` reçoit donc le rôle `level` au prochain démarrage (contrôle `E1011`)",
14
+ "it": "correzione: una definizione di oggetto corretta raggiunge ora anche le installazioni esistenti — `ensureObject` unisce i campi dell'adattatore (`name`, `type`, `role`, `read`, `write`, `unit`) invece del solo nome, quindi `commands.punchUserId` riceve il ruolo `level` al prossimo avvio (controllo `E1011`)",
15
+ "es": "corrección: una definición de objeto corregida llega ahora también a las instalaciones existentes — `ensureObject` combina los campos propios del adaptador (`name`, `type`, `role`, `read`, `write`, `unit`) en lugar de solo el nombre, por eso `commands.punchUserId` recibe el rol `level` en el próximo arranque (comprobación `E1011`)",
16
+ "pl": "poprawka: skorygowana definicja obiektu dociera teraz także do istniejących instalacji — `ensureObject` scala pola należące do adaptera (`name`, `type`, `role`, `read`, `write`, `unit`), a nie tylko nazwę, dlatego `commands.punchUserId` otrzyma rolę `level` przy następnym starcie (kontrola `E1011`)",
17
+ "uk": "виправлення: виправлене визначення об'єкта тепер доходить і до наявних установок — `ensureObject` об'єднує поля, якими володіє адаптер (`name`, `type`, `role`, `read`, `write`, `unit`), а не лише назву, тому `commands.punchUserId` отримає роль `level` під час наступного запуску (перевірка `E1011`)",
18
+ "zh-cn": "修复:修正后的对象定义现在也会到达已有安装 —— `ensureObject` 合并适配器拥有的字段(`name`、`type`、`role`、`read`、`write`、`unit`),而不仅是名称,因此 `commands.punchUserId` 会在下次启动时获得角色 `level`(检查 `E1011`)"
19
+ },
7
20
  "0.2.2": {
8
21
  "en": "the object structure check is satisfied: every object name carries all eleven languages (`E6001`) and the employee-id command uses the role `level` instead of `value` (`E1011`). The names live in one place and existing installations receive them on the next start",
9
22
  "de": "die Objektstruktur-Prüfung ist erfüllt: jeder Objektname trägt jetzt alle elf Sprachen (`E6001`) und der Mitarbeiter-Id-Befehl nutzt die Rolle `level` statt `value` (`E1011`). Die Namen liegen an einer Stelle, bestehende Installationen erhalten sie beim nächsten Start",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.time-tracker",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Time tracking (clock-in/out) for ioBroker with web app, badge terminal and reports",
5
5
  "author": {
6
6
  "name": "Alex",
package/www/sw.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sw.js","sources":["../../../../../../tmp/fa11d11c8a67d5cf3c5508c32b17fc8f/sw.js"],"sourcesContent":["import {clientsClaim as workbox_core_clientsClaim} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {registerRoute as workbox_routing_registerRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-routing/registerRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"person.png\",\n \"revision\": \"771d3283ece4739e203e02aac535855e\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"45c88f7faf80a0e4c5539908dc6b94f2\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"favicon.svg\",\n \"revision\": \"309e9efece69d60920618512ab9d57bf\"\n },\n {\n \"url\": \"assets/workbox-window.prod.es5-Bd17z0YL.js\",\n \"revision\": null\n },\n {\n \"url\": \"assets/index-QI3wrEvW.js\",\n \"revision\": null\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"manifest.webmanifest\",\n \"revision\": \"6f40f1ce0ce138d5af307fb0dbb1e6f9\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], denylist: [/^\\/api\\//],}));\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","denylist"],"mappings":"inBAoBAA,KAAKC,cACLC,EAAAA,eAMAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,MAEd,CACED,IAAO,2BACPC,SAAY,MAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,qCAEb,CAAA,GACHC,EAAAA,wBAA2CC,EAAAA,cAA8B,IAAIC,EAAAA,gBAAgCC,0BAA2C,cAAe,CACrKC,UAAW,CAAC,QAAUC,SAAU,CAAC"}
1
+ {"version":3,"file":"sw.js","sources":["../../../../../../tmp/39b73fa92f9c5a66a2b842640534a590/sw.js"],"sourcesContent":["import {clientsClaim as workbox_core_clientsClaim} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {registerRoute as workbox_routing_registerRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-routing/registerRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/home/runner/work/ioBroker.time-tracker/ioBroker.time-tracker/src-pwa/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\nself.skipWaiting();\nworkbox_core_clientsClaim();\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"person.png\",\n \"revision\": \"771d3283ece4739e203e02aac535855e\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"45c88f7faf80a0e4c5539908dc6b94f2\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"favicon.svg\",\n \"revision\": \"309e9efece69d60920618512ab9d57bf\"\n },\n {\n \"url\": \"assets/workbox-window.prod.es5-Bd17z0YL.js\",\n \"revision\": null\n },\n {\n \"url\": \"assets/index-QI3wrEvW.js\",\n \"revision\": null\n },\n {\n \"url\": \"icon-192.png\",\n \"revision\": \"940ef37caef09c52272ddab844b9ea9b\"\n },\n {\n \"url\": \"icon-512.png\",\n \"revision\": \"d8e3715779f4cd7e67e1f82f215a2f22\"\n },\n {\n \"url\": \"manifest.webmanifest\",\n \"revision\": \"6f40f1ce0ce138d5af307fb0dbb1e6f9\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();workbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"index.html\"), {\n allowlist: [/^\\/$/], denylist: [/^\\/api\\//],}));\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox_precaching_cleanupOutdatedCaches","workbox_routing_registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","allowlist","denylist"],"mappings":"inBAoBAA,KAAKC,cACLC,EAAAA,eAMAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,MAEd,CACED,IAAO,2BACPC,SAAY,MAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,eACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,qCAEb,CAAA,GACHC,EAAAA,wBAA2CC,EAAAA,cAA8B,IAAIC,EAAAA,gBAAgCC,0BAA2C,cAAe,CACrKC,UAAW,CAAC,QAAUC,SAAU,CAAC"}