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);
}