const postInterceptDispatcher = function()

in Clients/AmbrosiaJS/Ambrosia-Node/src/ICProcess.ts [168:329]


        const postInterceptDispatcher = function(message: Messages.DispatchedMessage): void
        {
            if (message.type === Messages.DispatchedMessageType.RPC)
            {
                let rpc: Messages.IncomingRPC = message as Messages.IncomingRPC;
                let expandTypes: boolean = false;
                let attrs: string = "";

                if (rpc.methodID === POST_BY_IMPULSE_METHOD_ID)
                {
                    // Create a Post call from the Impulse self-call
                    // TODO: The downside of handling this internally (rather than via code-gen), is that the user never gets to know the callID
                    //       for the post method call [unlike when using postFork()], which may make it harder to write the postResult handler
                    const destinationInstance: string = rpc.getJsonParam("destinationInstance");
                    const methodName: string = rpc.getJsonParam("methodName");
                    const methodVersion: number = parseInt(rpc.getJsonParam("methodVersion"));
                    const resultTimeoutInMs: number = parseInt(rpc.getJsonParam("resultTimeoutInMs"));
                    const callContextData: any = rpc.getJsonParam("callContextData");
                    const methodArgs: PostMethodArg[] = rpc.getJsonParam("methodArgs");

                    Utils.log(`Intercepted [Impulse] RPC invocation for post method '${methodName}'`);
                    postFork(destinationInstance, methodName, methodVersion, resultTimeoutInMs, callContextData, ...methodArgs);
                    return;
                }

                if (rpc.methodID === POST_METHOD_ID)
                {
                    // Handle post method results
                    if (_poster.isPostResult(rpc))
                    {
                        // isPostResult() will have invoked the result handler, so we're done
                        return;
                    }

                    // Handle "built-in" post methods (Note: These methods are NOT published)
                    switch (getPostMethodName(rpc))
                    {
                        case "_getPublishedMethods":
                            expandTypes = getPostMethodArg(rpc, "expandTypes");
                            let includePostMethodsOnly: boolean = getPostMethodArg(rpc, "includePostMethodsOnly");
                            let methodListXml: string = Meta.getPublishedMethodsXml(expandTypes, includePostMethodsOnly);
                            attrs = `fromInstance="${_config.icInstanceName}" expandedTypes="${expandTypes}"`;
                            postResult<string>(rpc, (methodListXml.length === 0) ? `<Methods ${attrs}/>` : `<Methods ${attrs}>${methodListXml}</Methods>`);
                            return;

                        case "_getPublishedTypes":
                            expandTypes = getPostMethodArg(rpc, "expandTypes");
                            let typeListXml: string = Meta.getPublishedTypesXml(expandTypes);
                            attrs = `fromInstance="${_config.icInstanceName}" expandedTypes="${expandTypes}"`;
                            postResult<string>(rpc, (typeListXml.length === 0) ? `<Types ${attrs}/>` : `<Types ${attrs}>${typeListXml}</Types>`);
                            return;

                        case "_isPublishedMethod":
                            let methodName: string = getPostMethodArg(rpc, "methodName");
                            let methodVersion: number = getPostMethodArg(rpc, "methodVersion");
                            let isPublished: boolean = (Meta.getPublishedMethod(methodName, methodVersion) !== null);
                            postResult<boolean>(rpc, isPublished);
                            return;

                        case "_echo":
                            postResult(rpc, getPostMethodArg(rpc, "payload")); // Note: The <T> value for postResult() isn't known in this case
                            return;

                        case "_ping":
                            postResult<number>(rpc, getPostMethodArg(rpc, "sentTime")); // The actual result (roundtripTimeInMs) is computed when the postResult is received
                            return;
                    }

                    // Handle the case where the post method/version has not been published (or the supplied parameters don't match the published parameters)
                    // Note: We can't do the same for non-post methods [by checking for the methodID] because non-post methods don't have an error-return capability
                    let methodName: string = getPostMethodName(rpc);
                    let methodVersion: number = getPostMethodVersion(rpc);
                    let isPublished: boolean = Meta.isPublishedMethod(methodName);
                    let method: Meta.Method | null = !isPublished ? null : Meta.getPublishedMethod(methodName, methodVersion);
                    let isSupportedVersion: boolean = (method !== null);

                    if (method !== null) // "if (isSupportedVersion)" isn't sufficient to make the compiler happy when using 'strictNullChecks'
                    {
                        let unknownArgNames: string[] = [];

                        for (const paramName of rpc.jsonParamNames)
                        {
                            if (paramName.startsWith(Poster.METHOD_PARAMETER_PREFIX))
                            {
                                // Whether the method parameter is optional but was supplied as required, or (conversely) if the method
                                // parameter is required but was supplied as optional, we'll still accept the supplied parameter
                                const argName: string = paramName.replace(Poster.METHOD_PARAMETER_PREFIX, "");
                                if (method.parameterNames.map(pn => Meta.Method.trimRest(Utils.trimTrailingChar(pn, "?"))).indexOf(Utils.trimTrailingChar(argName, "?")) === -1)
                                {
                                    unknownArgNames.push(argName);
                                }
                            }
                        }

                        if (unknownArgNames.length > 0)
                        {
                            Utils.log(`Warning: Instance '${getPostMethodSender(rpc)}' supplied ${unknownArgNames.length} unexpected arguments (${unknownArgNames.join(", ")}) for post method '${methodName}'`);
                            postError(rpc, new Error(`${unknownArgNames.length} unexpected arguments (${unknownArgNames.join(", ")}) were supplied`));
                            return;
                        }

                        // Check that all the required published parameters have been supplied, and are of the correct type
                        for (let i = 0; i < method.parameterNames.length; i++)
                        {
                            let paramName: string = Meta.Method.trimRest(method.parameterNames[i]);
                            let paramType: string = method.parameterTypes[i];
                            let expandedParamType: string = method.expandedParameterTypes[i];
                            let incomingParamName: string = Poster.METHOD_PARAMETER_PREFIX + paramName;
                            let incomingParamValue: any = rpc.getJsonParam(incomingParamName);

                            if (!paramName.endsWith("?") && (incomingParamValue === undefined))
                            {
                                Utils.log(`Warning: Instance '${getPostMethodSender(rpc)}' did not supply required argument '${paramName}' for post method '${methodName}'`);
                                postError(rpc, new Error(`Required argument '${paramName}' was not supplied (argument type: ${paramType})`));
                                return;
                            }

                            // Check that the type of the published/supplied parameters match
                            if (_config.lbOptions.typeCheckIncomingPostMethodParameters && method.isTypeChecked && (paramType !== "any") && (expandedParamType !== "any"))
                            {
                                const incomingParamType: string = Meta.Type.getRuntimeType(incomingParamValue); // May return null
                                if (incomingParamType)
                                {
                                    const failureReason: string | null = Meta.Type.compareTypes(incomingParamType, expandedParamType);
                                    if (failureReason)
                                    {
                                        Utils.log(`Warning: Instance '${getPostMethodSender(rpc)}' sent a parameter ('${paramName}') of the wrong type (${incomingParamType}) for post method '${methodName}'; ${failureReason}`);
                                        postError(rpc, new Error(`Argument '${paramName}' is of the wrong type (${incomingParamType}); ${failureReason}`));
                                        return;
                                    }
                                }
                            }
                        }
                    }
                    if (!isPublished)
                    {
                        Utils.log(`Warning: Instance '${getPostMethodSender(rpc)}' requested a non-published post method '${methodName}'`);
                        postError(rpc, new Error(`The method is not published`));
                        return;
                    }
                    if (!isSupportedVersion)
                    {
                        Utils.log(`Warning: Instance '${getPostMethodSender(rpc)}' requested a non-published version (${methodVersion}) of post method '${methodName}'`);
                        postError(rpc, new Error(`The requested version (${methodVersion}) of the method is not published`));
                        return;
                    }
                }
            }
            // Call the app-provided dispatcher. 
            // Note: The VSCode profiler may report that all the functions in the app's dispatcher are actually being called by postInterceptDispatcher(). 
            //       The current thinking for why this happens is because the V8 compiler is doing some sort of tail-call optimization.
            //       A workaround to get a more "reasonable" function name (eg. "wrappedDispatcher") recorded by the profiler is to do this:
            //           function wrappedDispatcher()
            //           {
            //               dispatcher(message);
            //           }
            //           wrappedDispatcher();
            //       However, this is a performance issue as we're putting another "needless" frame on the stack.
            //       The better workaround is for the user to modify their dispatcher method (if they so desire) to get a "full fidelity" stack when profiled,
            //       eg. by making a nested function call to the "real" dispatcher.
            dispatcher(message);
        }