2016-02-05 14:38:09 -05:00
// FunctionsRouter.js
2016-02-19 23:47:44 -05:00
2016-11-24 15:47:41 -05:00
var Parse = require ( 'parse/node' ) . Parse ,
triggers = require ( '../triggers' ) ;
2016-02-19 23:47:44 -05:00
import PromiseRouter from '../PromiseRouter' ;
2020-10-25 15:06:58 -05:00
import { promiseEnforceMasterKeyAccess , promiseEnsureIdempotency } from '../middlewares' ;
2016-08-30 07:19:21 -04:00
import { jobStatusHandler } from '../StatusHandler' ;
2016-06-10 03:37:05 +08:00
import _ from 'lodash' ;
2016-07-23 20:10:06 +02:00
import { logger } from '../logger' ;
2016-06-10 03:37:05 +08:00
2023-06-23 16:29:32 +02:00
function parseObject ( obj , config ) {
2016-07-19 02:14:32 -04:00
if ( Array . isArray ( obj ) ) {
2020-07-13 17:13:08 -05:00
return obj . map ( item => {
2024-01-15 17:02:57 +02:00
return parseObject ( item , config ) ;
2016-11-24 15:47:41 -05:00
} ) ;
2016-07-19 02:14:32 -04:00
} else if ( obj && obj . _ _type == 'Date' ) {
return Object . assign ( new Date ( obj . iso ) , obj ) ;
} else if ( obj && obj . _ _type == 'File' ) {
return Parse . File . fromJSON ( obj ) ;
2025-12-12 20:46:35 +01:00
} else if ( obj && obj . _ _type == 'Pointer' ) {
2023-05-26 06:02:33 +10:00
return Parse . Object . fromJSON ( {
_ _type : 'Pointer' ,
className : obj . className ,
objectId : obj . objectId ,
} ) ;
2016-07-19 02:14:32 -04:00
} else if ( obj && typeof obj === 'object' ) {
2023-06-23 16:29:32 +02:00
return parseParams ( obj , config ) ;
2016-07-19 02:14:32 -04:00
} else {
return obj ;
}
}
2023-06-23 16:29:32 +02:00
function parseParams ( params , config ) {
return _ . mapValues ( params , item => parseObject ( item , config ) ) ;
2016-06-10 03:37:05 +08:00
}
2016-02-19 23:47:44 -05:00
export class FunctionsRouter extends PromiseRouter {
mountRoutes ( ) {
2018-09-01 13:58:06 -04:00
this . route (
'POST' ,
'/functions/:functionName' ,
2020-07-15 20:10:33 +02:00
promiseEnsureIdempotency ,
2018-09-01 13:58:06 -04:00
FunctionsRouter . handleCloudFunction
) ;
this . route (
'POST' ,
'/jobs/:jobName' ,
2020-07-15 20:10:33 +02:00
promiseEnsureIdempotency ,
2018-09-01 13:58:06 -04:00
promiseEnforceMasterKeyAccess ,
2020-07-13 13:06:52 -05:00
function ( req ) {
2018-09-01 13:58:06 -04:00
return FunctionsRouter . handleCloudJob ( req ) ;
}
) ;
2020-07-13 13:06:52 -05:00
this . route ( 'POST' , '/jobs' , promiseEnforceMasterKeyAccess , function ( req ) {
2016-08-30 07:19:21 -04:00
return FunctionsRouter . handleCloudJob ( req ) ;
} ) ;
2016-02-19 23:47:44 -05:00
}
2016-04-22 08:20:14 +12:00
2016-08-30 07:19:21 -04:00
static handleCloudJob ( req ) {
2025-03-03 16:11:42 -05:00
const jobName = req . params . jobName || req . body ? . jobName ;
2016-08-30 07:19:21 -04:00
const applicationId = req . config . applicationId ;
const jobHandler = jobStatusHandler ( req . config ) ;
const jobFunction = triggers . getJob ( jobName , applicationId ) ;
if ( ! jobFunction ) {
throw new Parse . Error ( Parse . Error . SCRIPT _FAILED , 'Invalid job.' ) ;
}
let params = Object . assign ( { } , req . body , req . query ) ;
2023-06-23 16:29:32 +02:00
params = parseParams ( params , req . config ) ;
2016-08-30 07:19:21 -04:00
const request = {
params : params ,
log : req . config . loggerController ,
2017-10-18 14:13:09 +02:00
headers : req . config . headers ,
ip : req . config . ip ,
2018-08-06 17:39:38 -04:00
jobName ,
2025-10-14 20:16:31 +02:00
config : req . config ,
2018-09-01 13:58:06 -04:00
message : jobHandler . setMessage . bind ( jobHandler ) ,
2018-08-06 17:39:38 -04:00
} ;
2025-05-14 21:24:56 +02:00
return jobHandler . setRunning ( jobName ) . then ( jobStatus => {
2018-09-01 13:58:06 -04:00
request . jobId = jobStatus . objectId ;
2016-08-30 07:19:21 -04:00
// run the function async
2016-11-24 15:47:41 -05:00
process . nextTick ( ( ) => {
2018-09-01 13:58:06 -04:00
Promise . resolve ( )
. then ( ( ) => {
return jobFunction ( request ) ;
} )
. then (
2020-07-13 17:13:08 -05:00
result => {
2018-09-01 13:58:06 -04:00
jobHandler . setSucceeded ( result ) ;
} ,
2020-07-13 17:13:08 -05:00
error => {
2018-09-01 13:58:06 -04:00
jobHandler . setFailed ( error ) ;
}
) ;
2016-08-30 07:19:21 -04:00
} ) ;
return {
headers : {
2018-09-01 13:58:06 -04:00
'X-Parse-Job-Status-Id' : jobStatus . objectId ,
2016-08-30 07:19:21 -04:00
} ,
2018-09-01 13:58:06 -04:00
response : { } ,
} ;
2016-08-30 07:19:21 -04:00
} ) ;
}
2025-12-14 15:24:51 +01:00
static createResponseObject ( resolve , reject , statusCode = null ) {
let httpStatusCode = statusCode ;
const customHeaders = { } ;
let responseSent = false ;
const responseObject = {
2020-07-13 13:06:52 -05:00
success : function ( result ) {
2025-12-14 15:24:51 +01:00
if ( responseSent ) {
throw new Error ( 'Cannot call success() after response has already been sent. Make sure to call success() or error() only once per cloud function execution.' ) ;
}
responseSent = true ;
const response = {
2016-02-19 23:47:44 -05:00
response : {
2018-09-01 13:58:06 -04:00
result : Parse . _encode ( result ) ,
} ,
2025-12-14 15:24:51 +01:00
} ;
if ( httpStatusCode !== null ) {
response . status = httpStatusCode ;
}
if ( Object . keys ( customHeaders ) . length > 0 ) {
response . headers = customHeaders ;
}
resolve ( response ) ;
2016-02-19 23:47:44 -05:00
} ,
2020-07-13 13:06:52 -05:00
error : function ( message ) {
2025-12-14 15:24:51 +01:00
if ( responseSent ) {
throw new Error ( 'Cannot call error() after response has already been sent. Make sure to call success() or error() only once per cloud function execution.' ) ;
}
responseSent = true ;
2020-10-26 04:36:54 +11:00
const error = triggers . resolveError ( message ) ;
2025-12-14 15:24:51 +01:00
// If a custom status code was set, attach it to the error
if ( httpStatusCode !== null ) {
error . status = httpStatusCode ;
}
2020-10-22 08:42:50 +11:00
reject ( error ) ;
2016-08-30 07:19:21 -04:00
} ,
2025-12-14 15:24:51 +01:00
status : function ( code ) {
httpStatusCode = code ;
return responseObject ;
} ,
header : function ( key , value ) {
customHeaders [ key ] = value ;
return responseObject ;
} ,
_isResponseSent : ( ) => responseSent ,
2018-09-01 13:58:06 -04:00
} ;
2025-12-14 15:24:51 +01:00
return responseObject ;
2016-02-19 23:47:44 -05:00
}
static handleCloudFunction ( req ) {
2016-08-30 07:19:21 -04:00
const functionName = req . params . functionName ;
const applicationId = req . config . applicationId ;
const theFunction = triggers . getFunction ( functionName , applicationId ) ;
2020-10-26 04:36:54 +11:00
2018-08-06 17:39:38 -04:00
if ( ! theFunction ) {
2020-10-25 15:06:58 -05:00
throw new Parse . Error ( Parse . Error . SCRIPT _FAILED , ` Invalid function: " ${ functionName } " ` ) ;
2018-08-06 17:39:38 -04:00
}
let params = Object . assign ( { } , req . body , req . query ) ;
2023-06-23 16:29:32 +02:00
params = parseParams ( params , req . config ) ;
2018-08-06 17:39:38 -04:00
const request = {
params : params ,
2025-10-14 20:16:31 +02:00
config : req . config ,
2018-08-06 17:39:38 -04:00
master : req . auth && req . auth . isMaster ,
user : req . auth && req . auth . user ,
installationId : req . info . installationId ,
log : req . config . loggerController ,
headers : req . config . headers ,
ip : req . config . ip ,
2018-09-01 13:58:06 -04:00
functionName ,
2020-07-11 02:17:27 +05:30
context : req . info . context ,
2018-08-06 17:39:38 -04:00
} ;
2016-02-19 23:47:44 -05:00
2020-07-13 13:06:52 -05:00
return new Promise ( function ( resolve , reject ) {
2020-10-25 15:06:58 -05:00
const userString = req . auth && req . auth . user ? req . auth . user . id : undefined ;
2025-12-14 15:24:51 +01:00
const responseObject = FunctionsRouter . createResponseObject (
2020-07-13 17:13:08 -05:00
result => {
2018-09-01 13:58:06 -04:00
try {
2024-03-21 10:19:29 -05:00
if ( req . config . logLevels . cloudFunctionSuccess !== 'silent' ) {
const cleanInput = logger . truncateLogMessage ( JSON . stringify ( params ) ) ;
const cleanResult = logger . truncateLogMessage ( JSON . stringify ( result . response . result ) ) ;
logger [ req . config . logLevels . cloudFunctionSuccess ] (
` Ran cloud function ${ functionName } for user ${ userString } with: \n Input: ${ cleanInput } \n Result: ${ cleanResult } ` ,
{
functionName ,
params ,
user : userString ,
}
) ;
}
2018-09-01 13:58:06 -04:00
resolve ( result ) ;
} catch ( e ) {
reject ( e ) ;
}
} ,
2020-07-13 17:13:08 -05:00
error => {
2018-09-01 13:58:06 -04:00
try {
2024-03-21 10:19:29 -05:00
if ( req . config . logLevels . cloudFunctionError !== 'silent' ) {
const cleanInput = logger . truncateLogMessage ( JSON . stringify ( params ) ) ;
logger [ req . config . logLevels . cloudFunctionError ] (
` Failed running cloud function ${ functionName } for user ${ userString } with: \n Input: ${ cleanInput } \n Error: ` +
JSON . stringify ( error ) ,
{
functionName ,
error ,
params ,
user : userString ,
}
) ;
}
2018-09-01 13:58:06 -04:00
reject ( error ) ;
} catch ( e ) {
reject ( e ) ;
}
2018-08-06 17:39:38 -04:00
}
2018-09-01 13:58:06 -04:00
) ;
2025-12-14 15:24:51 +01:00
const { success , error } = responseObject ;
2018-09-01 13:58:06 -04:00
return Promise . resolve ( )
2020-10-26 04:36:54 +11:00
. then ( ( ) => {
2021-02-13 09:01:38 +11:00
return triggers . maybeRunValidator ( request , functionName , req . auth ) ;
2020-10-26 04:36:54 +11:00
} )
2018-09-01 13:58:06 -04:00
. then ( ( ) => {
2025-12-14 15:24:51 +01:00
// Check if function expects 2 parameters (req, res) - Express style
if ( theFunction . length >= 2 ) {
return theFunction ( request , responseObject ) ;
} else {
// Traditional style - single parameter
return theFunction ( request ) ;
}
2018-09-01 13:58:06 -04:00
} )
2025-12-14 15:24:51 +01:00
. then ( result => {
// For Express-style functions, only send response if not already sent
if ( theFunction . length >= 2 ) {
if ( ! responseObject . _isResponseSent ( ) ) {
// If Express-style function returns a value without calling res.success/error
if ( result !== undefined ) {
success ( result ) ;
}
// If no response sent and no value returned, this is an error in user code
// but we don't handle it here to maintain backward compatibility
}
} else {
// For traditional functions, always call success with the result (even if undefined)
success ( result ) ;
}
} , error ) ;
2018-08-06 17:39:38 -04:00
} ) ;
2016-02-19 23:47:44 -05:00
}
}