diff --git a/sharedworker/self.go b/sharedworker/self.go new file mode 100644 index 0000000..d136f33 --- /dev/null +++ b/sharedworker/self.go @@ -0,0 +1,57 @@ +//go:build js && wasm + +package sharedworker + +import ( + "context" + + "github.com/hack-pad/go-webworkers/types" + + "github.com/hack-pad/safejs" +) + +// GlobalSelf represents the global scope, named "self", in the context of using SharedWorkers. +// Supports receiving connection via Listen(), where each of the ConnectEvent has Ports() whose +// first element represents the MessagePort connected with the channel with its parent, +// which in turns support receiving message via its Listen() and PostMessage(). +type GlobalSelf struct { + self *types.SharedWorkerGlobalScope +} + +// Self returns the global "self" +func Self() (*GlobalSelf, error) { + self, err := safejs.Global().Get("self") + if err != nil { + return nil, err + } + scope, err := types.WrapSharedWorkerGlobalScope(self) + if err != nil { + return nil, err + } + return &GlobalSelf{ + self: scope, + }, nil +} + +// Listen sends message events representing the connect event on a channel for events fired +// by connection calls to this worker from within the parent scope. +// Users are expected to call the Ports() on the MessageEvent, and take the 1st one as the target MessagePort. +// Stops the listener and closes the channel when ctx is canceled. +func (s *GlobalSelf) Listen(ctx context.Context) (<-chan types.MessageEventConnect, error) { + return s.self.Listen(ctx) +} + +// Close discards any tasks queued in the global scope's event loop, effectively closing this particular scope. +func (s *GlobalSelf) Close() error { + return s.self.Close() +} + +// Name returns the name that the Worker was (optionally) given when it was created. +func (s *GlobalSelf) Name() (string, error) { + return s.self.Name() +} + +// Location returns the WorkerLocation in the form of url.URL for this worker. +func (s *GlobalSelf) Location() (*types.WorkerLocation, error) { + return s.self.Location() +} diff --git a/sharedworker/self_test.go b/sharedworker/self_test.go new file mode 100644 index 0000000..2854be4 --- /dev/null +++ b/sharedworker/self_test.go @@ -0,0 +1,39 @@ +//go:build js && wasm + +package sharedworker + +import ( + "testing" +) + +func TestSelfName(t *testing.T) { + t.Skip("This test case only runs inside a worker") + t.Parallel() + self, err := Self() + if err != nil { + t.Fatal(err) + } + name, err := self.Name() + if err != nil { + t.Fatal(err) + } + if name != "" { + t.Errorf("Expected %q, got %q", "", name) + } +} + +func TestSelfLocation(t *testing.T) { + t.Skip("This test case only runs inside a worker") + t.Parallel() + self, err := Self() + if err != nil { + t.Fatal(err) + } + loc, err := self.Location() + if err != nil { + t.Fatal(err) + } + if loc.String() == "" { + t.Errorf("Expected %q, got %q", loc.String(), "") + } +} diff --git a/sharedworker/shared_worker.go b/sharedworker/shared_worker.go new file mode 100644 index 0000000..2d9afd1 --- /dev/null +++ b/sharedworker/shared_worker.go @@ -0,0 +1,100 @@ +//go:build js && wasm + +// Package sharedworker provides a Shared Web Workers driver for Go code compiled to WebAssembly. +package sharedworker + +import ( + "context" + + "github.com/hack-pad/go-webworkers/types" + + "github.com/hack-pad/safejs" +) + +var ( + jsURL = safejs.MustGetGlobal("URL") + jsBlob = safejs.MustGetGlobal("Blob") +) + +// SharedWorker is a Shared Web Worker, which represents a background task created via a script. +// Use Listen() and PostMessage() to communicate with the worker. +type SharedWorker struct { + url string + name string + worker safejs.Value + msgport *types.MessagePort +} + +// New starts a worker with the given script's URL and name +func New(url, name string) (*SharedWorker, error) { + worker, err := safejs.MustGetGlobal("SharedWorker").New(url, name) + if err != nil { + return nil, err + } + port, err := worker.Get("port") + if err != nil { + return nil, err + } + msgport, err := types.WrapMessagePort(port) + if err != nil { + return nil, err + } + return &SharedWorker{ + url: url, + name: name, + msgport: msgport, + worker: worker, + }, nil +} + +// NewFromScript is like New, but starts the worker with the given script (in JavaScript) +func NewFromScript(jsScript, name string) (*SharedWorker, error) { + blob, err := jsBlob.New([]any{jsScript}, map[string]any{ + "type": "text/javascript", + }) + if err != nil { + return nil, err + } + objectURL, err := jsURL.Call("createObjectURL", blob) + if err != nil { + return nil, err + } + objectURLStr, err := objectURL.String() + if err != nil { + return nil, err + } + return New(objectURLStr, name) +} + +// URL returns the script URL of the worker +func (w *SharedWorker) URL() string { + return w.url +} + +// Name returns the name of the worker +func (w *SharedWorker) Name() string { + return w.name +} + +// PostMessage sends data in a message to the worker, optionally transferring ownership of all items in transfers. +// +// The data may be any value handled by the "structured clone algorithm", which includes cyclical references. +// +// Transfers is an optional array of Transferable objects to transfer ownership of. +// If the ownership of an object is transferred, it becomes unusable in the context it was sent from and becomes available only to the worker it was sent to. +// Transferable objects are instances of classes like ArrayBuffer, MessagePort or ImageBitmap objects that can be transferred. +// null is not an acceptable value for transfer. +func (w *SharedWorker) PostMessage(data safejs.Value, transfers []safejs.Value) error { + return w.msgport.PostMessage(data, transfers) +} + +// Listen sends message events on a channel for events fired by self.postMessage() calls inside the Worker's global scope. +// Stops the listener and closes the channel when ctx is canceled. +func (w *SharedWorker) Listen(ctx context.Context) (<-chan types.MessageEventMessage, error) { + return w.msgport.Listen(ctx) +} + +// Close closes the message port of this worker. +func (w *SharedWorker) Close() error { + return w.msgport.Close() +} diff --git a/sharedworker/shared_worker_test.go b/sharedworker/shared_worker_test.go new file mode 100644 index 0000000..27c1334 --- /dev/null +++ b/sharedworker/shared_worker_test.go @@ -0,0 +1,182 @@ +//go:build js && wasm + +package sharedworker + +import ( + "context" + "fmt" + "testing" + + "github.com/hack-pad/safejs" +) + +var ( + jsJSON = safejs.MustGetGlobal("JSON") + jsUint8Array = safejs.MustGetGlobal("Uint8Array") +) + +func makeBlobURL(t *testing.T, contents []byte, contentType string) string { + t.Helper() + jsContents, err := jsUint8Array.New(len(contents)) + if err != nil { + t.Fatal(err) + } + _, err = safejs.CopyBytesToJS(jsContents, contents) + if err != nil { + t.Fatal(err) + } + blob, err := jsBlob.New([]any{jsContents}, map[string]any{ + "type": contentType, + }) + if err != nil { + t.Fatal(err) + } + url, err := jsURL.Call("createObjectURL", blob) + if err != nil { + t.Fatal(err) + } + urlString, err := url.String() + if err != nil { + t.Fatal(err) + } + return urlString +} + +func TestNew(t *testing.T) { + t.Parallel() + const messageText = "Hello, world!" + blobURL := makeBlobURL(t, []byte(fmt.Sprintf(`"use strict"; +onconnect = (e) => { + const port = e.ports[0]; + port.postMessage(self.name + ": " + %q); +}; +`, messageText)), "text/javascript") + workerName := "worker" + worker, err := New(blobURL, workerName) + if err != nil { + t.Fatal(err) + } + + if worker.URL() != blobURL { + t.Fatalf("url expect=%q, got=%q", blobURL, worker.URL()) + } + + if worker.Name() != workerName { + t.Fatalf("url expect=%q, got=%q", workerName, worker.Name()) + } + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + messages, err := worker.Listen(ctx) + if err != nil { + t.Fatal(err) + } + message := <-messages + data, err := message.Data() + if err != nil { + t.Fatal(err) + } + dataStr, err := data.String() + if err != nil { + t.Fatal(err) + } + if msg := workerName + ": " + messageText; dataStr != msg { + t.Errorf("Expected %q, got %q", msg, dataStr) + } +} + +func TestNewFromScript(t *testing.T) { + t.Parallel() + const messageText = "Hello, world!" + script := fmt.Sprintf(` +"use strict"; + +onconnect = (e) => { + const port = e.ports[0]; + port.postMessage(self.name + ": " + %q); +}; +`, messageText) + workerName := "worker" + worker, err := NewFromScript(script, workerName) + if err != nil { + t.Fatal(err) + } + if worker.URL() == "" { + t.Fatal("url unexpect to be empty") + } + + if worker.Name() != workerName { + t.Fatalf("url expect=%q, got=%q", workerName, worker.Name()) + } + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + messages, err := worker.Listen(ctx) + if err != nil { + t.Fatal(err) + } + message := <-messages + data, err := message.Data() + if err != nil { + t.Fatal(err) + } + dataStr, err := data.String() + if err != nil { + t.Fatal(err) + } + if msg := workerName + ": " + messageText; dataStr != msg { + t.Errorf("Expected %q, got %q", msg, dataStr) + } +} + +func TestWorkerPostMessage(t *testing.T) { + t.Parallel() + const pingPongScript = ` +"use strict"; + +onconnect = (e) => { + const port = e.ports[0]; + port.onmessage = (event) => { + port.postMessage(event.data + " pong!"); + }; +}; +` + pingMessage, err := safejs.ValueOf("ping!") + if err != nil { + t.Fatal(err) + } + + t.Run("listen before post", func(t *testing.T) { + t.Parallel() + worker, err := NewFromScript(pingPongScript, "") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + messages, err := worker.Listen(ctx) + if err != nil { + t.Fatal(err) + } + + err = worker.PostMessage(pingMessage, nil) + if err != nil { + t.Fatal(err) + } + + message := <-messages + data, err := message.Data() + if err != nil { + t.Fatal(err) + } + dataStr, err := data.String() + if err != nil { + t.Error(err) + } + expectedResponse := "ping! pong!" + if dataStr != expectedResponse { + t.Errorf("Expected response %q, got: %q", expectedResponse, dataStr) + } + }) +} diff --git a/types/listen_message_event.go b/types/listen_message_event.go new file mode 100644 index 0000000..65b1cb2 --- /dev/null +++ b/types/listen_message_event.go @@ -0,0 +1,64 @@ +package types + +import ( + "context" + + "github.com/hack-pad/safejs" +) + +type listenable interface { + MessageEventConnect | MessageEventMessage +} + +// listen adds the EventListener on the listener for the specified events. +// It returns a channel, which will send the MessageEvent(s) listened on, until the ctx is canceled. +func listen[T listenable](ctx context.Context, listener safejs.Value, parseFunc func(safejs.Value) T, events ...string) (_ <-chan T, err error) { + ctx, cancel := context.WithCancel(ctx) + defer func() { + if err != nil { + cancel() + } + }() + + eventsCh := make(chan T) + + var handlers []safejs.Func + for range events { + handler, err := nonBlocking(func(args []safejs.Value) { + eventsCh <- parseFunc(args[0]) + }) + if err != nil { + return nil, err + } + handlers = append(handlers, handler) + } + + go func() { + <-ctx.Done() + for i := range events { + event, handler := events[i], handlers[i] + _, err := listener.Call("removeEventListener", event, handler) + if err == nil { + handler.Release() + } + } + close(eventsCh) + }() + + for i := range events { + event, handler := events[i], handlers[i] + _, err = listener.Call("addEventListener", event, handler) + if err != nil { + return nil, err + } + } + + return eventsCh, nil +} + +func nonBlocking(fn func(args []safejs.Value)) (safejs.Func, error) { + return safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) any { + go fn(args) + return nil + }) +} diff --git a/types/message_event_connect.go b/types/message_event_connect.go new file mode 100644 index 0000000..0374812 --- /dev/null +++ b/types/message_event_connect.go @@ -0,0 +1,43 @@ +package types + +import ( + "github.com/hack-pad/safejs" + "github.com/pkg/errors" +) + +// MessageEventConnect represents a JS MessageEvent received from the "connect" event. +type MessageEventConnect struct { + ports []*MessagePort + err error +} + +// Ports returns this event's ports or a parse error +func (e MessageEventConnect) Ports() ([]*MessagePort, error) { + return e.ports, errors.Wrapf(e.err, "failed to parse MessageEventConnect %+v", e.ports) +} + +func parseMessageEventConnect(v safejs.Value) MessageEventConnect { + ports, err := v.Get("ports") + if err != nil { + return MessageEventConnect{err: err} + } + portsLen, err := ports.Length() + if err != nil { + return MessageEventConnect{err: err} + } + var msgports []*MessagePort + for i := 0; i < portsLen; i++ { + port, err := ports.Index(i) + if err != nil { + return MessageEventConnect{err: err} + } + msgport, err := WrapMessagePort(port) + if err != nil { + return MessageEventConnect{err: err} + } + msgports = append(msgports, msgport) + } + return MessageEventConnect{ + ports: msgports, + } +} diff --git a/types/message_event_message.go b/types/message_event_message.go new file mode 100644 index 0000000..28f9da1 --- /dev/null +++ b/types/message_event_message.go @@ -0,0 +1,39 @@ +//go:build js && wasm + +package types + +import ( + "github.com/hack-pad/safejs" + "github.com/pkg/errors" +) + +// MessageEventMessage represents a JS MessageEvent received from the "message" event. +type MessageEventMessage struct { + data safejs.Value + err error + target *MessagePort +} + +// Data returns this event's data or a parse error +func (e MessageEventMessage) Data() (safejs.Value, error) { + return e.data, errors.Wrapf(e.err, "failed to parse MessageEventMessage %+v", e.data) +} + +func parseMessageEventMessage(v safejs.Value) MessageEventMessage { + value, err := v.Get("target") + if err != nil { + return MessageEventMessage{err: err} + } + target, err := WrapMessagePort(value) + if err != nil { + return MessageEventMessage{err: err} + } + data, err := v.Get("data") + if err != nil { + return MessageEventMessage{err: err} + } + return MessageEventMessage{ + data: data, + target: target, + } +} diff --git a/types/message_port.go b/types/message_port.go new file mode 100644 index 0000000..2e6f06a --- /dev/null +++ b/types/message_port.go @@ -0,0 +1,62 @@ +//go:build js && wasm + +package types + +import ( + "context" + "fmt" + + "github.com/hack-pad/safejs" +) + +type MessagePort struct { + jsMessagePort safejs.Value +} + +func WrapMessagePort(v safejs.Value) (*MessagePort, error) { + someMethod, err := v.Get("postMessage") + if err != nil { + return nil, err + } + if truthy, err := someMethod.Truthy(); err != nil || !truthy { + return nil, fmt.Errorf("invalid MessagePort value: postMessage is not a function") + } + return &MessagePort{v}, nil +} + +func (p *MessagePort) PostMessage(data safejs.Value, transfers []safejs.Value) error { + args := append([]any{data}, toJSSlice(transfers)) + _, err := p.jsMessagePort.Call("postMessage", args...) + return err +} + +func toJSSlice[Type any](slice []Type) []any { + newSlice := make([]any, len(slice)) + for i := range slice { + newSlice[i] = slice[i] + } + return newSlice +} + +// Listen starts the MessagePort to listen on the "message" and "messageerror" events, until the ctx is canceled. +func (p *MessagePort) Listen(ctx context.Context) (<-chan MessageEventMessage, error) { + events, err := listen(ctx, p.jsMessagePort, parseMessageEventMessage, "message", "messageerror") + if err != nil { + return nil, err + } + + if start, err := p.jsMessagePort.Get("start"); err == nil { + if truthy, err := start.Truthy(); err == nil && truthy { + if _, err := p.jsMessagePort.Call("start"); err != nil { + return nil, err + } + } + } + return events, nil +} + +// Close disconnects the port, so it is no longer active. This stops the flow of messages to that port. +func (p *MessagePort) Close() error { + _, err := p.jsMessagePort.Call("close") + return err +} diff --git a/types/shared_worker_global_scope.go b/types/shared_worker_global_scope.go new file mode 100644 index 0000000..83bbe21 --- /dev/null +++ b/types/shared_worker_global_scope.go @@ -0,0 +1,87 @@ +//go:build js && wasm + +package types + +import ( + "context" + "fmt" + + "github.com/hack-pad/safejs" +) + +type SharedWorkerGlobalScope struct { + self safejs.Value +} + +func WrapSharedWorkerGlobalScope(v safejs.Value) (*SharedWorkerGlobalScope, error) { + someMethod, err := v.Get("SharedWorkerGlobalScope") + if err != nil { + return nil, err + } + if truthy, err := someMethod.Truthy(); err != nil || !truthy { + return nil, fmt.Errorf("invalid SharedWorkerGlobalScope value: SharedWorkerGlobalScope is not a function") + } + return &SharedWorkerGlobalScope{v}, nil +} + +// Listen listens on the "connect" events, until the ctx is canceled. +func (p *SharedWorkerGlobalScope) Listen(ctx context.Context) (<-chan MessageEventConnect, error) { + events, err := listen(ctx, p.self, parseMessageEventConnect, "connect") + if err != nil { + return nil, err + } + return events, nil +} + +// Close discards any tasks queued in the global scope's event loop, effectively closing this particular scope. +func (p *SharedWorkerGlobalScope) Close() error { + _, err := p.self.Call("close") + return err +} + +// Name returns the name that the Worker was (optionally) given when it was created. +func (p *SharedWorkerGlobalScope) Name() (string, error) { + v, err := p.self.Get("name") + if err != nil { + return "", err + } + return v.String() +} + +// Location returns the WorkerLocation in the form of url.URL for this worker. +func (p *SharedWorkerGlobalScope) Location() (*WorkerLocation, error) { + loc, err := p.self.Get("location") + if err != nil { + return nil, err + } + + location := &WorkerLocation{} + l := []struct { + target *string + prop string + }{ + {&location.Hash, "hash"}, + {&location.Host, "host"}, + {&location.HostName, "hostname"}, + {&location.Href, "href"}, + {&location.Origin, "origin"}, + {&location.PathName, "pathname"}, + {&location.Port, "port"}, + {&location.Protocol, "protocol"}, + {&location.Search, "search"}, + } + + for _, entry := range l { + v, err := loc.Get(entry.prop) + if err != nil { + return nil, err + } + vv, err := v.String() + if err != nil { + return nil, err + } + *entry.target = vv + } + + return location, nil +} diff --git a/types/workerlocation.go b/types/workerlocation.go new file mode 100644 index 0000000..576ed72 --- /dev/null +++ b/types/workerlocation.go @@ -0,0 +1,17 @@ +package types + +type WorkerLocation struct { + Hash string + Host string + HostName string + Href string + Origin string + PathName string + Port string + Protocol string + Search string +} + +func (loc WorkerLocation) String() string { + return loc.Href +} diff --git a/worker/message_event.go b/worker/message_event.go deleted file mode 100644 index 44adf70..0000000 --- a/worker/message_event.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build js && wasm - -package worker - -import ( - "github.com/hack-pad/safejs" - "github.com/pkg/errors" -) - -// MessageEvent is received from the channel returned by Listen(). -// Represents a JS MessageEvent. -type MessageEvent struct { - data safejs.Value - err error - target *messagePort -} - -// Data returns this event's data or a parse error -func (e MessageEvent) Data() (safejs.Value, error) { - return e.data, errors.Wrapf(e.err, "failed to parse MessageEvent %+v", e.data) -} - -func parseMessageEvent(v safejs.Value) MessageEvent { - value, err := v.Get("target") - if err != nil { - return MessageEvent{err: err} - } - target, err := wrapMessagePort(value) - if err != nil { - return MessageEvent{err: err} - } - data, err := v.Get("data") - if err != nil { - return MessageEvent{err: err} - } - return MessageEvent{ - data: data, - target: target, - } -} diff --git a/worker/message_port.go b/worker/message_port.go deleted file mode 100644 index 8fb0e56..0000000 --- a/worker/message_port.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build js && wasm - -package worker - -import ( - "context" - "fmt" - - "github.com/hack-pad/safejs" -) - -type messagePort struct { - jsMessagePort safejs.Value -} - -func wrapMessagePort(v safejs.Value) (*messagePort, error) { - someMethod, err := v.Get("postMessage") - if err != nil { - return nil, err - } - if truthy, err := someMethod.Truthy(); err != nil || !truthy { - return nil, fmt.Errorf("invalid MessagePort value: postMessage is not a function") - } - return &messagePort{v}, nil -} - -func (p *messagePort) PostMessage(data safejs.Value, transfers []safejs.Value) error { - args := append([]any{data}, toJSSlice(transfers)) - _, err := p.jsMessagePort.Call("postMessage", args...) - return err -} - -func toJSSlice[Type any](slice []Type) []any { - newSlice := make([]any, len(slice)) - for i := range slice { - newSlice[i] = slice[i] - } - return newSlice -} - -func (p *messagePort) Listen(ctx context.Context) (_ <-chan MessageEvent, err error) { - ctx, cancel := context.WithCancel(ctx) - defer func() { - if err != nil { - cancel() - } - }() - - events := make(chan MessageEvent) - messageHandler, err := nonBlocking(func(args []safejs.Value) { - events <- parseMessageEvent(args[0]) - }) - if err != nil { - return nil, err - } - errorHandler, err := nonBlocking(func(args []safejs.Value) { - events <- parseMessageEvent(args[0]) - }) - if err != nil { - return nil, err - } - - go func() { - <-ctx.Done() - _, err := p.jsMessagePort.Call("removeEventListener", "message", messageHandler) - if err == nil { - messageHandler.Release() - } - _, err = p.jsMessagePort.Call("removeEventListener", "messageerror", errorHandler) - if err == nil { - errorHandler.Release() - } - close(events) - }() - _, err = p.jsMessagePort.Call("addEventListener", "message", messageHandler) - if err != nil { - return nil, err - } - _, err = p.jsMessagePort.Call("addEventListener", "messageerror", errorHandler) - if err != nil { - return nil, err - } - if start, err := p.jsMessagePort.Get("start"); err == nil { - if truthy, err := start.Truthy(); err == nil && truthy { - if _, err := p.jsMessagePort.Call("start"); err != nil { - return nil, err - } - } - } - return events, nil -} - -func nonBlocking(fn func(args []safejs.Value)) (safejs.Func, error) { - return safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) any { - go fn(args) - return nil - }) -} diff --git a/worker/self.go b/worker/self.go index cd773cd..2da9000 100644 --- a/worker/self.go +++ b/worker/self.go @@ -5,6 +5,8 @@ package worker import ( "context" + "github.com/hack-pad/go-webworkers/types" + "github.com/hack-pad/safejs" ) @@ -12,7 +14,7 @@ import ( // Supports sending and receiving messages via PostMessage() and Listen(). type GlobalSelf struct { self safejs.Value - port *messagePort + port *types.MessagePort } // Self returns the global "self" @@ -21,7 +23,7 @@ func Self() (*GlobalSelf, error) { if err != nil { return nil, err } - port, err := wrapMessagePort(self) + port, err := types.WrapMessagePort(self) if err != nil { return nil, err } @@ -46,7 +48,7 @@ func (s *GlobalSelf) PostMessage(message safejs.Value, transfers []safejs.Value) // Listen sends message events on a channel for events fired by worker.postMessage() calls inside the main thread's global scope. // Stops the listener and closes the channel when ctx is canceled. -func (s *GlobalSelf) Listen(ctx context.Context) (<-chan MessageEvent, error) { +func (s *GlobalSelf) Listen(ctx context.Context) (<-chan types.MessageEventMessage, error) { return s.port.Listen(ctx) } diff --git a/worker/worker.go b/worker/worker.go index 17ec9b0..b183456 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -1,25 +1,26 @@ //go:build js && wasm -// Package worker provides a Web Workers driver for Go code compiled to WebAssembly. +// Package worker provides a Dedicated Web Workers driver for Go code compiled to WebAssembly. package worker import ( "context" + "github.com/hack-pad/go-webworkers/types" + "github.com/hack-pad/safejs" ) var ( - jsWorker = safejs.MustGetGlobal("Worker") - jsURL = safejs.MustGetGlobal("URL") - jsBlob = safejs.MustGetGlobal("Blob") + jsURL = safejs.MustGetGlobal("URL") + jsBlob = safejs.MustGetGlobal("Blob") ) -// Worker is a Web Worker, which represents a background task created via a script. +// Worker is a Dedicaetd Web Worker, which represents a background task created via a script. // Use Listen() and PostMessage() to communicate with the worker. type Worker struct { worker safejs.Value - port *messagePort + port *types.MessagePort } // Options contains optional configuration for new Workers @@ -42,11 +43,11 @@ func New(url string, options Options) (*Worker, error) { if err != nil { return nil, err } - worker, err := jsWorker.New(url, jsOptions) + worker, err := safejs.MustGetGlobal("Worker").New(url, jsOptions) if err != nil { return nil, err } - port, err := wrapMessagePort(worker) + port, err := types.WrapMessagePort(worker) if err != nil { return nil, err } @@ -96,6 +97,6 @@ func (w *Worker) PostMessage(data safejs.Value, transfers []safejs.Value) error // Listen sends message events on a channel for events fired by self.postMessage() calls inside the Worker's global scope. // Stops the listener and closes the channel when ctx is canceled. -func (w *Worker) Listen(ctx context.Context) (<-chan MessageEvent, error) { +func (w *Worker) Listen(ctx context.Context) (<-chan types.MessageEventMessage, error) { return w.port.Listen(ctx) }