diff --git a/cloudflare/env.go b/cloudflare/env.go index 3d74986..75c09e6 100644 --- a/cloudflare/env.go +++ b/cloudflare/env.go @@ -1,10 +1,12 @@ package cloudflare -import "github.com/syumai/workers/internal/jsutil" +import ( + "context" +) // Getenv gets a value of an environment variable. // - https://developers.cloudflare.com/workers/platform/environment-variables/ -// - Technically, this function is just an alias for js.Global().Get(env_name).String(). -func Getenv(name string) string { - return jsutil.Global.Get(name).String() +// - This function panics when a runtime context is not found. +func Getenv(ctx context.Context, name string) string { + return getRuntimeContextEnv(ctx).Get(name).String() } diff --git a/cloudflare/kv.go b/cloudflare/kv.go index f5d0387..13da052 100644 --- a/cloudflare/kv.go +++ b/cloudflare/kv.go @@ -1,6 +1,7 @@ package cloudflare import ( + "context" "fmt" "io" "syscall/js" @@ -17,9 +18,10 @@ type KVNamespace struct { // NewKVNamespace returns KVNamespace for given variable name. // - variable name must be defined in wrangler.toml as kv_namespace's binding. -// - if the given variable name doesn't exist on Global object, returns error. -func NewKVNamespace(varName string) (*KVNamespace, error) { - inst := js.Global().Get(varName) +// - if the given variable name doesn't exist on runtime context, returns error. +// - This function panics when a runtime context is not found. +func NewKVNamespace(ctx context.Context, varName string) (*KVNamespace, error) { + inst := getRuntimeContextEnv(ctx).Get(varName) if inst.IsUndefined() { return nil, fmt.Errorf("%s is undefined", varName) } diff --git a/cloudflare/r2bucket.go b/cloudflare/r2bucket.go index 2fbc899..3697d04 100644 --- a/cloudflare/r2bucket.go +++ b/cloudflare/r2bucket.go @@ -1,6 +1,7 @@ package cloudflare import ( + "context" "fmt" "io" "syscall/js" @@ -18,9 +19,10 @@ type R2Bucket struct { // NewR2Bucket returns R2Bucket for given variable name. // - variable name must be defined in wrangler.toml. // - see example: https://github.com/syumai/workers/tree/main/examples/r2-image-viewer -// - if the given variable name doesn't exist on Global object, returns error. -func NewR2Bucket(varName string) (*R2Bucket, error) { - inst := js.Global().Get(varName) +// - if the given variable name doesn't exist on runtime context, returns error. +// - This function panics when a runtime context is not found. +func NewR2Bucket(ctx context.Context, varName string) (*R2Bucket, error) { + inst := getRuntimeContextEnv(ctx).Get(varName) if inst.IsUndefined() { return nil, fmt.Errorf("%s is undefined", varName) } diff --git a/cloudflare/runtimecontext.go b/cloudflare/runtimecontext.go new file mode 100644 index 0000000..bc3fd2d --- /dev/null +++ b/cloudflare/runtimecontext.go @@ -0,0 +1,35 @@ +package cloudflare + +import ( + "context" + "syscall/js" + + "github.com/syumai/workers/internal/runtimecontext" +) + +/** + * The type definition of RuntimeContext for Cloudflare Worker expects: + * ```ts + * type RuntimeContext { + * env: Env; + * ctx: ExecutionContext; + * } + * ``` + * This type is based on the type definition of ExportedHandlerFetchHandler. + * - see: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#LL564 + */ + +// getRuntimeContextEnv gets object which holds environment variables bound to Cloudflare worker. +// - see: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#L566 +func getRuntimeContextEnv(ctx context.Context) js.Value { + runtimeCtxValue := runtimecontext.MustExtract(ctx) + return runtimeCtxValue.Get("env") +} + +// getExecutionContext gets ExecutionContext object from context. +// - see: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#L567 +// - see also: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#L554 +func getExecutionContext(ctx context.Context) js.Value { + runtimeCtxValue := runtimecontext.MustExtract(ctx) + return runtimeCtxValue.Get("ctx") +} diff --git a/examples/basic-auth-proxy/worker.mjs b/examples/basic-auth-proxy/worker.mjs index 06ac27d..649ccf0 100644 --- a/examples/basic-auth-proxy/worker.mjs +++ b/examples/basic-auth-proxy/worker.mjs @@ -4,22 +4,19 @@ import mod from "./dist/app.wasm"; const go = new Go(); +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { go.run(instance); return instance; }); -const readyPromise = new Promise((resolve) => { - globalThis.ready = resolve; -}); - -async function processRequest(event) { - const req = event.request; - await load; - await readyPromise; - return handleRequest(req); +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } } - -addEventListener("fetch", (event) => { - event.respondWith(processRequest(event)); -}) diff --git a/examples/env/main.go b/examples/env/main.go index bd8aa79..cf1e10d 100644 --- a/examples/env/main.go +++ b/examples/env/main.go @@ -10,7 +10,7 @@ import ( func main() { handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - fmt.Fprintf(w, "MY_ENV: %s", cloudflare.Getenv("MY_ENV")) + fmt.Fprintf(w, "MY_ENV: %s", cloudflare.Getenv(req.Context(), "MY_ENV")) }) workers.Serve(handler) } diff --git a/examples/env/worker.mjs b/examples/env/worker.mjs index 06ac27d..649ccf0 100644 --- a/examples/env/worker.mjs +++ b/examples/env/worker.mjs @@ -4,22 +4,19 @@ import mod from "./dist/app.wasm"; const go = new Go(); +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { go.run(instance); return instance; }); -const readyPromise = new Promise((resolve) => { - globalThis.ready = resolve; -}); - -async function processRequest(event) { - const req = event.request; - await load; - await readyPromise; - return handleRequest(req); +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } } - -addEventListener("fetch", (event) => { - event.respondWith(processRequest(event)); -}) diff --git a/examples/hello/worker.mjs b/examples/hello/worker.mjs index 06ac27d..649ccf0 100644 --- a/examples/hello/worker.mjs +++ b/examples/hello/worker.mjs @@ -4,22 +4,19 @@ import mod from "./dist/app.wasm"; const go = new Go(); +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { go.run(instance); return instance; }); -const readyPromise = new Promise((resolve) => { - globalThis.ready = resolve; -}); - -async function processRequest(event) { - const req = event.request; - await load; - await readyPromise; - return handleRequest(req); +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } } - -addEventListener("fetch", (event) => { - event.respondWith(processRequest(event)); -}) diff --git a/examples/kv-counter/main.go b/examples/kv-counter/main.go index b9a0768..b3af357 100644 --- a/examples/kv-counter/main.go +++ b/examples/kv-counter/main.go @@ -7,9 +7,8 @@ import ( "os" "strconv" - "github.com/syumai/workers/cloudflare" - "github.com/syumai/workers" + "github.com/syumai/workers/cloudflare" ) // counterNamespace is a bounded KV namespace for storing counter. @@ -25,35 +24,25 @@ func handleErr(w http.ResponseWriter, msg string, err error) { } func main() { - // initialize KV namespace instance - kv, err := cloudflare.NewKVNamespace(counterNamespace) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to init KV: %v", err) - os.Exit(1) - } - http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { if req.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) return } + // initialize KV namespace instance + kv, err := cloudflare.NewKVNamespace(req.Context(), counterNamespace) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to init KV: %v", err) + os.Exit(1) + } + countStr, err := kv.GetString(countKey, nil) if err != nil { handleErr(w, "failed to get current count\n", err) return } - /* - countReader, err := kv.GetReader(countKey, nil) - if err != nil { - handleErr(w, "failed to get current count\n", err) - return - } - b, _ := io.ReadAll(countReader) - countStr := string(b) - */ - // ignore err and treat count value as 0 count, _ := strconv.Atoi(countStr) @@ -65,28 +54,7 @@ func main() { return } - /* - err = kv.PutReader(countKey, strings.NewReader(nextCountStr), nil) - if err != nil { - handleErr(w, "failed to put next count\n", err) - return - } - */ - w.Header().Set("Content-Type", "text/plain") - - /* - // List returns only `count` as the keys in this namespace. - v, err := kv.List(nil) - if err != nil { - handleErr(w, "failed to list\n", err) - return - } - for i, key := range v.Keys { - fmt.Fprintf(w, "%d: %s\n", i, key.Name) - } - */ - w.Write([]byte(nextCountStr)) }) diff --git a/examples/kv-counter/worker.mjs b/examples/kv-counter/worker.mjs index 06ac27d..649ccf0 100644 --- a/examples/kv-counter/worker.mjs +++ b/examples/kv-counter/worker.mjs @@ -4,22 +4,19 @@ import mod from "./dist/app.wasm"; const go = new Go(); +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { go.run(instance); return instance; }); -const readyPromise = new Promise((resolve) => { - globalThis.ready = resolve; -}); - -async function processRequest(event) { - const req = event.request; - await load; - await readyPromise; - return handleRequest(req); +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } } - -addEventListener("fetch", (event) => { - event.respondWith(processRequest(event)); -}) diff --git a/examples/r2-image-server/main.go b/examples/r2-image-server/main.go index 518dc92..9d4167c 100644 --- a/examples/r2-image-server/main.go +++ b/examples/r2-image-server/main.go @@ -1,11 +1,11 @@ package main import ( + "context" "fmt" "io" "log" "net/http" - "os" "strings" "github.com/syumai/workers" @@ -22,20 +22,19 @@ func handleErr(w http.ResponseWriter, msg string, err error) { w.Write([]byte(msg)) } -type server struct { - bucket *cloudflare.R2Bucket -} +type server struct{} -func newServer() (*server, error) { - bucket, err := cloudflare.NewR2Bucket(bucketName) - if err != nil { - return nil, err - } - return &server{bucket: bucket}, nil +func (s *server) bucket(ctx context.Context) (*cloudflare.R2Bucket, error) { + return cloudflare.NewR2Bucket(ctx, bucketName) } func (s *server) post(w http.ResponseWriter, req *http.Request, key string) { - objects, err := s.bucket.List() + bucket, err := s.bucket(req.Context()) + if err != nil { + handleErr(w, "failed to initialize R2Bucket\n", err) + return + } + objects, err := bucket.List() if err != nil { handleErr(w, "failed to list R2Objects\n", err) return @@ -47,7 +46,7 @@ func (s *server) post(w http.ResponseWriter, req *http.Request, key string) { return } } - _, err = s.bucket.Put(key, req.Body, &cloudflare.R2PutOptions{ + _, err = bucket.Put(key, req.Body, &cloudflare.R2PutOptions{ HTTPMetadata: cloudflare.R2HTTPMetadata{ ContentType: req.Header.Get("Content-Type"), }, @@ -64,7 +63,12 @@ func (s *server) post(w http.ResponseWriter, req *http.Request, key string) { func (s *server) get(w http.ResponseWriter, req *http.Request, key string) { // get image object from R2 - imgObj, err := s.bucket.Get(key) + bucket, err := s.bucket(req.Context()) + if err != nil { + handleErr(w, "failed to initialize R2Bucket\n", err) + return + } + imgObj, err := bucket.Get(key) if err != nil { handleErr(w, "failed to get R2Object\n", err) return @@ -86,7 +90,12 @@ func (s *server) get(w http.ResponseWriter, req *http.Request, key string) { func (s *server) delete(w http.ResponseWriter, req *http.Request, key string) { // delete image object from R2 - if err := s.bucket.Delete(key); err != nil { + bucket, err := s.bucket(req.Context()) + if err != nil { + handleErr(w, "failed to initialize R2Bucket\n", err) + return + } + if err := bucket.Delete(key); err != nil { handleErr(w, "failed to delete R2Object\n", err) return } @@ -114,10 +123,5 @@ func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) { } func main() { - s, err := newServer() - if err != nil { - fmt.Fprintf(os.Stderr, "failed to start server: %v", err) - os.Exit(1) - } - workers.Serve(s) + workers.Serve(&server{}) } diff --git a/examples/r2-image-server/worker.mjs b/examples/r2-image-server/worker.mjs index 06ac27d..649ccf0 100644 --- a/examples/r2-image-server/worker.mjs +++ b/examples/r2-image-server/worker.mjs @@ -4,22 +4,19 @@ import mod from "./dist/app.wasm"; const go = new Go(); +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { go.run(instance); return instance; }); -const readyPromise = new Promise((resolve) => { - globalThis.ready = resolve; -}); - -async function processRequest(event) { - const req = event.request; - await load; - await readyPromise; - return handleRequest(req); +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } } - -addEventListener("fetch", (event) => { - event.respondWith(processRequest(event)); -}) diff --git a/examples/r2-image-viewer/main.go b/examples/r2-image-viewer/main.go index 924281a..cc897ab 100644 --- a/examples/r2-image-viewer/main.go +++ b/examples/r2-image-viewer/main.go @@ -23,7 +23,7 @@ func handleErr(w http.ResponseWriter, msg string, err error) { // This example is based on implementation in syumai/workers-playground // - https://github.com/syumai/workers-playground/blob/e32881648ccc055e3690a0d9c750a834261c333e/r2-image-viewer/src/index.ts#L30 func handler(w http.ResponseWriter, req *http.Request) { - bucket, err := cloudflare.NewR2Bucket(bucketName) + bucket, err := cloudflare.NewR2Bucket(req.Context(), bucketName) if err != nil { handleErr(w, "failed to get R2Bucket\n", err) return diff --git a/examples/r2-image-viewer/worker.mjs b/examples/r2-image-viewer/worker.mjs index 06ac27d..649ccf0 100644 --- a/examples/r2-image-viewer/worker.mjs +++ b/examples/r2-image-viewer/worker.mjs @@ -4,22 +4,19 @@ import mod from "./dist/app.wasm"; const go = new Go(); +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { go.run(instance); return instance; }); -const readyPromise = new Promise((resolve) => { - globalThis.ready = resolve; -}); - -async function processRequest(event) { - const req = event.request; - await load; - await readyPromise; - return handleRequest(req); +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } } - -addEventListener("fetch", (event) => { - event.respondWith(processRequest(event)); -}) diff --git a/examples/simple-json-server/app/app_easyjson.go b/examples/simple-json-server/app/app_easyjson.go index 73b768e..185bcfa 100644 --- a/examples/simple-json-server/app/app_easyjson.go +++ b/examples/simple-json-server/app/app_easyjson.go @@ -4,7 +4,6 @@ package app import ( json "encoding/json" - easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" @@ -18,7 +17,7 @@ var ( _ easyjson.Marshaler ) -func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(in *jlexer.Lexer, out *HelloResponse) { +func easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(in *jlexer.Lexer, out *HelloResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -49,7 +48,7 @@ func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(in *jlexer.Le in.Consumed() } } -func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(out *jwriter.Writer, in HelloResponse) { +func easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(out *jwriter.Writer, in HelloResponse) { out.RawByte('{') first := true _ = first @@ -64,27 +63,27 @@ func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(out *jwriter. // MarshalJSON supports json.Marshaler interface func (v HelloResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(&w, v) + easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(w, v) + easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(&r, v) + easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(l, v) + easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(l, v) } -func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(in *jlexer.Lexer, out *HelloRequest) { +func easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(in *jlexer.Lexer, out *HelloRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -115,7 +114,7 @@ func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(in *jlexer.L in.Consumed() } } -func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(out *jwriter.Writer, in HelloRequest) { +func easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(out *jwriter.Writer, in HelloRequest) { out.RawByte('{') first := true _ = first @@ -130,23 +129,23 @@ func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(out *jwriter // MarshalJSON supports json.Marshaler interface func (v HelloRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(&w, v) + easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(w, v) + easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(&r, v) + easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(l, v) + easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(l, v) } diff --git a/examples/simple-json-server/worker.mjs b/examples/simple-json-server/worker.mjs index 06ac27d..649ccf0 100644 --- a/examples/simple-json-server/worker.mjs +++ b/examples/simple-json-server/worker.mjs @@ -4,22 +4,19 @@ import mod from "./dist/app.wasm"; const go = new Go(); +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { go.run(instance); return instance; }); -const readyPromise = new Promise((resolve) => { - globalThis.ready = resolve; -}); - -async function processRequest(event) { - const req = event.request; - await load; - await readyPromise; - return handleRequest(req); +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } } - -addEventListener("fetch", (event) => { - event.respondWith(processRequest(event)); -}) diff --git a/handler.go b/handler.go index 4612961..bc9232a 100644 --- a/handler.go +++ b/handler.go @@ -1,12 +1,14 @@ package workers import ( + "context" "fmt" "io" "net/http" "syscall/js" "github.com/syumai/workers/internal/jsutil" + "github.com/syumai/workers/internal/runtimecontext" ) var httpHandler http.Handler @@ -14,15 +16,20 @@ var httpHandler http.Handler func init() { var handleRequestCallback js.Func handleRequestCallback = js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 1 { + if len(args) > 2 { panic(fmt.Errorf("too many args given to handleRequest: %d", len(args))) } + reqObj := args[0] + runtimeCtxObj := js.Null() + if len(args) > 1 { + runtimeCtxObj = args[1] + } var cb js.Func cb = js.FuncOf(func(_ js.Value, pArgs []js.Value) any { defer cb.Release() resolve := pArgs[0] go func() { - res, err := handleRequest(args[0]) + res, err := handleRequest(reqObj, runtimeCtxObj) if err != nil { panic(err) } @@ -36,7 +43,7 @@ func init() { } // handleRequest accepts a Request object and returns Response object. -func handleRequest(reqObj js.Value) (js.Value, error) { +func handleRequest(reqObj js.Value, runtimeCtxObj js.Value) (js.Value, error) { if httpHandler == nil { return js.Value{}, fmt.Errorf("Serve must be called before handleRequest.") } @@ -44,6 +51,8 @@ func handleRequest(reqObj js.Value) (js.Value, error) { if err != nil { panic(err) } + ctx := runtimecontext.New(context.Background(), runtimeCtxObj) + req = req.WithContext(ctx) reader, writer := io.Pipe() w := &responseWriterBuffer{ header: http.Header{}, diff --git a/internal/runtimecontext/context.go b/internal/runtimecontext/context.go new file mode 100644 index 0000000..aed2463 --- /dev/null +++ b/internal/runtimecontext/context.go @@ -0,0 +1,25 @@ +package runtimecontext + +import ( + "context" + "errors" + "syscall/js" +) + +type runtimeCtxKey struct{} + +func New(ctx context.Context, runtimeCtxObj js.Value) context.Context { + return context.WithValue(ctx, runtimeCtxKey{}, runtimeCtxObj) +} + +var ErrRuntimeContextNotFound = errors.New("runtime context was not found") + +// MustExtract extracts runtime context object from context. +// This function panics when runtime context object was not found. +func MustExtract(ctx context.Context) js.Value { + v, ok := ctx.Value(runtimeCtxKey{}).(js.Value) + if !ok { + panic(ErrRuntimeContextNotFound) + } + return v +}