Yes, registering your method with the window object is correct.
The relevant part of the source code can be found here:
https://github.com/aspnet/Extensions/tree/master/src/JSInterop
Peeking into it you will find:
invokeJSFromDotNet: (identifier: string, argsJson: string) => { ... }
where
@param identifier Identifies the globally-reachable function to
invoke.
Taking a look at the findJSFunction you see the part that checks if the given identifier is registered with the window object and if not generates the error you see:
function findJSFunction(identifier: string): Function {
(...)
let result: any = window;
let resultIdentifier = 'window';
let lastSegmentValue: any;
identifier.split('.').forEach(segment => {
if (segment in result) {
lastSegmentValue = result;
result = result[segment];
resultIdentifier += '.' + segment;
} else {
throw new Error(`Could not find '${segment}' in '${resultIdentifier}'.`);
}
});