zwave-js 15.0.0-beta.2 → 15.0.0

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.
Files changed (32) hide show
  1. package/build/cjs/lib/_version.d.ts +1 -1
  2. package/build/cjs/lib/_version.js +1 -1
  3. package/build/cjs/lib/_version.js.map +1 -1
  4. package/build/cjs/lib/controller/FirmwareUpdateService.js +1 -0
  5. package/build/cjs/lib/controller/FirmwareUpdateService.js.map +2 -2
  6. package/build/cjs/lib/node/Node.d.ts +8 -0
  7. package/build/cjs/lib/node/Node.js +14 -0
  8. package/build/cjs/lib/node/Node.js.map +2 -2
  9. package/build/cjs/lib/zniffer/MPDU.js +4 -2
  10. package/build/cjs/lib/zniffer/MPDU.js.map +2 -2
  11. package/build/cjs/lib/zniffer/Zniffer.d.ts +20 -1
  12. package/build/cjs/lib/zniffer/Zniffer.js +68 -1
  13. package/build/cjs/lib/zniffer/Zniffer.js.map +2 -2
  14. package/build/esm/lib/_version.d.ts +1 -1
  15. package/build/esm/lib/_version.d.ts.map +1 -1
  16. package/build/esm/lib/_version.js +1 -1
  17. package/build/esm/lib/_version.js.map +1 -1
  18. package/build/esm/lib/controller/FirmwareUpdateService.d.ts.map +1 -1
  19. package/build/esm/lib/controller/FirmwareUpdateService.js +1 -0
  20. package/build/esm/lib/controller/FirmwareUpdateService.js.map +1 -1
  21. package/build/esm/lib/node/Node.d.ts +8 -0
  22. package/build/esm/lib/node/Node.d.ts.map +1 -1
  23. package/build/esm/lib/node/Node.js +14 -0
  24. package/build/esm/lib/node/Node.js.map +1 -1
  25. package/build/esm/lib/zniffer/MPDU.d.ts.map +1 -1
  26. package/build/esm/lib/zniffer/MPDU.js +4 -2
  27. package/build/esm/lib/zniffer/MPDU.js.map +1 -1
  28. package/build/esm/lib/zniffer/Zniffer.d.ts +20 -1
  29. package/build/esm/lib/zniffer/Zniffer.d.ts.map +1 -1
  30. package/build/esm/lib/zniffer/Zniffer.js +79 -3
  31. package/build/esm/lib/zniffer/Zniffer.js.map +1 -1
  32. package/package.json +11 -11
@@ -1,3 +1,3 @@
1
- export declare const PACKAGE_VERSION = "15.0.0-beta.2";
1
+ export declare const PACKAGE_VERSION = "15.0.0";
2
2
  export declare const PACKAGE_NAME = "zwave-js";
3
3
  //# sourceMappingURL=_version.d.ts.map
@@ -22,7 +22,7 @@ __export(version_exports, {
22
22
  PACKAGE_VERSION: () => PACKAGE_VERSION
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const PACKAGE_VERSION = "15.0.0-beta.2";
25
+ const PACKAGE_VERSION = "15.0.0";
26
26
  const PACKAGE_NAME = "zwave-js";
27
27
  // Annotate the CommonJS export names for ESM import in node:
28
28
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/_version.ts"],
4
- "sourcesContent": ["// This file is auto-generated by the codegen maintenance script\nexport const PACKAGE_VERSION = \"15.0.0-beta.2\";\nexport const PACKAGE_NAME = \"zwave-js\";\n"],
4
+ "sourcesContent": ["// This file is auto-generated by the codegen maintenance script\nexport const PACKAGE_VERSION = \"15.0.0\";\nexport const PACKAGE_NAME = \"zwave-js\";\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;;;;;;AACO,MAAM,kBAAkB;AACxB,MAAM,eAAe;",
6
6
  "names": []
7
7
  }
@@ -113,6 +113,7 @@ function rfRegionToUpdateServiceRegion(rfRegion) {
113
113
  switch (rfRegion) {
114
114
  case import_core.RFRegion["Default (EU)"]:
115
115
  case import_core.RFRegion.Europe:
116
+ case import_core.RFRegion["Europe (Long Range)"]:
116
117
  return "europe";
117
118
  case import_core.RFRegion.USA:
118
119
  case import_core.RFRegion["USA (Long Range)"]:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/controller/FirmwareUpdateService.ts"],
4
- "sourcesContent": ["import {\n\ttype Firmware,\n\tRFRegion,\n\tZWaveError,\n\tZWaveErrorCodes,\n\tdigest,\n\textractFirmware,\n\tguessFirmwareFileFormat,\n} from \"@zwave-js/core\";\nimport { Bytes, type Timer, formatId, getenv } from \"@zwave-js/shared\";\nimport { setTimer } from \"@zwave-js/shared\";\nimport type { Options as KyOptions } from \"ky\";\nimport type PQueue from \"p-queue\";\nimport type {\n\tFirmwareUpdateDeviceID,\n\tFirmwareUpdateFileInfo,\n\tFirmwareUpdateInfo,\n\tFirmwareUpdateServiceResponse,\n} from \"./_Types.js\";\n\nfunction serviceURL(): string {\n\treturn getenv(\"ZWAVEJS_FW_SERVICE_URL\") || \"https://firmware.zwave-js.io\";\n}\nconst DOWNLOAD_TIMEOUT = 60000;\n// const MAX_FIRMWARE_SIZE = 10 * 1024 * 1024; // 10MB should be enough for any conceivable Z-Wave chip\n\nconst MAX_CACHE_SECONDS = 60 * 60 * 24; // Cache for a day at max\nconst CLEAN_CACHE_INTERVAL_MS = 60 * 60 * 1000; // Remove stale entries from the cache every hour\n\nconst requestCache = new Map<string, CachedRequest<unknown>>();\ninterface CachedRequest<T> {\n\tresponse: T;\n\tstaleDate: number;\n}\n\n// Queue requests to the firmware update service. Only allow few parallel requests so we can make some use of the cache.\nlet requestQueue: PQueue | undefined;\n\nlet cleanCacheTimeout: Timer | undefined;\nfunction cleanCache() {\n\tcleanCacheTimeout?.clear();\n\tcleanCacheTimeout = undefined;\n\n\tconst now = Date.now();\n\tfor (const [key, cached] of requestCache) {\n\t\tif (cached.staleDate < now) {\n\t\t\trequestCache.delete(key);\n\t\t}\n\t}\n\n\tif (requestCache.size > 0) {\n\t\tcleanCacheTimeout = setTimer(\n\t\t\tcleanCache,\n\t\t\tCLEAN_CACHE_INTERVAL_MS,\n\t\t).unref();\n\t}\n}\n\nasync function cachedRequest<T>(url: string, config: KyOptions): Promise<T> {\n\tconst hash = Bytes.view(\n\t\tawait digest(\n\t\t\t\"sha-256\",\n\t\t\tBytes.from(JSON.stringify(config.json)),\n\t\t),\n\t).toString(\"hex\");\n\tconst cacheKey = `${config.method}:${url}:${hash}`;\n\n\t// Return cached requests if they are not stale yet\n\tif (requestCache.has(cacheKey)) {\n\t\tconst cached = requestCache.get(cacheKey)!;\n\t\tif (cached.staleDate > Date.now()) {\n\t\t\treturn cached.response as T;\n\t\t}\n\t}\n\n\tconst { default: ky } = await import(\"ky\");\n\tconst response = await ky(url, config);\n\tconst responseJson = await response.json<T>();\n\n\t// Check if we can cache the response\n\tif (response.status === 200 && response.headers.has(\"cache-control\")) {\n\t\tconst cacheControl = response.headers.get(\"cache-control\")!;\n\t\tconst age = response.headers.get(\"age\");\n\t\tconst date = response.headers.get(\"date\");\n\n\t\tlet maxAge: number | undefined;\n\t\tconst maxAgeMatch = cacheControl.match(/max-age=(\\d+)/);\n\t\tif (maxAgeMatch) {\n\t\t\tmaxAge = Math.max(0, parseInt(maxAgeMatch[1], 10));\n\t\t}\n\n\t\tif (maxAge) {\n\t\t\tlet currentAge: number;\n\t\t\tif (age) {\n\t\t\t\tcurrentAge = parseInt(age, 10);\n\t\t\t} else if (date) {\n\t\t\t\tcurrentAge = (Date.now() - Date.parse(date))\n\t\t\t\t\t/ 1000;\n\t\t\t} else {\n\t\t\t\tcurrentAge = 0;\n\t\t\t}\n\t\t\tcurrentAge = Math.max(0, currentAge);\n\n\t\t\tif (maxAge > currentAge) {\n\t\t\t\trequestCache.set(cacheKey, {\n\t\t\t\t\tresponse: responseJson,\n\t\t\t\t\tstaleDate: Date.now()\n\t\t\t\t\t\t+ Math.min(MAX_CACHE_SECONDS, maxAge - currentAge)\n\t\t\t\t\t\t\t* 1000,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Regularly clean the cache\n\tif (!cleanCacheTimeout) {\n\t\tcleanCacheTimeout = setTimer(\n\t\t\tcleanCache,\n\t\t\tCLEAN_CACHE_INTERVAL_MS,\n\t\t).unref();\n\t}\n\n\treturn responseJson;\n}\n\nfunction hasExtension(pathname: string): boolean {\n\treturn /\\.[a-z0-9_]+$/i.test(pathname);\n}\n\nexport interface GetAvailableFirmwareUpdateOptions {\n\tuserAgent: string;\n\tapiKey?: string;\n\tincludePrereleases?: boolean;\n}\n\n/** Converts the RF region to a format the update service understands */\nfunction rfRegionToUpdateServiceRegion(\n\trfRegion?: RFRegion,\n): string | undefined {\n\tswitch (rfRegion) {\n\t\tcase RFRegion[\"Default (EU)\"]:\n\t\tcase RFRegion.Europe:\n\t\t\treturn \"europe\";\n\t\tcase RFRegion.USA:\n\t\tcase RFRegion[\"USA (Long Range)\"]:\n\t\t\treturn \"usa\";\n\t\tcase RFRegion[\"Australia/New Zealand\"]:\n\t\t\treturn \"australia/new zealand\";\n\t\tcase RFRegion[\"Hong Kong\"]:\n\t\t\treturn \"hong kong\";\n\t\tcase RFRegion.India:\n\t\t\treturn \"india\";\n\t\tcase RFRegion.Israel:\n\t\t\treturn \"israel\";\n\t\tcase RFRegion.Russia:\n\t\t\treturn \"russia\";\n\t\tcase RFRegion.China:\n\t\t\treturn \"china\";\n\t\tcase RFRegion.Japan:\n\t\t\treturn \"japan\";\n\t\tcase RFRegion.Korea:\n\t\t\treturn \"korea\";\n\t}\n}\n\n/**\n * Retrieves the available firmware updates for the node with the given fingerprint.\n * Returns the service response or `undefined` in case of an error.\n */\nexport async function getAvailableFirmwareUpdates(\n\tdeviceId: FirmwareUpdateDeviceID,\n\toptions: GetAvailableFirmwareUpdateOptions,\n): Promise<FirmwareUpdateInfo[]> {\n\tconst headers = new Headers({\n\t\t\"User-Agent\": options.userAgent,\n\t\t\"Content-Type\": \"application/json\",\n\t});\n\tif (options.apiKey) {\n\t\theaders.set(\"X-API-Key\", options.apiKey);\n\t}\n\n\tconst body: Record<string, string> = {\n\t\tmanufacturerId: formatId(deviceId.manufacturerId),\n\t\tproductType: formatId(deviceId.productType),\n\t\tproductId: formatId(deviceId.productId),\n\t\tfirmwareVersion: deviceId.firmwareVersion,\n\t};\n\tconst rfRegion = rfRegionToUpdateServiceRegion(deviceId.rfRegion);\n\tif (rfRegion) {\n\t\tbody.region = rfRegion;\n\t}\n\n\t// Prereleases and/or RF region-specific updates are only available in v3\n\tconst apiVersion = options.includePrereleases || !!rfRegion ? \"v3\" : \"v1\";\n\n\tconst url = `${serviceURL()}/api/${apiVersion}/updates`;\n\tconst config: KyOptions = {\n\t\tmethod: \"POST\",\n\t\tjson: body,\n\t\t// Consider re-enabling this instead of using cachedGot()\n\t\t// At the moment, the built-in caching has some issues though, so we stick\n\t\t// with our own implementation\n\t\t// cache: requestCache,\n\t\t// cacheOptions: {\n\t\t// \tshared: false,\n\t\t// },\n\t\theaders,\n\t};\n\n\tif (!requestQueue) {\n\t\t// I just love ESM\n\t\tconst PQueue = (await import(\"p-queue\")).default;\n\t\trequestQueue = new PQueue({ concurrency: 2 });\n\t}\n\t// Weird types...\n\tconst result = (\n\t\tawait requestQueue.add(() => cachedRequest(url, config))\n\t) as FirmwareUpdateServiceResponse[];\n\n\t// Remember the device ID in the response, so we can use it later\n\t// to ensure the update is for the correct device\n\treturn result.map((update) => ({\n\t\tdevice: deviceId,\n\t\t...update,\n\t\tchannel: update.channel ?? \"stable\",\n\t}));\n}\n\nexport async function downloadFirmwareUpdate(\n\tfile: FirmwareUpdateFileInfo,\n): Promise<Firmware> {\n\tconst [hashAlgorithm, expectedHash] = file.integrity.split(\":\", 2);\n\n\tif (hashAlgorithm !== \"sha256\") {\n\t\tthrow new ZWaveError(\n\t\t\t`Unsupported hash algorithm ${hashAlgorithm} for integrity check`,\n\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t);\n\t}\n\t// TODO: Make request abort-able (requires AbortController, Node 14.17+ / Node 16)\n\n\t// Download the firmware file\n\tconst { default: ky } = await import(\"ky\");\n\tconst downloadResponse = await ky.get(file.url, {\n\t\ttimeout: DOWNLOAD_TIMEOUT,\n\t\t// TODO: figure out how to do maxContentLength: MAX_FIRMWARE_SIZE,\n\t});\n\n\tconst rawData = new Uint8Array(await downloadResponse.arrayBuffer());\n\n\tconst requestedPathname = new URL(file.url).pathname;\n\t// The response may be redirected, so the filename information may be different\n\t// from the requested URL\n\tlet actualPathname: string | undefined;\n\ttry {\n\t\tactualPathname = new URL(downloadResponse.url).pathname;\n\t} catch {\n\t\t// ignore\n\t}\n\n\t// Infer the file type from the content-disposition header or the filename\n\tlet filename: string;\n\tconst contentDisposition = downloadResponse.headers.get(\n\t\t\"content-disposition\",\n\t);\n\tif (\n\t\tcontentDisposition?.startsWith(\n\t\t\t\"attachment; filename=\",\n\t\t)\n\t) {\n\t\tfilename = contentDisposition\n\t\t\t.split(\"filename=\")[1]\n\t\t\t.replace(/^\"/, \"\")\n\t\t\t.replace(/[\";]$/, \"\");\n\t} else if (actualPathname && hasExtension(actualPathname)) {\n\t\tfilename = actualPathname;\n\t} else {\n\t\tfilename = requestedPathname;\n\t}\n\n\t// Extract the raw data\n\tconst format = guessFirmwareFileFormat(filename, rawData);\n\tconst firmware = await extractFirmware(rawData, format);\n\n\t// Ensure the hash matches\n\tconst actualHash = Bytes.view(\n\t\tawait digest(\"sha-256\", firmware.data),\n\t).toString(\"hex\");\n\n\tif (actualHash !== expectedHash) {\n\t\tthrow new ZWaveError(\n\t\t\t`Integrity check failed. Expected hash ${expectedHash}, got ${actualHash}`,\n\t\t\tZWaveErrorCodes.FWUpdateService_IntegrityCheckFailed,\n\t\t);\n\t}\n\n\treturn {\n\t\tdata: firmware.data,\n\t\t// Don't trust the guessed firmware target, use the one from the provided info\n\t\tfirmwareTarget: file.target,\n\t};\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n\ttype Firmware,\n\tRFRegion,\n\tZWaveError,\n\tZWaveErrorCodes,\n\tdigest,\n\textractFirmware,\n\tguessFirmwareFileFormat,\n} from \"@zwave-js/core\";\nimport { Bytes, type Timer, formatId, getenv } from \"@zwave-js/shared\";\nimport { setTimer } from \"@zwave-js/shared\";\nimport type { Options as KyOptions } from \"ky\";\nimport type PQueue from \"p-queue\";\nimport type {\n\tFirmwareUpdateDeviceID,\n\tFirmwareUpdateFileInfo,\n\tFirmwareUpdateInfo,\n\tFirmwareUpdateServiceResponse,\n} from \"./_Types.js\";\n\nfunction serviceURL(): string {\n\treturn getenv(\"ZWAVEJS_FW_SERVICE_URL\") || \"https://firmware.zwave-js.io\";\n}\nconst DOWNLOAD_TIMEOUT = 60000;\n// const MAX_FIRMWARE_SIZE = 10 * 1024 * 1024; // 10MB should be enough for any conceivable Z-Wave chip\n\nconst MAX_CACHE_SECONDS = 60 * 60 * 24; // Cache for a day at max\nconst CLEAN_CACHE_INTERVAL_MS = 60 * 60 * 1000; // Remove stale entries from the cache every hour\n\nconst requestCache = new Map<string, CachedRequest<unknown>>();\ninterface CachedRequest<T> {\n\tresponse: T;\n\tstaleDate: number;\n}\n\n// Queue requests to the firmware update service. Only allow few parallel requests so we can make some use of the cache.\nlet requestQueue: PQueue | undefined;\n\nlet cleanCacheTimeout: Timer | undefined;\nfunction cleanCache() {\n\tcleanCacheTimeout?.clear();\n\tcleanCacheTimeout = undefined;\n\n\tconst now = Date.now();\n\tfor (const [key, cached] of requestCache) {\n\t\tif (cached.staleDate < now) {\n\t\t\trequestCache.delete(key);\n\t\t}\n\t}\n\n\tif (requestCache.size > 0) {\n\t\tcleanCacheTimeout = setTimer(\n\t\t\tcleanCache,\n\t\t\tCLEAN_CACHE_INTERVAL_MS,\n\t\t).unref();\n\t}\n}\n\nasync function cachedRequest<T>(url: string, config: KyOptions): Promise<T> {\n\tconst hash = Bytes.view(\n\t\tawait digest(\n\t\t\t\"sha-256\",\n\t\t\tBytes.from(JSON.stringify(config.json)),\n\t\t),\n\t).toString(\"hex\");\n\tconst cacheKey = `${config.method}:${url}:${hash}`;\n\n\t// Return cached requests if they are not stale yet\n\tif (requestCache.has(cacheKey)) {\n\t\tconst cached = requestCache.get(cacheKey)!;\n\t\tif (cached.staleDate > Date.now()) {\n\t\t\treturn cached.response as T;\n\t\t}\n\t}\n\n\tconst { default: ky } = await import(\"ky\");\n\tconst response = await ky(url, config);\n\tconst responseJson = await response.json<T>();\n\n\t// Check if we can cache the response\n\tif (response.status === 200 && response.headers.has(\"cache-control\")) {\n\t\tconst cacheControl = response.headers.get(\"cache-control\")!;\n\t\tconst age = response.headers.get(\"age\");\n\t\tconst date = response.headers.get(\"date\");\n\n\t\tlet maxAge: number | undefined;\n\t\tconst maxAgeMatch = cacheControl.match(/max-age=(\\d+)/);\n\t\tif (maxAgeMatch) {\n\t\t\tmaxAge = Math.max(0, parseInt(maxAgeMatch[1], 10));\n\t\t}\n\n\t\tif (maxAge) {\n\t\t\tlet currentAge: number;\n\t\t\tif (age) {\n\t\t\t\tcurrentAge = parseInt(age, 10);\n\t\t\t} else if (date) {\n\t\t\t\tcurrentAge = (Date.now() - Date.parse(date))\n\t\t\t\t\t/ 1000;\n\t\t\t} else {\n\t\t\t\tcurrentAge = 0;\n\t\t\t}\n\t\t\tcurrentAge = Math.max(0, currentAge);\n\n\t\t\tif (maxAge > currentAge) {\n\t\t\t\trequestCache.set(cacheKey, {\n\t\t\t\t\tresponse: responseJson,\n\t\t\t\t\tstaleDate: Date.now()\n\t\t\t\t\t\t+ Math.min(MAX_CACHE_SECONDS, maxAge - currentAge)\n\t\t\t\t\t\t\t* 1000,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Regularly clean the cache\n\tif (!cleanCacheTimeout) {\n\t\tcleanCacheTimeout = setTimer(\n\t\t\tcleanCache,\n\t\t\tCLEAN_CACHE_INTERVAL_MS,\n\t\t).unref();\n\t}\n\n\treturn responseJson;\n}\n\nfunction hasExtension(pathname: string): boolean {\n\treturn /\\.[a-z0-9_]+$/i.test(pathname);\n}\n\nexport interface GetAvailableFirmwareUpdateOptions {\n\tuserAgent: string;\n\tapiKey?: string;\n\tincludePrereleases?: boolean;\n}\n\n/** Converts the RF region to a format the update service understands */\nfunction rfRegionToUpdateServiceRegion(\n\trfRegion?: RFRegion,\n): string | undefined {\n\tswitch (rfRegion) {\n\t\tcase RFRegion[\"Default (EU)\"]:\n\t\tcase RFRegion.Europe:\n\t\tcase RFRegion[\"Europe (Long Range)\"]:\n\t\t\treturn \"europe\";\n\t\tcase RFRegion.USA:\n\t\tcase RFRegion[\"USA (Long Range)\"]:\n\t\t\treturn \"usa\";\n\t\tcase RFRegion[\"Australia/New Zealand\"]:\n\t\t\treturn \"australia/new zealand\";\n\t\tcase RFRegion[\"Hong Kong\"]:\n\t\t\treturn \"hong kong\";\n\t\tcase RFRegion.India:\n\t\t\treturn \"india\";\n\t\tcase RFRegion.Israel:\n\t\t\treturn \"israel\";\n\t\tcase RFRegion.Russia:\n\t\t\treturn \"russia\";\n\t\tcase RFRegion.China:\n\t\t\treturn \"china\";\n\t\tcase RFRegion.Japan:\n\t\t\treturn \"japan\";\n\t\tcase RFRegion.Korea:\n\t\t\treturn \"korea\";\n\t}\n}\n\n/**\n * Retrieves the available firmware updates for the node with the given fingerprint.\n * Returns the service response or `undefined` in case of an error.\n */\nexport async function getAvailableFirmwareUpdates(\n\tdeviceId: FirmwareUpdateDeviceID,\n\toptions: GetAvailableFirmwareUpdateOptions,\n): Promise<FirmwareUpdateInfo[]> {\n\tconst headers = new Headers({\n\t\t\"User-Agent\": options.userAgent,\n\t\t\"Content-Type\": \"application/json\",\n\t});\n\tif (options.apiKey) {\n\t\theaders.set(\"X-API-Key\", options.apiKey);\n\t}\n\n\tconst body: Record<string, string> = {\n\t\tmanufacturerId: formatId(deviceId.manufacturerId),\n\t\tproductType: formatId(deviceId.productType),\n\t\tproductId: formatId(deviceId.productId),\n\t\tfirmwareVersion: deviceId.firmwareVersion,\n\t};\n\tconst rfRegion = rfRegionToUpdateServiceRegion(deviceId.rfRegion);\n\tif (rfRegion) {\n\t\tbody.region = rfRegion;\n\t}\n\n\t// Prereleases and/or RF region-specific updates are only available in v3\n\tconst apiVersion = options.includePrereleases || !!rfRegion ? \"v3\" : \"v1\";\n\n\tconst url = `${serviceURL()}/api/${apiVersion}/updates`;\n\tconst config: KyOptions = {\n\t\tmethod: \"POST\",\n\t\tjson: body,\n\t\t// Consider re-enabling this instead of using cachedGot()\n\t\t// At the moment, the built-in caching has some issues though, so we stick\n\t\t// with our own implementation\n\t\t// cache: requestCache,\n\t\t// cacheOptions: {\n\t\t// \tshared: false,\n\t\t// },\n\t\theaders,\n\t};\n\n\tif (!requestQueue) {\n\t\t// I just love ESM\n\t\tconst PQueue = (await import(\"p-queue\")).default;\n\t\trequestQueue = new PQueue({ concurrency: 2 });\n\t}\n\t// Weird types...\n\tconst result = (\n\t\tawait requestQueue.add(() => cachedRequest(url, config))\n\t) as FirmwareUpdateServiceResponse[];\n\n\t// Remember the device ID in the response, so we can use it later\n\t// to ensure the update is for the correct device\n\treturn result.map((update) => ({\n\t\tdevice: deviceId,\n\t\t...update,\n\t\tchannel: update.channel ?? \"stable\",\n\t}));\n}\n\nexport async function downloadFirmwareUpdate(\n\tfile: FirmwareUpdateFileInfo,\n): Promise<Firmware> {\n\tconst [hashAlgorithm, expectedHash] = file.integrity.split(\":\", 2);\n\n\tif (hashAlgorithm !== \"sha256\") {\n\t\tthrow new ZWaveError(\n\t\t\t`Unsupported hash algorithm ${hashAlgorithm} for integrity check`,\n\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t);\n\t}\n\t// TODO: Make request abort-able (requires AbortController, Node 14.17+ / Node 16)\n\n\t// Download the firmware file\n\tconst { default: ky } = await import(\"ky\");\n\tconst downloadResponse = await ky.get(file.url, {\n\t\ttimeout: DOWNLOAD_TIMEOUT,\n\t\t// TODO: figure out how to do maxContentLength: MAX_FIRMWARE_SIZE,\n\t});\n\n\tconst rawData = new Uint8Array(await downloadResponse.arrayBuffer());\n\n\tconst requestedPathname = new URL(file.url).pathname;\n\t// The response may be redirected, so the filename information may be different\n\t// from the requested URL\n\tlet actualPathname: string | undefined;\n\ttry {\n\t\tactualPathname = new URL(downloadResponse.url).pathname;\n\t} catch {\n\t\t// ignore\n\t}\n\n\t// Infer the file type from the content-disposition header or the filename\n\tlet filename: string;\n\tconst contentDisposition = downloadResponse.headers.get(\n\t\t\"content-disposition\",\n\t);\n\tif (\n\t\tcontentDisposition?.startsWith(\n\t\t\t\"attachment; filename=\",\n\t\t)\n\t) {\n\t\tfilename = contentDisposition\n\t\t\t.split(\"filename=\")[1]\n\t\t\t.replace(/^\"/, \"\")\n\t\t\t.replace(/[\";]$/, \"\");\n\t} else if (actualPathname && hasExtension(actualPathname)) {\n\t\tfilename = actualPathname;\n\t} else {\n\t\tfilename = requestedPathname;\n\t}\n\n\t// Extract the raw data\n\tconst format = guessFirmwareFileFormat(filename, rawData);\n\tconst firmware = await extractFirmware(rawData, format);\n\n\t// Ensure the hash matches\n\tconst actualHash = Bytes.view(\n\t\tawait digest(\"sha-256\", firmware.data),\n\t).toString(\"hex\");\n\n\tif (actualHash !== expectedHash) {\n\t\tthrow new ZWaveError(\n\t\t\t`Integrity check failed. Expected hash ${expectedHash}, got ${actualHash}`,\n\t\t\tZWaveErrorCodes.FWUpdateService_IntegrityCheckFailed,\n\t\t);\n\t}\n\n\treturn {\n\t\tdata: firmware.data,\n\t\t// Don't trust the guessed firmware target, use the one from the provided info\n\t\tfirmwareTarget: file.target,\n\t};\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_shared"]
7
7
  }
@@ -62,6 +62,14 @@ export declare class ZWaveNode extends ZWaveNodeMixins implements QuerySecurityC
62
62
  * Contains additional information about this node, loaded from a config file
63
63
  */
64
64
  get deviceConfig(): DeviceConfig | undefined;
65
+ /**
66
+ * Returns the manufacturer/brand name defined in the device configuration,
67
+ * or looks it up from the manufacturer database if no config is available
68
+ */
69
+ get manufacturer(): string | undefined;
70
+ /**
71
+ * Returns the device label defined in the device configuration.
72
+ */
65
73
  get label(): string | undefined;
66
74
  get deviceDatabaseUrl(): MaybeNotKnown<string>;
67
75
  /** The last time a message was received from this node */
@@ -271,6 +271,20 @@ let ZWaveNode = (() => {
271
271
  get deviceConfig() {
272
272
  return this._deviceConfig;
273
273
  }
274
+ /**
275
+ * Returns the manufacturer/brand name defined in the device configuration,
276
+ * or looks it up from the manufacturer database if no config is available
277
+ */
278
+ get manufacturer() {
279
+ if (this._deviceConfig)
280
+ return this._deviceConfig.manufacturer;
281
+ if (this.manufacturerId != void 0) {
282
+ return this.driver.lookupManufacturer(this.manufacturerId);
283
+ }
284
+ }
285
+ /**
286
+ * Returns the device label defined in the device configuration.
287
+ */
274
288
  get label() {
275
289
  return this._deviceConfig?.label;
276
290
  }