GopeedLab/gopeed · error

missing handler

Error message

missing handler

What it means

Thrown by the event-registration bridge in newJSEventsRuntime (extension_runtime_webview.go:74-87) when a gopeed.events listener is registered with zero arguments. All of onResolve, onStart, onError and onDone route through the same register closure, which requires exactly one callable handler argument; an empty call panics immediately as a JS exception inside the extension script.

Source

Thrown at pkg/download/extension_runtime_webview.go:78

	})
	_ = obj.Set("revokeObjectURL", func(call goja.FunctionCall) goja.Value {
		fn, ok := goja.AssertFunction(vm.Get("__gopeed_blob_revoke_object_url"))
		if !ok {
			panic(vm.ToValue(fmt.Errorf("blob runtime is not available")))
		}
		if _, err := fn(goja.Undefined(), call.Argument(0)); err != nil {
			panic(vm.ToValue(err))
		}
		return goja.Undefined()
	})
	return obj
}

func newJSEventsRuntime(vm *goja.Runtime, events InstanceEvents) *goja.Object {
	obj := vm.NewObject()
	register := func(event ActivationEvent, call goja.FunctionCall) goja.Value {
		if len(call.Arguments) == 0 {
			panic(vm.ToValue(fmt.Errorf("missing handler")))
		}
		fnValue := call.Argument(0)
		exported, ok := fnValue.Export().(func(goja.FunctionCall) goja.Value)
		if !ok {
			panic(vm.ToValue(fmt.Errorf("handler must be a function")))
		}
		events.register(event, engine.JSFunction(exported))
		return goja.Undefined()
	}
	_ = obj.Set("onResolve", func(call goja.FunctionCall) goja.Value {
		return register(EventOnResolve, call)
	})
	_ = obj.Set("onStart", func(call goja.FunctionCall) goja.Value {
		return register(EventOnStart, call)
	})
	_ = obj.Set("onError", func(call goja.FunctionCall) goja.Value {
		return register(EventOnError, call)
	})

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Pass a function as the first argument: gopeed.events.onDone(handler)
  2. If the handler comes from a variable, verify it is defined and callable before registering
  3. Check the extension log/stack trace for the exact registration site named in the panic

Example fix

// before
gopeed.events.onDone();

// after
gopeed.events.onDone((task) => {
  gopeed.logger.info(`task finished: ${task.id}`);
});
Defensive patterns

Strategy: validation

Validate before calling

if (arguments.length === 0 || typeof handler === "undefined") {
  throw new TypeError("event handler is required");
}
gopeed.events.onDone(handler);

Type guard

function hasHandler(fn) {
  return typeof fn === "function";
}

Prevention

When it happens

Trigger: Calling gopeed.events.onResolve(), onStart(), onError() or onDone() with no arguments, e.g. a forgotten callback while refactoring, a handler variable that was never defined so the call site ends up empty, or generated/glued code that emits the bare registration call.

Common situations: Copy-pasting an event registration template and deleting the handler; minifiers or code generators that drop unused function arguments; typos like gopeed.events.onDone; calling registration inside a loop where the handler array is empty.

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/96d54e8354e599c3. Report an issue: GitHub.