{"record":{"id":"96d54e8354e599c3","repo":"GopeedLab/gopeed","slug":"missing-handler","errorCode":null,"errorMessage":"missing handler","messagePattern":"missing handler","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/extension_runtime_webview.go","lineNumber":78,"sourceCode":"\t})\n\t_ = obj.Set(\"revokeObjectURL\", func(call goja.FunctionCall) goja.Value {\n\t\tfn, ok := goja.AssertFunction(vm.Get(\"__gopeed_blob_revoke_object_url\"))\n\t\tif !ok {\n\t\t\tpanic(vm.ToValue(fmt.Errorf(\"blob runtime is not available\")))\n\t\t}\n\t\tif _, err := fn(goja.Undefined(), call.Argument(0)); err != nil {\n\t\t\tpanic(vm.ToValue(err))\n\t\t}\n\t\treturn goja.Undefined()\n\t})\n\treturn obj\n}\n\nfunc newJSEventsRuntime(vm *goja.Runtime, events InstanceEvents) *goja.Object {\n\tobj := vm.NewObject()\n\tregister := func(event ActivationEvent, call goja.FunctionCall) goja.Value {\n\t\tif len(call.Arguments) == 0 {\n\t\t\tpanic(vm.ToValue(fmt.Errorf(\"missing handler\")))\n\t\t}\n\t\tfnValue := call.Argument(0)\n\t\texported, ok := fnValue.Export().(func(goja.FunctionCall) goja.Value)\n\t\tif !ok {\n\t\t\tpanic(vm.ToValue(fmt.Errorf(\"handler must be a function\")))\n\t\t}\n\t\tevents.register(event, engine.JSFunction(exported))\n\t\treturn goja.Undefined()\n\t}\n\t_ = obj.Set(\"onResolve\", func(call goja.FunctionCall) goja.Value {\n\t\treturn register(EventOnResolve, call)\n\t})\n\t_ = obj.Set(\"onStart\", func(call goja.FunctionCall) goja.Value {\n\t\treturn register(EventOnStart, call)\n\t})\n\t_ = obj.Set(\"onError\", func(call goja.FunctionCall) goja.Value {\n\t\treturn register(EventOnError, call)\n\t})","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/extension_runtime_webview.go#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a function as the first argument: gopeed.events.onDone(handler)","If the handler comes from a variable, verify it is defined and callable before registering","Check the extension log/stack trace for the exact registration site named in the panic"],"exampleFix":"// before\ngopeed.events.onDone();\n\n// after\ngopeed.events.onDone((task) => {\n  gopeed.logger.info(`task finished: ${task.id}`);\n});","handlingStrategy":"validation","validationCode":"if (arguments.length === 0 || typeof handler === \"undefined\") {\n  throw new TypeError(\"event handler is required\");\n}\ngopeed.events.onDone(handler);","typeGuard":"function hasHandler(fn) {\n  return typeof fn === \"function\";\n}","tryCatchPattern":null,"preventionTips":["Register handlers with inline arrow functions so the argument is never omitted","Lint extension code with no-unused-expressions rules that flag bare method calls","Centralize event registration in one helper that validates its arguments"],"tags":["extension","events","javascript","validation"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}