{"record":{"id":"88be84cb95e3eb9d","repo":"GopeedLab/gopeed","slug":"handler-must-be-a-function","errorCode":null,"errorMessage":"handler must be a function","messagePattern":"handler must be a function","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/extension_runtime_webview.go","lineNumber":83,"sourceCode":"\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})\n\t_ = obj.Set(\"onDone\", func(call goja.FunctionCall) goja.Value {\n\t\treturn register(EventOnDone, call)\n\t})\n\treturn obj\n}","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/extension_runtime_webview.go#L65-L101","documentation":"Thrown by the same register closure when an event handler argument is present but is not a function. The bridge exports the first argument and asserts it to func(goja.FunctionCall) goja.Value (extension_runtime_webview.go:80-84), the shape goja produces for callable JS values; strings, numbers, plain objects, booleans and undefined all fail the assertion.","triggerScenarios":"Calling gopeed.events.onResolve(\"handler.js\"), passing an object like {handle(){}} instead of the function itself, passing the result of a call (onResolve(register())) instead of the reference, or passing an identifier that is undefined (typo in the function name).","commonSituations":"Registering a method without binding (passing obj.method loses nothing, but passing obj itself fails); passing a string name expecting the runtime to look it up; referencing a handler defined in another module that was never imported.","solutions":["Pass the function reference directly: gopeed.events.onError(handler)","If wrapping state, pass an arrow: gopeed.events.onError((t, e) => obj.onError(t, e))","Verify the identifier is defined (typeof handler === 'function') at the call site"],"exampleFix":"// before\ngopeed.events.onError(\"handleError\");\n\n// after\nfunction handleError(task, err) {\n  gopeed.logger.warn(String(err));\n}\ngopeed.events.onError(handleError);","handlingStrategy":"type-guard","validationCode":"if (typeof handler !== \"function\") {\n  throw new TypeError(`handler for ${event} must be a function, got ${typeof handler}`);\n}","typeGuard":"function isHandler(value) {\n  return typeof value === \"function\";\n}","tryCatchPattern":null,"preventionTips":["Pass function references, never string names or option objects","Verify imported handler modules resolved (typo'd imports are undefined)","Bind methods explicitly when registering: gopeed.events.onError((t, e) => obj.onError(t, e))"],"tags":["extension","events","javascript","type-validation"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}