├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── examples └── cached-image-proxy │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.json │ └── wrangler.toml ├── go.mod ├── go.sum ├── main.go ├── package.json ├── tsconfig.json ├── tsup.config.ts ├── worker ├── index.ts └── wasm_exec.js └── wrangler.toml /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .wrangler 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2024-present [syumai](https://github.com/syumai/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dev 2 | dev: 3 | npx wrangler dev 4 | 5 | .PHONY: build 6 | build: 7 | GOOS=js GOARCH=wasm go build -o ./build/app.wasm . 8 | 9 | .PHONY: deploy 10 | deploy: 11 | npx wrangler deploy 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-image-proxy-worker 2 | 3 | - A Cloudflare Worker application which serves patched version of [imageproxy](https://github.com/willnorris/imageproxy). 4 | - This proxy worker supports: 5 | - Resizing image 6 | - Cropping image 7 | - Convert image to jpeg / png 8 | - Caching is not supported. 9 | 10 | ## Usage 11 | 12 | - Clone this repo and run dev server. 13 | - Open URLs with supported [ParseOptions](https://pkg.go.dev/willnorris.com/go/imageproxy#ParseOptions). 14 | - e.g. http://localhost:8787/600,fit,jpg,q80/https://r2-image-viewer.syumai.com/akiba.jpg 15 | 16 | ## Requirements 17 | 18 | This project requires these tools to be installed globally. 19 | 20 | - Node.js 21 | - Go 1.22.3 22 | 23 | ## Development 24 | 25 | * At first, please run `npm i`. 26 | 27 | ### Commands 28 | 29 | ``` 30 | npm run dev # run dev server 31 | npm run deploy # deploy worker 32 | ``` 33 | 34 | ## Using Service Bindings 35 | 36 | - Type definitions from `npm i -D @syumai/go-image-proxy-worker` is available. 37 | - https://www.npmjs.com/package/@syumai/go-image-proxy-worker 38 | - See: [cached-image-proxy example](https://github.com/syumai/go-image-proxy-worker/tree/main/examples/cached-image-proxy). 39 | 40 | ## Author 41 | 42 | syumai 43 | 44 | ## License 45 | 46 | MIT 47 | -------------------------------------------------------------------------------- /examples/cached-image-proxy/README.md: -------------------------------------------------------------------------------- 1 | # cached-image-proxy 2 | 3 | - An example worker which caches response from go-image-proxy-worker. 4 | 5 | ## Example 6 | 7 | https://cached-image-proxy.syumai.com/?file=curry.jpg&size=500&format=jpeg 8 | 9 | ### Available options 10 | 11 | * file 12 | - [syumai.png](https://r2-image-viewer.syumai.com/syumai.png) (small PNG) 13 | - [curry.jpg](https://r2-image-viewer.syumai.com/curry.jpg) (middle-sized JPEG) 14 | 15 | * size 16 | - number 17 | * format 18 | - jpeg 19 | - png 20 | - tiff 21 | 22 | ## Development 23 | 24 | * At first, please run `npm i`. 25 | 26 | ### Commands 27 | 28 | ``` 29 | npm run dev # run dev server 30 | npm run deploy # deploy worker 31 | ``` 32 | 33 | ## Note 34 | 35 | * large size image exceeds memory limit and currently not works. 36 | - [akiba.jpg](https://r2-image-viewer.syumai.com/akiba.jpg) (large JPEG) 37 | 38 | -------------------------------------------------------------------------------- /examples/cached-image-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cached-image-proxy", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "wrangler dev", 7 | "deploy": "wrangler deploy" 8 | }, 9 | "dependencies": { 10 | "@hono/zod-validator": "^0.2.1", 11 | "hono": "^4.3.7", 12 | "zod": "^3.23.8" 13 | }, 14 | "devDependencies": { 15 | "@cloudflare/workers-types": "^4.20240512.0", 16 | "@syumai/go-image-proxy-worker": "^0.1.0", 17 | "typescript": "^5.4.5", 18 | "wrangler": "^3.57.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/cached-image-proxy/src/index.ts: -------------------------------------------------------------------------------- 1 | import { zValidator } from "@hono/zod-validator"; 2 | import { Hono } from "hono"; 3 | import { cache } from "hono/cache"; 4 | import { etag } from "hono/etag"; 5 | import { z } from "zod"; 6 | import type { ImageProxy, ParseOptions } from "@syumai/go-image-proxy-worker"; 7 | 8 | type Bindings = { 9 | IMAGE_PROXY: Service; 10 | }; 11 | 12 | const app = new Hono<{ Bindings: Bindings }>(); 13 | 14 | const schema = z.object({ 15 | file: z.union([ 16 | z.literal("syumai.png"), 17 | z.literal("curry.jpg"), 18 | ]), 19 | size: z.preprocess((x) => Number(x), z.number()).optional(), 20 | format: z 21 | .union([z.literal("jpeg"), z.literal("png"), z.literal("tiff")]) 22 | .optional(), 23 | }); 24 | 25 | app.get( 26 | "/", 27 | zValidator("query", schema, (result, c) => { 28 | if (!result.success) { 29 | return c.text("Bad Request", 400); 30 | } 31 | }), 32 | etag({ weak: true }), 33 | cache({ 34 | cacheName: "cached-image-proxy", 35 | cacheControl: "public, max-age=14400", 36 | }), 37 | async (c) => { 38 | const data = c.req.valid("query"); 39 | 40 | const opts: ParseOptions = {}; 41 | if (data.size !== undefined) { 42 | opts.size = data.size; 43 | opts.fit = true; 44 | } 45 | if (data.format !== undefined) { 46 | opts.format = data.format; 47 | } 48 | const proxyRes = await c.env.IMAGE_PROXY.proxy( 49 | new URL(c.req.url).origin, 50 | `https://r2-image-viewer.syumai.com/${data.file}`, 51 | opts 52 | ); 53 | 54 | c.header( 55 | "Content-Type", 56 | proxyRes.headers.get("Content-Type") ?? "application/octet-stream" 57 | ); 58 | return c.body(proxyRes.body); 59 | } 60 | ); 61 | 62 | export default app; 63 | -------------------------------------------------------------------------------- /examples/cached-image-proxy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "Node", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "noUnusedLocals": false, 10 | "noUnusedParameters": true, 11 | "allowJs": true, 12 | "types": ["node", "@cloudflare/workers-types"], 13 | "rootDir": "src", 14 | }, 15 | "include": ["src/**/*"], 16 | } 17 | -------------------------------------------------------------------------------- /examples/cached-image-proxy/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "cached-image-proxy" 2 | main = "src/index.ts" 3 | compatibility_date = "2024-05-12" 4 | 5 | [[services]] 6 | binding = "IMAGE_PROXY" 7 | service = "go-image-proxy-worker" 8 | entrypoint = "ImageProxy" 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/syumai/go-image-proxy-worker 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/syumai/imageproxy v0.11.0 7 | github.com/syumai/tinyutil v0.3.2 8 | github.com/syumai/workers v0.26.0 9 | ) 10 | 11 | require ( 12 | github.com/beorn7/perks v1.0.1 // indirect 13 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 14 | github.com/disintegration/imaging v1.6.2 // indirect 15 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 16 | github.com/muesli/smartcrop v0.3.0 // indirect 17 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect 18 | github.com/prometheus/client_golang v1.19.1 // indirect 19 | github.com/prometheus/client_model v0.6.1 // indirect 20 | github.com/prometheus/common v0.53.0 // indirect 21 | github.com/prometheus/procfs v0.15.0 // indirect 22 | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect 23 | golang.org/x/image v0.16.0 // indirect 24 | golang.org/x/sys v0.20.0 // indirect 25 | google.golang.org/protobuf v1.34.1 // indirect 26 | willnorris.com/go/gifresize v1.0.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= 8 | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= 9 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 10 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 11 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 12 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 13 | github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc= 14 | github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= 15 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 16 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 17 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 18 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 19 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 20 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 21 | github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= 22 | github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= 23 | github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek= 24 | github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk= 25 | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= 26 | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= 27 | github.com/syumai/imageproxy v0.11.0 h1:IkM8Ugmu8v1nRcz6YtV0Oo72/vRMoPmQ0gP2F+nCOCw= 28 | github.com/syumai/imageproxy v0.11.0/go.mod h1:PuOHxSNEzByPS060gjEdyFf5LXP/xnBnzpKZIvNOVSk= 29 | github.com/syumai/tinyutil v0.3.2 h1:W6yvMJBo64jK5Rz1Ytq/IF8ZqUUSMqi1R2zyEUTnnsg= 30 | github.com/syumai/tinyutil v0.3.2/go.mod h1:/owCyUs1bh6tKxH7K1Ze3M/zZtZ+vGrj3h82fgNHDFI= 31 | github.com/syumai/workers v0.26.0 h1:icxE2mimM6138HUyXoz8nBjq2bC7YHPTSB8qAaHi31k= 32 | github.com/syumai/workers v0.26.0/go.mod h1:ZnqmdiHNBrbxOLrZ/HJ5jzHy6af9cmiNZk10R9NrIEA= 33 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 34 | golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= 35 | golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= 36 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 37 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 38 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 39 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= 40 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 41 | willnorris.com/go/gifresize v1.0.0 h1:GKS68zjNhHMqkgNTv4iFAO/j/sNcVSOHQ7SqmDAIAmM= 42 | willnorris.com/go/gifresize v1.0.0/go.mod h1:eBM8gogBGCcaH603vxSpnfjwXIpq6nmnj/jauBDKtAk= 43 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/syumai/imageproxy" 7 | "github.com/syumai/tinyutil/httputil" 8 | "github.com/syumai/workers" 9 | ) 10 | 11 | func main() { 12 | mux := http.NewServeMux() 13 | mux.Handle("/favicon.ico", http.NotFoundHandler()) 14 | mux.Handle("/", imageproxy.NewProxy(httputil.DefaultClient.Transport, nil)) 15 | workers.Serve(mux) 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@syumai/go-image-proxy-worker", 3 | "version": "0.1.0", 4 | "description": "A Cloudflare Worker application which serves patched version of imageproxy", 5 | "main": "dist/index.js", 6 | "type": "module", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "scripts": { 12 | "build": "tsup", 13 | "build-wasm": "GOOS=js GOARCH=wasm go build -o ./dist/app.wasm .", 14 | "dev": "wrangler dev", 15 | "deploy": "wrangler deploy", 16 | "publint": "publint", 17 | "release": "npm run build && npm run publint && npm publish" 18 | }, 19 | "exports": { 20 | ".": [ 21 | { 22 | "types": "./dist/index.d.ts", 23 | "import": "./dist/index.js" 24 | }, 25 | { 26 | "types": "./dist/index.d.cts", 27 | "require": "./dist/index.cjs" 28 | } 29 | ] 30 | }, 31 | "license": "MIT", 32 | "publishConfig": { 33 | "registry": "https://registry.npmjs.org", 34 | "access": "public" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/syumai/go-image-proxy-worker.git" 39 | }, 40 | "devDependencies": { 41 | "@cloudflare/workers-types": "^4.20240512.0", 42 | "publint": "^0.2.7", 43 | "tsup": "^8.0.2", 44 | "typescript": "^5.4.5", 45 | "wrangler": "^3.57.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "moduleResolution": "Node", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": true, 13 | "allowJs": true, 14 | "types": ["node", "@cloudflare/workers-types"], 15 | "rootDir": "worker", 16 | "outDir": "dist", 17 | }, 18 | "include": ["worker/**/*"], 19 | } 20 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["worker/index.ts"], 5 | splitting: true, 6 | sourcemap: true, 7 | clean: true, 8 | format: ["cjs", "esm"], 9 | external: ["cloudflare:workers", "cloudflare:sockets", "./app.wasm"], 10 | dts: { 11 | banner: '/// ', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /worker/index.ts: -------------------------------------------------------------------------------- 1 | import "./wasm_exec.js"; 2 | import { connect } from "cloudflare:sockets"; 3 | // @ts-ignore 4 | import mod from "./app.wasm"; 5 | import { WorkerEntrypoint } from "cloudflare:workers"; 6 | 7 | declare const Go: any; 8 | 9 | Object.defineProperty(globalThis, "tryCatch", { 10 | value: (fn: () => void) => { 11 | try { 12 | return { 13 | result: fn(), 14 | }; 15 | } catch (e) { 16 | return { 17 | error: e, 18 | }; 19 | } 20 | }, 21 | writable: false, 22 | }); 23 | 24 | async function run(ctx: ReturnType) { 25 | const go = new Go(); 26 | 27 | let ready: (v?: unknown) => void; 28 | const readyPromise = new Promise((resolve) => { 29 | ready = resolve; 30 | }); 31 | const instance = new WebAssembly.Instance(mod, { 32 | ...go.importObject, 33 | workers: { 34 | ready: () => { 35 | ready(); 36 | }, 37 | }, 38 | }); 39 | go.run(instance, ctx); 40 | await readyPromise; 41 | } 42 | 43 | type Env = {}; 44 | 45 | // Bindings for syumai/workers 46 | type Binding = { 47 | handleRequest?: (req: Request) => Promise; 48 | }; 49 | 50 | function createRuntimeContext( 51 | env: Env, 52 | ctx: ExecutionContext, 53 | binding: Binding 54 | ) { 55 | return { 56 | env, 57 | ctx, 58 | connect, 59 | binding, 60 | }; 61 | } 62 | 63 | async function handleFetch(req: Request, env: Env, ctx: ExecutionContext) { 64 | const binding: Binding = {}; 65 | await run(createRuntimeContext(env, ctx, binding)); 66 | return binding.handleRequest!(req); 67 | } 68 | 69 | export default { 70 | fetch: handleFetch, 71 | }; 72 | 73 | type Size = number | `${number}`; 74 | 75 | /** 76 | * ParseOptions is a set of options for imageproxy based on: https://pkg.go.dev/willnorris.com/go/imageproxy#ParseOptions 77 | */ 78 | export type ParseOptions = { 79 | /** 80 | * X coordinate of top left rectangle corner (default: 0) 81 | */ 82 | cropX?: number; 83 | /** 84 | * Y coordinate of top left rectangle corner (default: 0) 85 | */ 86 | cropY?: number; 87 | /** 88 | * rectangle width (default: image width) 89 | */ 90 | cropWidth?: number; 91 | /** 92 | * rectangle height (default: image height) 93 | */ 94 | cropHeight?: number; 95 | /** 96 | * The "sc" option will perform a content-aware smart crop to fit the requested image width and height dimensions. 97 | * The smart crop option will override any requested rectangular crop. 98 | */ 99 | smartCrop?: boolean; 100 | /** 101 | * The size option takes the general form "{width}x{height}", where width and height are numbers. Integer values greater than 1 are interpreted as exact pixel values. 102 | * Floats between 0 and 1 are interpreted as percentages of the original image size. 103 | * If either value is omitted or set to 0, it will be automatically set to preserve the aspect ratio based on the other dimension. 104 | * If a single number is provided (with no "x" separator), it will be used for both height and width. 105 | * Depending on the size options specified, an image may be cropped to fit the requested size. 106 | * In all cases, the original aspect ratio of the image will be preserved; imageproxy will never stretch the original image. 107 | * When no explicit crop mode is specified, the following rules are followed: 108 | * - If both width and height values are specified, the image will be scaled to fill the space, cropping if necessary to fit the exact dimension. 109 | * - If only one of the width or height values is specified, the image will be resized to fit the specified dimension, scaling the other dimension as needed to maintain the aspect ratio. 110 | */ 111 | size?: `${Size}x${Size}` | `x${Size}` | `${Size}x` | Size; 112 | /** 113 | * If the "fit" option is specified together with a width and height value, the image will be resized to fit within a containing box of the specified size. As always, the original aspect ratio will be preserved. 114 | * Specifying the "fit" option with only one of either width or height does the same thing as if "fit" had not been specified. 115 | */ 116 | fit?: boolean; 117 | /** 118 | * The "r{degrees}" option will rotate the image the specified number of degrees, counter-clockwise. 119 | * Valid degrees values are 90, 180, and 270. 120 | */ 121 | rotationDegrees?: 90 | 180 | 270; 122 | /** 123 | * The "q{qualityPercentage}" option can be used to specify the quality of the output file (JPEG only). 124 | * If not specified, the default value of "95" is used. 125 | */ 126 | quality?: number; 127 | /** 128 | * The "jpeg", "png", and "tiff" options can be used to specify the desired image format of the proxied image. 129 | */ 130 | format?: "jpeg" | "png" | "tiff"; 131 | /** 132 | * The "s{signature}" option specifies an optional base64 encoded HMAC used to sign the remote URL in the request. The HMAC key used to verify signatures is provided to the imageproxy server on startup. 133 | */ 134 | signature?: string; 135 | }; 136 | 137 | function convertParseOptions(opts: ParseOptions): string { 138 | const parts: string[] = []; 139 | if (opts.cropX !== undefined) { 140 | parts.push(`cx${opts.cropX}`); 141 | } 142 | if (opts.cropY !== undefined) { 143 | parts.push(`cy${opts.cropY}`); 144 | } 145 | if (opts.cropWidth !== undefined) { 146 | parts.push(`cw${opts.cropWidth}`); 147 | } 148 | if (opts.cropHeight !== undefined) { 149 | parts.push(`ch${opts.cropHeight}`); 150 | } 151 | if (opts.smartCrop !== undefined) { 152 | parts.push("sc"); 153 | } 154 | if (opts.size !== undefined) { 155 | parts.push(String(opts.size)); 156 | } 157 | if (opts.fit !== undefined) { 158 | parts.push("fit"); 159 | } 160 | if (opts.rotationDegrees !== undefined) { 161 | parts.push(`r${opts.rotationDegrees}`); 162 | } 163 | if (opts.quality !== undefined) { 164 | parts.push(`q${opts.quality}`); 165 | } 166 | if (opts.format !== undefined) { 167 | parts.push(opts.format); 168 | } 169 | if (opts.signature !== undefined) { 170 | parts.push(`s${opts.signature}`); 171 | } 172 | return parts.join(","); 173 | } 174 | 175 | export class ImageProxy extends WorkerEntrypoint { 176 | async proxy(baseUrl: string, imageUrl: string | URL, opts: ParseOptions) { 177 | const url = new URL( 178 | `${convertParseOptions(opts) || "100"}/${imageUrl 179 | .toString() 180 | .replaceAll("//", "/")}`, 181 | baseUrl 182 | ); 183 | return await handleFetch( 184 | new Request(url, { 185 | method: "GET", 186 | headers: { 187 | host: url.host, 188 | }, 189 | }), 190 | this.env, 191 | this.ctx 192 | ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /worker/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | "use strict"; 6 | 7 | (() => { 8 | const enosys = () => { 9 | const err = new Error("not implemented"); 10 | err.code = "ENOSYS"; 11 | return err; 12 | }; 13 | 14 | if (!globalThis.fs) { 15 | let outputBuf = ""; 16 | globalThis.fs = { 17 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 18 | writeSync(fd, buf) { 19 | outputBuf += decoder.decode(buf); 20 | const nl = outputBuf.lastIndexOf("\n"); 21 | if (nl != -1) { 22 | console.log(outputBuf.substring(0, nl)); 23 | outputBuf = outputBuf.substring(nl + 1); 24 | } 25 | return buf.length; 26 | }, 27 | write(fd, buf, offset, length, position, callback) { 28 | if (offset !== 0 || length !== buf.length || position !== null) { 29 | callback(enosys()); 30 | return; 31 | } 32 | const n = this.writeSync(fd, buf); 33 | callback(null, n); 34 | }, 35 | chmod(path, mode, callback) { callback(enosys()); }, 36 | chown(path, uid, gid, callback) { callback(enosys()); }, 37 | close(fd, callback) { callback(enosys()); }, 38 | fchmod(fd, mode, callback) { callback(enosys()); }, 39 | fchown(fd, uid, gid, callback) { callback(enosys()); }, 40 | fstat(fd, callback) { callback(enosys()); }, 41 | fsync(fd, callback) { callback(null); }, 42 | ftruncate(fd, length, callback) { callback(enosys()); }, 43 | lchown(path, uid, gid, callback) { callback(enosys()); }, 44 | link(path, link, callback) { callback(enosys()); }, 45 | lstat(path, callback) { callback(enosys()); }, 46 | mkdir(path, perm, callback) { callback(enosys()); }, 47 | open(path, flags, mode, callback) { callback(enosys()); }, 48 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 49 | readdir(path, callback) { callback(enosys()); }, 50 | readlink(path, callback) { callback(enosys()); }, 51 | rename(from, to, callback) { callback(enosys()); }, 52 | rmdir(path, callback) { callback(enosys()); }, 53 | stat(path, callback) { callback(enosys()); }, 54 | symlink(path, link, callback) { callback(enosys()); }, 55 | truncate(path, length, callback) { callback(enosys()); }, 56 | unlink(path, callback) { callback(enosys()); }, 57 | utimes(path, atime, mtime, callback) { callback(enosys()); }, 58 | }; 59 | } 60 | 61 | if (!globalThis.process) { 62 | globalThis.process = { 63 | getuid() { return -1; }, 64 | getgid() { return -1; }, 65 | geteuid() { return -1; }, 66 | getegid() { return -1; }, 67 | getgroups() { throw enosys(); }, 68 | pid: -1, 69 | ppid: -1, 70 | umask() { throw enosys(); }, 71 | cwd() { throw enosys(); }, 72 | chdir() { throw enosys(); }, 73 | } 74 | } 75 | 76 | if (!globalThis.crypto) { 77 | throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); 78 | } 79 | 80 | if (!globalThis.performance) { 81 | throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); 82 | } 83 | 84 | if (!globalThis.TextEncoder) { 85 | throw new Error("globalThis.TextEncoder is not available, polyfill required"); 86 | } 87 | 88 | if (!globalThis.TextDecoder) { 89 | throw new Error("globalThis.TextDecoder is not available, polyfill required"); 90 | } 91 | 92 | const encoder = new TextEncoder("utf-8"); 93 | const decoder = new TextDecoder("utf-8"); 94 | 95 | globalThis.Go = class { 96 | constructor() { 97 | this.argv = ["js"]; 98 | this.env = {}; 99 | this.exit = (code) => { 100 | if (code !== 0) { 101 | console.warn("exit code:", code); 102 | } 103 | }; 104 | this._exitPromise = new Promise((resolve) => { 105 | this._resolveExitPromise = resolve; 106 | }); 107 | this._pendingEvent = null; 108 | this._scheduledTimeouts = new Map(); 109 | this._nextCallbackTimeoutID = 1; 110 | 111 | const setInt64 = (addr, v) => { 112 | this.mem.setUint32(addr + 0, v, true); 113 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); 114 | } 115 | 116 | const setInt32 = (addr, v) => { 117 | this.mem.setUint32(addr + 0, v, true); 118 | } 119 | 120 | const getInt64 = (addr) => { 121 | const low = this.mem.getUint32(addr + 0, true); 122 | const high = this.mem.getInt32(addr + 4, true); 123 | return low + high * 4294967296; 124 | } 125 | 126 | const loadValue = (addr) => { 127 | const f = this.mem.getFloat64(addr, true); 128 | if (f === 0) { 129 | return undefined; 130 | } 131 | if (!isNaN(f)) { 132 | return f; 133 | } 134 | 135 | const id = this.mem.getUint32(addr, true); 136 | return this._values[id]; 137 | } 138 | 139 | const storeValue = (addr, v) => { 140 | const nanHead = 0x7FF80000; 141 | 142 | if (typeof v === "number" && v !== 0) { 143 | if (isNaN(v)) { 144 | this.mem.setUint32(addr + 4, nanHead, true); 145 | this.mem.setUint32(addr, 0, true); 146 | return; 147 | } 148 | this.mem.setFloat64(addr, v, true); 149 | return; 150 | } 151 | 152 | if (v === undefined) { 153 | this.mem.setFloat64(addr, 0, true); 154 | return; 155 | } 156 | 157 | let id = this._ids.get(v); 158 | if (id === undefined) { 159 | id = this._idPool.pop(); 160 | if (id === undefined) { 161 | id = this._values.length; 162 | } 163 | this._values[id] = v; 164 | this._goRefCounts[id] = 0; 165 | this._ids.set(v, id); 166 | } 167 | this._goRefCounts[id]++; 168 | let typeFlag = 0; 169 | switch (typeof v) { 170 | case "object": 171 | if (v !== null) { 172 | typeFlag = 1; 173 | } 174 | break; 175 | case "string": 176 | typeFlag = 2; 177 | break; 178 | case "symbol": 179 | typeFlag = 3; 180 | break; 181 | case "function": 182 | typeFlag = 4; 183 | break; 184 | } 185 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 186 | this.mem.setUint32(addr, id, true); 187 | } 188 | 189 | const loadSlice = (addr) => { 190 | const array = getInt64(addr + 0); 191 | const len = getInt64(addr + 8); 192 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 193 | } 194 | 195 | const loadSliceOfValues = (addr) => { 196 | const array = getInt64(addr + 0); 197 | const len = getInt64(addr + 8); 198 | const a = new Array(len); 199 | for (let i = 0; i < len; i++) { 200 | a[i] = loadValue(array + i * 8); 201 | } 202 | return a; 203 | } 204 | 205 | const loadString = (addr) => { 206 | const saddr = getInt64(addr + 0); 207 | const len = getInt64(addr + 8); 208 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 209 | } 210 | 211 | const timeOrigin = Date.now() - performance.now(); 212 | this.importObject = { 213 | _gotest: { 214 | add: (a, b) => a + b, 215 | }, 216 | gojs: { 217 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 218 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 219 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 220 | // This changes the SP, thus we have to update the SP used by the imported function. 221 | 222 | // func wasmExit(code int32) 223 | "runtime.wasmExit": (sp) => { 224 | sp >>>= 0; 225 | const code = this.mem.getInt32(sp + 8, true); 226 | this.exited = true; 227 | delete this._inst; 228 | delete this._values; 229 | delete this._goRefCounts; 230 | delete this._ids; 231 | delete this._idPool; 232 | this.exit(code); 233 | }, 234 | 235 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 236 | "runtime.wasmWrite": (sp) => { 237 | sp >>>= 0; 238 | const fd = getInt64(sp + 8); 239 | const p = getInt64(sp + 16); 240 | const n = this.mem.getInt32(sp + 24, true); 241 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 242 | }, 243 | 244 | // func resetMemoryDataView() 245 | "runtime.resetMemoryDataView": (sp) => { 246 | sp >>>= 0; 247 | this.mem = new DataView(this._inst.exports.mem.buffer); 248 | }, 249 | 250 | // func nanotime1() int64 251 | "runtime.nanotime1": (sp) => { 252 | sp >>>= 0; 253 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 254 | }, 255 | 256 | // func walltime() (sec int64, nsec int32) 257 | "runtime.walltime": (sp) => { 258 | sp >>>= 0; 259 | const msec = (new Date).getTime(); 260 | setInt64(sp + 8, msec / 1000); 261 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 262 | }, 263 | 264 | // func scheduleTimeoutEvent(delay int64) int32 265 | "runtime.scheduleTimeoutEvent": (sp) => { 266 | sp >>>= 0; 267 | const id = this._nextCallbackTimeoutID; 268 | this._nextCallbackTimeoutID++; 269 | this._scheduledTimeouts.set(id, setTimeout( 270 | () => { 271 | this._resume(); 272 | while (this._scheduledTimeouts.has(id)) { 273 | // for some reason Go failed to register the timeout event, log and try again 274 | // (temporary workaround for https://github.com/golang/go/issues/28975) 275 | console.warn("scheduleTimeoutEvent: missed timeout event"); 276 | this._resume(); 277 | } 278 | }, 279 | getInt64(sp + 8), 280 | )); 281 | this.mem.setInt32(sp + 16, id, true); 282 | }, 283 | 284 | // func clearTimeoutEvent(id int32) 285 | "runtime.clearTimeoutEvent": (sp) => { 286 | sp >>>= 0; 287 | const id = this.mem.getInt32(sp + 8, true); 288 | clearTimeout(this._scheduledTimeouts.get(id)); 289 | this._scheduledTimeouts.delete(id); 290 | }, 291 | 292 | // func getRandomData(r []byte) 293 | "runtime.getRandomData": (sp) => { 294 | sp >>>= 0; 295 | crypto.getRandomValues(loadSlice(sp + 8)); 296 | }, 297 | 298 | // func finalizeRef(v ref) 299 | "syscall/js.finalizeRef": (sp) => { 300 | sp >>>= 0; 301 | const id = this.mem.getUint32(sp + 8, true); 302 | this._goRefCounts[id]--; 303 | if (this._goRefCounts[id] === 0) { 304 | const v = this._values[id]; 305 | this._values[id] = null; 306 | this._ids.delete(v); 307 | this._idPool.push(id); 308 | } 309 | }, 310 | 311 | // func stringVal(value string) ref 312 | "syscall/js.stringVal": (sp) => { 313 | sp >>>= 0; 314 | storeValue(sp + 24, loadString(sp + 8)); 315 | }, 316 | 317 | // func valueGet(v ref, p string) ref 318 | "syscall/js.valueGet": (sp) => { 319 | sp >>>= 0; 320 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 321 | sp = this._inst.exports.getsp() >>> 0; // see comment above 322 | storeValue(sp + 32, result); 323 | }, 324 | 325 | // func valueSet(v ref, p string, x ref) 326 | "syscall/js.valueSet": (sp) => { 327 | sp >>>= 0; 328 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 329 | }, 330 | 331 | // func valueDelete(v ref, p string) 332 | "syscall/js.valueDelete": (sp) => { 333 | sp >>>= 0; 334 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 335 | }, 336 | 337 | // func valueIndex(v ref, i int) ref 338 | "syscall/js.valueIndex": (sp) => { 339 | sp >>>= 0; 340 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 341 | }, 342 | 343 | // valueSetIndex(v ref, i int, x ref) 344 | "syscall/js.valueSetIndex": (sp) => { 345 | sp >>>= 0; 346 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 347 | }, 348 | 349 | // func valueCall(v ref, m string, args []ref) (ref, bool) 350 | "syscall/js.valueCall": (sp) => { 351 | sp >>>= 0; 352 | try { 353 | const v = loadValue(sp + 8); 354 | const m = Reflect.get(v, loadString(sp + 16)); 355 | const args = loadSliceOfValues(sp + 32); 356 | const result = Reflect.apply(m, v, args); 357 | sp = this._inst.exports.getsp() >>> 0; // see comment above 358 | storeValue(sp + 56, result); 359 | this.mem.setUint8(sp + 64, 1); 360 | } catch (err) { 361 | sp = this._inst.exports.getsp() >>> 0; // see comment above 362 | storeValue(sp + 56, err); 363 | this.mem.setUint8(sp + 64, 0); 364 | } 365 | }, 366 | 367 | // func valueInvoke(v ref, args []ref) (ref, bool) 368 | "syscall/js.valueInvoke": (sp) => { 369 | sp >>>= 0; 370 | try { 371 | const v = loadValue(sp + 8); 372 | const args = loadSliceOfValues(sp + 16); 373 | const result = Reflect.apply(v, undefined, args); 374 | sp = this._inst.exports.getsp() >>> 0; // see comment above 375 | storeValue(sp + 40, result); 376 | this.mem.setUint8(sp + 48, 1); 377 | } catch (err) { 378 | sp = this._inst.exports.getsp() >>> 0; // see comment above 379 | storeValue(sp + 40, err); 380 | this.mem.setUint8(sp + 48, 0); 381 | } 382 | }, 383 | 384 | // func valueNew(v ref, args []ref) (ref, bool) 385 | "syscall/js.valueNew": (sp) => { 386 | sp >>>= 0; 387 | try { 388 | const v = loadValue(sp + 8); 389 | const args = loadSliceOfValues(sp + 16); 390 | const result = Reflect.construct(v, args); 391 | sp = this._inst.exports.getsp() >>> 0; // see comment above 392 | storeValue(sp + 40, result); 393 | this.mem.setUint8(sp + 48, 1); 394 | } catch (err) { 395 | sp = this._inst.exports.getsp() >>> 0; // see comment above 396 | storeValue(sp + 40, err); 397 | this.mem.setUint8(sp + 48, 0); 398 | } 399 | }, 400 | 401 | // func valueLength(v ref) int 402 | "syscall/js.valueLength": (sp) => { 403 | sp >>>= 0; 404 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 405 | }, 406 | 407 | // valuePrepareString(v ref) (ref, int) 408 | "syscall/js.valuePrepareString": (sp) => { 409 | sp >>>= 0; 410 | const str = encoder.encode(String(loadValue(sp + 8))); 411 | storeValue(sp + 16, str); 412 | setInt64(sp + 24, str.length); 413 | }, 414 | 415 | // valueLoadString(v ref, b []byte) 416 | "syscall/js.valueLoadString": (sp) => { 417 | sp >>>= 0; 418 | const str = loadValue(sp + 8); 419 | loadSlice(sp + 16).set(str); 420 | }, 421 | 422 | // func valueInstanceOf(v ref, t ref) bool 423 | "syscall/js.valueInstanceOf": (sp) => { 424 | sp >>>= 0; 425 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); 426 | }, 427 | 428 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 429 | "syscall/js.copyBytesToGo": (sp) => { 430 | sp >>>= 0; 431 | const dst = loadSlice(sp + 8); 432 | const src = loadValue(sp + 32); 433 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 434 | this.mem.setUint8(sp + 48, 0); 435 | return; 436 | } 437 | const toCopy = src.subarray(0, dst.length); 438 | dst.set(toCopy); 439 | setInt64(sp + 40, toCopy.length); 440 | this.mem.setUint8(sp + 48, 1); 441 | }, 442 | 443 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 444 | "syscall/js.copyBytesToJS": (sp) => { 445 | sp >>>= 0; 446 | const dst = loadValue(sp + 8); 447 | const src = loadSlice(sp + 16); 448 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 449 | this.mem.setUint8(sp + 48, 0); 450 | return; 451 | } 452 | const toCopy = src.subarray(0, dst.length); 453 | dst.set(toCopy); 454 | setInt64(sp + 40, toCopy.length); 455 | this.mem.setUint8(sp + 48, 1); 456 | }, 457 | 458 | "debug": (value) => { 459 | console.log(value); 460 | }, 461 | } 462 | }; 463 | } 464 | 465 | async run(instance, context) { 466 | if (!(instance instanceof WebAssembly.Instance)) { 467 | throw new Error("Go.run: WebAssembly.Instance expected"); 468 | } 469 | this._inst = instance; 470 | this.mem = new DataView(this._inst.exports.mem.buffer); 471 | const globalProxy = new Proxy(globalThis, { 472 | get(target, prop) { 473 | if (prop === 'context') { 474 | return context; 475 | } 476 | return Reflect.get(...arguments); 477 | } 478 | }) 479 | this._values = [ // JS values that Go currently has references to, indexed by reference id 480 | NaN, 481 | 0, 482 | null, 483 | true, 484 | false, 485 | globalProxy, 486 | this, 487 | ]; 488 | this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id 489 | this._ids = new Map([ // mapping from JS values to reference ids 490 | [0, 1], 491 | [null, 2], 492 | [true, 3], 493 | [false, 4], 494 | [globalProxy, 5], 495 | [this, 6], 496 | ]); 497 | this._idPool = []; // unused ids that have been garbage collected 498 | this.exited = false; // whether the Go program has exited 499 | 500 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 501 | let offset = 4096; 502 | 503 | const strPtr = (str) => { 504 | const ptr = offset; 505 | const bytes = encoder.encode(str + "\0"); 506 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 507 | offset += bytes.length; 508 | if (offset % 8 !== 0) { 509 | offset += 8 - (offset % 8); 510 | } 511 | return ptr; 512 | }; 513 | 514 | const argc = this.argv.length; 515 | 516 | const argvPtrs = []; 517 | this.argv.forEach((arg) => { 518 | argvPtrs.push(strPtr(arg)); 519 | }); 520 | argvPtrs.push(0); 521 | 522 | const keys = Object.keys(this.env).sort(); 523 | keys.forEach((key) => { 524 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 525 | }); 526 | argvPtrs.push(0); 527 | 528 | const argv = offset; 529 | argvPtrs.forEach((ptr) => { 530 | this.mem.setUint32(offset, ptr, true); 531 | this.mem.setUint32(offset + 4, 0, true); 532 | offset += 8; 533 | }); 534 | 535 | // The linker guarantees global data starts from at least wasmMinDataAddr. 536 | // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. 537 | const wasmMinDataAddr = 4096 + 8192; 538 | if (offset >= wasmMinDataAddr) { 539 | throw new Error("total length of command line and environment variables exceeds limit"); 540 | } 541 | 542 | this._inst.exports.run(argc, argv); 543 | if (this.exited) { 544 | this._resolveExitPromise(); 545 | } 546 | await this._exitPromise; 547 | } 548 | 549 | _resume() { 550 | if (this.exited) { 551 | throw new Error("Go program has already exited"); 552 | } 553 | this._inst.exports.resume(); 554 | if (this.exited) { 555 | this._resolveExitPromise(); 556 | } 557 | } 558 | 559 | _makeFuncWrapper(id) { 560 | const go = this; 561 | return function () { 562 | const event = { id: id, this: this, args: arguments }; 563 | go._pendingEvent = event; 564 | go._resume(); 565 | return event.result; 566 | }; 567 | } 568 | } 569 | })(); -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "go-image-proxy-worker" 2 | main = "./dist/index.js" 3 | compatibility_date = "2024-05-12" 4 | 5 | [build] 6 | command = "npm run build && npm run build-wasm" 7 | --------------------------------------------------------------------------------