diff --git a/package.json b/package.json index dfe24fa..68a9018 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "eslint": "^8.23.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", - "miniflare": "~2.8.1", + "miniflare": "~2.7.1", "prettier": "^2.7.1", "prettier-plugin-organize-imports": "^3.1.1", "typescript": "~4.7.4", diff --git a/src/lib/cache.ts b/src/lib/cache.ts deleted file mode 100644 index 1c398e3..0000000 --- a/src/lib/cache.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { array2hex } from "./shared"; - -export interface CacheOptions { - req: Request; - context: ExecutionContext; - cacheKey: string | Request; - maxAge?: number; - sMaxAge?: number; -} - -export async function withCache( - options: CacheOptions, - responseFactory: () => Promise -): Promise { - const { req, context, cacheKey, sMaxAge = 600, maxAge = 5 } = options; - - // Find the cache key in the cache - const cache = caches.default; - let response = await cache.match(cacheKey); - if (response) { - if ( - req.headers.has("if-none-match") && - req.headers.get("if-none-match") === response.headers.get("etag") - ) { - return new Response(null, { status: 304 }); - } - console.log("cache hit"); - return response; - } - - response = await responseFactory(); - - if (response.status === 200) { - const responseBody = await response.clone().text(); - const hash = array2hex( - new Uint8Array( - await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(responseBody) - ) - ) - ); - - // Cache the response for 10 minutes on the server, revalidate after 5 seconds on the client - response.headers.append( - "Cache-Control", - `public, s-maxage=${sMaxAge}, max-age=${maxAge}, stale-while-revalidate` - ); - response.headers.append("ETag", hash); - context.waitUntil(cache.put(cacheKey, response.clone())); - } - - return response; -} diff --git a/src/lib/fs/filesystem.ts b/src/lib/fs/filesystem.ts index d1eb1c3..f1a4f42 100644 --- a/src/lib/fs/filesystem.ts +++ b/src/lib/fs/filesystem.ts @@ -2,7 +2,7 @@ export interface FileSystem { writeFile( file: string, - data: string | ArrayBuffer | ArrayBufferView | Blob + data: string | ReadableStream | ArrayBuffer | ArrayBufferView | Blob ): Promise; readFile(file: string): Promise; // deleteFile(file: string): Promise; diff --git a/src/lib/shared.ts b/src/lib/shared.ts index 1a0f3ad..cf13d65 100644 --- a/src/lib/shared.ts +++ b/src/lib/shared.ts @@ -1,3 +1,5 @@ +import { error } from "itty-router-extras"; + export const hexKeyRegex4Digits = /^0x[a-f0-9]{4}$/; export const hexKeyRegex2Digits = /^0x[a-f0-9]{2}$/; export const firmwareVersionRegex = /^\d{1,3}\.\d{1,3}$/; @@ -47,13 +49,6 @@ export function padVersion(version: string): string { return version + ".0"; } -// expands object types recursively -export type ExpandRecursively = T extends object - ? T extends infer O - ? { [K in keyof O]: ExpandRecursively } - : never - : T; - export function array2hex(arr: Uint8Array): string { return [...arr].map((x) => x.toString(16).padStart(2, "0")).join(""); } @@ -66,3 +61,50 @@ export function hex2array(hex: string): Uint8Array { } return ret; } + +/** Constant-time string comparison */ +export function safeCompare(expected: string, actual: string): boolean { + const lenExpected = expected.length; + let result = 0; + + if (lenExpected !== actual.length) { + actual = expected; + result = 1; + } + + for (let i = 0; i < lenExpected; i++) { + result |= expected.charCodeAt(i) ^ actual.charCodeAt(i); + } + + return result === 0; +} + +// expands object types recursively +export type ExpandRecursively = T extends object + ? T extends infer O + ? { [K in keyof O]: ExpandRecursively } + : never + : T; + +export function clientError( + message: BodyInit | Record | undefined, + code: number = 400 +): Response { + return error(code, message); +} + +export function serverError( + message: BodyInit | Record, + code: number = 500 +): Response { + return error(code, message); +} + +export type RequestWithProps< + U extends Record[], + T extends Request = Request +> = U extends [infer First, ...infer Rest extends Record[]] + ? RequestWithProps + : T; + +export type ContentProps = { content: unknown }; diff --git a/src/lib/shared_cloudflare.ts b/src/lib/shared_cloudflare.ts deleted file mode 100644 index 16e13f5..0000000 --- a/src/lib/shared_cloudflare.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { error } from "itty-router-extras"; - -/** Constant-time string comparison */ -export function safeCompare(expected: string, actual: string): boolean { - const lenExpected = expected.length; - let result = 0; - - if (lenExpected !== actual.length) { - actual = expected; - result = 1; - } - - for (let i = 0; i < lenExpected; i++) { - result |= expected.charCodeAt(i) ^ actual.charCodeAt(i); - } - - return result === 0; -} - -export function clientError( - message: BodyInit | Record | undefined, - code: number = 400 -): Response { - return error(code, message); -} - -export function serverError( - message: BodyInit | Record, - code: number = 500 -): Response { - return error(code, message); -} - -export type RequestWithProps< - U extends Record[], - T extends Request = Request -> = U extends [infer First, ...infer Rest extends Record[]] - ? RequestWithProps - : T; - -export type ContentProps = { content: unknown }; diff --git a/src/routes/admin.ts b/src/routes/admin.ts index 44ba450..c748b4f 100644 --- a/src/routes/admin.ts +++ b/src/routes/admin.ts @@ -9,13 +9,13 @@ import { import type { RateLimiterProps } from "../durable_objects/RateLimiter"; import { encryptAPIKey } from "../lib/apiKeys"; import { createCachedR2FS, getFilesVersion } from "../lib/fs/cachedR2FS"; -import { hex2array } from "../lib/shared"; import { clientError, ContentProps, + hex2array, safeCompare, type RequestWithProps, -} from "../lib/shared_cloudflare"; +} from "../lib/shared"; import { uploadSchema } from "../lib/uploadSchema"; import type { CloudflareEnvironment } from "../worker"; diff --git a/src/routes/api.ts b/src/routes/api.ts index 42d7950..c8a1bdf 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -2,7 +2,6 @@ import { withDurables } from "itty-durable"; import { json, type ThrowableRouter } from "itty-router-extras"; import { APIv1_RequestSchema } from "../apiV1"; import type { RateLimiterProps } from "../durable_objects/RateLimiter"; -import { withCache } from "../lib/cache"; import { lookupConfig } from "../lib/config"; import { createCachedR2FS, getFilesVersion } from "../lib/fs/cachedR2FS"; import { @@ -10,7 +9,7 @@ import { ContentProps, serverError, type RequestWithProps, -} from "../lib/shared_cloudflare"; +} from "../lib/shared"; import { APIKeyProps, withAPIKey } from "../middleware/withAPIKey"; import type { CloudflareEnvironment } from "../worker"; @@ -52,8 +51,7 @@ export default function register(router: ThrowableRouter): void { "/api/v1/updates", async ( req: RequestWithProps<[ContentProps]>, - env: CloudflareEnvironment, - context: ExecutionContext + env: CloudflareEnvironment ) => { const result = await APIv1_RequestSchema.safeParseAsync( req.content @@ -73,42 +71,21 @@ export default function register(router: ThrowableRouter): void { return serverError("Filesystem empty"); } - // Figure out if this info is already cached - const cacheUrl = new URL( - `/${manufacturerId}:${productType}:${productId}:${firmwareVersion}?version=${version}`, - req.url + const config = await lookupConfig( + createCachedR2FS(env.CONFIG_FILES, env.R2_CACHE, version), + "/", + manufacturerId, + productType, + productId, + firmwareVersion ); - const cacheKey = cacheUrl.toString(); - return withCache( - { - req, - context, - cacheKey, - // Cache for 1 hour on the client - maxAge: 60 * 60, - // Cache for 1 day on the server. - // We use the file hash/revision as part of the cache key, - // so we can safely cache for a longer time. - sMaxAge: 60 * 60 * 24, - }, - async () => { - const config = await lookupConfig( - createCachedR2FS( - env.CONFIG_FILES, - env.R2_CACHE, - version - ), - "/", - manufacturerId, - productType, - productId, - firmwareVersion - ); + if (!config) { + // Config not found + return json([]); + } - return json(config?.upgrades ?? []); - } - ); + return json(config.upgrades); } ); } diff --git a/tsconfig.json b/tsconfig.json index 5b562ea..e67b4b1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,8 +16,7 @@ "moduleResolution": "Node", /* Support for Cloudflare */ - "types": ["@cloudflare/workers-types"], - "lib": ["ESNext"] + "types": ["@cloudflare/workers-types"] }, "include": ["src/**/*.ts", "test/**/*.ts"] } diff --git a/wrangler.toml b/wrangler.toml index 1fdce4c..e0574c3 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -1,6 +1,6 @@ name = "zwave-js-firmware-updates" usage_model = "bundled" -# workers_dev = true +workers_dev = true compatibility_date = "2022-08-28" # node_compat = true @@ -38,7 +38,7 @@ format = "modules" port = 8787 watch = true -cache_persist = true +cache = false env_path = ".env" kv_persist = true diff --git a/yarn.lock b/yarn.lock index 4144f58..0bbb8f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -124,18 +124,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/cache@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/cache@npm:2.8.1" - dependencies: - "@miniflare/core": 2.8.1 - "@miniflare/shared": 2.8.1 - http-cache-semantics: ^4.1.0 - undici: 5.9.1 - checksum: ff081113fe3c57204611d2848218c445b6741ab2ab90c2689c911ed87df50cf9f1b7e35ae2efeadf5819e4e835b1d6e9361513e1565075d0083486f9b97b299f - languageName: node - linkType: hard - "@miniflare/cli-parser@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/cli-parser@npm:2.7.1" @@ -146,16 +134,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/cli-parser@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/cli-parser@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - kleur: ^4.1.4 - checksum: 2e2d39dd3b7fb2b9351b68b514116e3b0747c74a00feafae1eb46b2726a77338028a12bb53c2a93fa4dfee8b8b236d7591f26f1291cb76aa58cf0f7aaabf581f - languageName: node - linkType: hard - "@miniflare/core@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/core@npm:2.7.1" @@ -173,24 +151,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/core@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/core@npm:2.8.1" - dependencies: - "@iarna/toml": ^2.2.5 - "@miniflare/queues": 2.8.1 - "@miniflare/shared": 2.8.1 - "@miniflare/watcher": 2.8.1 - busboy: ^1.6.0 - dotenv: ^10.0.0 - kleur: ^4.1.4 - set-cookie-parser: ^2.4.8 - undici: 5.9.1 - urlpattern-polyfill: ^4.0.3 - checksum: 6973aa35b43e24d56616013a5555d1ae1ee02c030f8a2a6fc506b42067cbbb767ac613ffd24458a364ffe2d02c77d3db71967fab242465669ffe5632c45bcffc - languageName: node - linkType: hard - "@miniflare/durable-objects@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/durable-objects@npm:2.7.1" @@ -203,18 +163,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/durable-objects@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/durable-objects@npm:2.8.1" - dependencies: - "@miniflare/core": 2.8.1 - "@miniflare/shared": 2.8.1 - "@miniflare/storage-memory": 2.8.1 - undici: 5.9.1 - checksum: 46f47adc92153af6bead91209a1a5124bdcc501203266bbf54e3f40d2191e4fb21d381564e10b67b348c036d8451cdaa7f907e2c1fdaf56ddae61d2fc5b468e9 - languageName: node - linkType: hard - "@miniflare/html-rewriter@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/html-rewriter@npm:2.7.1" @@ -227,18 +175,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/html-rewriter@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/html-rewriter@npm:2.8.1" - dependencies: - "@miniflare/core": 2.8.1 - "@miniflare/shared": 2.8.1 - html-rewriter-wasm: ^0.4.1 - undici: 5.9.1 - checksum: 785713e0f2e13da56f5089a65ab0d83d545cc94ef3c7e96ce789f84630854090bbfc5494903de4e43e2e5873b2287e83f998d242672a42efb540766671f2055b - languageName: node - linkType: hard - "@miniflare/http-server@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/http-server@npm:2.7.1" @@ -255,22 +191,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/http-server@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/http-server@npm:2.8.1" - dependencies: - "@miniflare/core": 2.8.1 - "@miniflare/shared": 2.8.1 - "@miniflare/web-sockets": 2.8.1 - kleur: ^4.1.4 - selfsigned: ^2.0.0 - undici: 5.9.1 - ws: ^8.2.2 - youch: ^2.2.2 - checksum: fb1fbd1aec7c0f499427aa3a1df87722a70da23d8a2e10d9922d475c4a249ea2d45844cbf9e2fe51ec9752392b3eca786832c2bde0f63491d84faacb755460a8 - languageName: node - linkType: hard - "@miniflare/kv@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/kv@npm:2.7.1" @@ -280,24 +200,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/kv@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/kv@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - checksum: daba9bd9aa0fc8057e6a9fe6caa8a3ca1790747d26adfae8307baf3214d51839582b2178cfb8f8470b18cca4819e1ffdfb34110318ba02cc3b4e28de25eecad3 - languageName: node - linkType: hard - -"@miniflare/queues@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/queues@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - checksum: 247e74b35474ab2b9dc753345ac58bfec34762c416a75d79039fa149848c24bb5582705f868c178db0f161fc80142aed98e17d71f4f825227f853f1ca1ebe301 - languageName: node - linkType: hard - "@miniflare/r2@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/r2@npm:2.7.1" @@ -308,16 +210,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/r2@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/r2@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - undici: 5.9.1 - checksum: ba527df7d8677f07363c5e6d8e067b0a545cf3afcbfefc0bfdebf8f7832fa6044e592314d4eff5f44aaa7e832f6d7c3cb3f3351f049a9977c91e1aa9a258d5c8 - languageName: node - linkType: hard - "@miniflare/runner-vm@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/runner-vm@npm:2.7.1" @@ -327,15 +219,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/runner-vm@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/runner-vm@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - checksum: d07e7bf0cb07f04bfe413b95b17f5c42d5e83ea750ae6364a0f87e3d5b3b6e95a4d28a5d693be549b2177466abc14966cfbb5f610cd541f7cdcfa4f4b99896b5 - languageName: node - linkType: hard - "@miniflare/scheduler@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/scheduler@npm:2.7.1" @@ -347,17 +230,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/scheduler@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/scheduler@npm:2.8.1" - dependencies: - "@miniflare/core": 2.8.1 - "@miniflare/shared": 2.8.1 - cron-schedule: ^3.0.4 - checksum: 14ab29b4f93a659075dc4a66ecfc69e7e318fa734362f753761a6b6c90813e21c38cad1769a3ae85ef88363fe28098b4ae41334126eb6ab3d7ae93026f92d392 - languageName: node - linkType: hard - "@miniflare/shared@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/shared@npm:2.7.1" @@ -368,16 +240,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/shared@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/shared@npm:2.8.1" - dependencies: - kleur: ^4.1.4 - picomatch: ^2.3.1 - checksum: 1e84953b3c736e40764d243cd1051f3312249d4e38599715bdb03ee5464c8b496c68d40fca58273f08b068a9223034e741636ffe93cf92a9385041adb1ee3eab - languageName: node - linkType: hard - "@miniflare/sites@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/sites@npm:2.7.1" @@ -389,17 +251,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/sites@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/sites@npm:2.8.1" - dependencies: - "@miniflare/kv": 2.8.1 - "@miniflare/shared": 2.8.1 - "@miniflare/storage-file": 2.8.1 - checksum: 39709825a9670fececa0fb116bbecb13206ba7f56acc51b5f629d0d9acfd7513b406b424b4fb180da904d3f850a933aa6f3bc3eb86d03a07c6a91aa971e71516 - languageName: node - linkType: hard - "@miniflare/storage-file@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/storage-file@npm:2.7.1" @@ -410,16 +261,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/storage-file@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/storage-file@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - "@miniflare/storage-memory": 2.8.1 - checksum: 9d6734a4bcbe41589ddbfdfe77d7657533a941ada44e0b23f517ed72c9320f727292302c270432e3715a1add889f98380fcb68fb1a2cc9760aaa6b63c97366f2 - languageName: node - linkType: hard - "@miniflare/storage-memory@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/storage-memory@npm:2.7.1" @@ -429,15 +270,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/storage-memory@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/storage-memory@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - checksum: c32a21857b769dd91b4af9a90df8d95b285a0312279912e6d8d142b6f41cbc38fa9326b4978145d40f5edc4dd94a8dcec4aed9aa518abe972683807519f5453d - languageName: node - linkType: hard - "@miniflare/watcher@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/watcher@npm:2.7.1" @@ -447,15 +279,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/watcher@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/watcher@npm:2.8.1" - dependencies: - "@miniflare/shared": 2.8.1 - checksum: d3419f3d8e935ca04695880a94a2ff0afbaee56cf626a3572b90724ba431bdd992226da026842590932e01ec4c8602b04e7e20e18a11c03abbee3a104a97d332 - languageName: node - linkType: hard - "@miniflare/web-sockets@npm:2.7.1": version: 2.7.1 resolution: "@miniflare/web-sockets@npm:2.7.1" @@ -468,18 +291,6 @@ __metadata: languageName: node linkType: hard -"@miniflare/web-sockets@npm:2.8.1": - version: 2.8.1 - resolution: "@miniflare/web-sockets@npm:2.8.1" - dependencies: - "@miniflare/core": 2.8.1 - "@miniflare/shared": 2.8.1 - undici: 5.9.1 - ws: ^8.2.2 - checksum: 9f589da035be0a41c61ef480ea293d04a414b9d03e06507b8ccefa66601f197a3e30a0d52e5be4fbbc520cb9519790c80b79db532e4aefffd65aa5c9b1576e70 - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -771,7 +582,7 @@ __metadata: itty-router-extras: ^0.4.2 json-logic-js: ^2.0.2 json5: ^2.2.1 - miniflare: ~2.8.1 + miniflare: ~2.7.1 path-browserify: ^1.0.1 prettier: ^2.7.1 prettier-plugin-organize-imports: ^3.1.1 @@ -3008,7 +2819,7 @@ __metadata: languageName: node linkType: hard -"miniflare@npm:^2.7.1": +"miniflare@npm:^2.7.1, miniflare@npm:~2.7.1": version: 2.7.1 resolution: "miniflare@npm:2.7.1" dependencies: @@ -3048,47 +2859,6 @@ __metadata: languageName: node linkType: hard -"miniflare@npm:~2.8.1": - version: 2.8.1 - resolution: "miniflare@npm:2.8.1" - dependencies: - "@miniflare/cache": 2.8.1 - "@miniflare/cli-parser": 2.8.1 - "@miniflare/core": 2.8.1 - "@miniflare/durable-objects": 2.8.1 - "@miniflare/html-rewriter": 2.8.1 - "@miniflare/http-server": 2.8.1 - "@miniflare/kv": 2.8.1 - "@miniflare/queues": 2.8.1 - "@miniflare/r2": 2.8.1 - "@miniflare/runner-vm": 2.8.1 - "@miniflare/scheduler": 2.8.1 - "@miniflare/shared": 2.8.1 - "@miniflare/sites": 2.8.1 - "@miniflare/storage-file": 2.8.1 - "@miniflare/storage-memory": 2.8.1 - "@miniflare/web-sockets": 2.8.1 - kleur: ^4.1.4 - semiver: ^1.1.0 - source-map-support: ^0.5.20 - undici: 5.9.1 - peerDependencies: - "@miniflare/storage-redis": 2.8.1 - cron-schedule: ^3.0.4 - ioredis: ^4.27.9 - peerDependenciesMeta: - "@miniflare/storage-redis": - optional: true - cron-schedule: - optional: true - ioredis: - optional: true - bin: - miniflare: bootstrap.js - checksum: f2147b41d68fa877d2f4de25700f092c5fc6e1ea32e26e03650097b9c894ab0149ada988e3c2d5bd85a6c7d6851255a5c87b81d6c8e30bf6371a5c8a6443f285 - languageName: node - linkType: hard - "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2"