import { grpc } from "google-gax" ; import { UserRefreshClient } from "google-auth-library" ; import { ClientOptions } from "./client" ; import { AllServices , errors , GoogleAdsServiceClient , ServiceName , services , } from "./protos" ; import { CustomerOptions , CustomerCredentials , RequestOptions , MutateOperation , MutateOptions , } from "./types" ; import { getFieldMask , toSnakeCase } from "./utils" ; import { googleAdsVersion } from "./version" ; import { Hooks } from "./hooks" ; export const FAILURE_KEY = `google.ads.googleads.${ googleAdsVersion }.errors.googleadsfailure-bin` ; export interface CallHeaders { "developer-token" : string ; "login-customer-id" ? : string ; "linked-customer-id" ? : string ; } export class Service { protected readonly clientOptions : ClientOptions ; protected readonly customerOptions : CustomerOptions ; protected readonly hooks : Hooks ; private serviceCache ! : Record < ServiceName , AllServices > ; constructor ( clientOptions , customerOptions , hooks ? ) { this . clientOptions = clientOptions ; this . customerOptions = customerOptions ; this . hooks = hooks ?? { } ; this . serviceCache = { } ; } public get credentials ( ) : CustomerCredentials { return { customer_id : this . customerOptions . customer_id , login_customer_id : this . customerOptions . login_customer_id , linked_customer_id : this . customerOptions . linked_customer_id , } ; } protected get callHeaders ( ) : CallHeaders { const headers = { "developer-token" : this . clientOptions . developer_token , } ; if ( this . customerOptions . login_customer_id ) { headers [ "login-customer-id" ] = this . customerOptions . login_customer_id ; } if ( this . customerOptions . linked_customer_id ) { headers [ "linked-customer-id" ] = this . customerOptions . linked_customer_id ; } return headers ; } private getCredentials ( ) { const sslCreds = grpc . credentials . createSsl ( ) ; const authClient = new UserRefreshClient ( this . clientOptions . client_id , this . clientOptions . client_secret , this . customerOptions . refresh_token ) ; const credentials = grpc . credentials . combineChannelCredentials ( sslCreds , grpc . credentials . createFromGoogleCredential ( authClient ) ) ; return credentials ; } protected loadService < T = AllServices > ( service ) { if ( this . serviceCache [ service ] ) { return this . serviceCache [ service ] as unknown as T ; } const { [ service ] : protoService } = require ( "google-ads-node" ) ; if ( typeof protoService === "undefined" ) { throw new Error ( `Service "${ service }" could not be found` ) ; } const client = new protoService ( { sslCreds : this . getCredentials ( ) , } ) ; this . serviceCache [ service ] = client ; return client as unknown as T ; } protected getGoogleAdsError ( error ) { if ( typeof error ?. metadata ?. internalRepr . get ( FAILURE_KEY ) === "undefined" ) { return error ; } const [ buffer ] = error . metadata . internalRepr . get ( FAILURE_KEY ) ; return this . decodeGoogleAdsFailureBuffer ( buffer ) ; } private decodeGoogleAdsFailureBuffer ( buffer ) { const googleAdsFailure = errors . GoogleAdsFailure . decode ( buffer ) ; return googleAdsFailure ; } protected decodePartialFailureError < T > ( response ) { if ( typeof ( response as any ) ?. partial_failure_error === "undefined" || ! ( response as any ) ?. partial_failure_error ) { return response ; } const { details } = ( response as any ) . partial_failure_error ; const buffer = details ?. find ( ( d ) => d . type_url . includes ( "errors.GoogleAdsFailure" ) ) ?. value ; if ( typeof buffer === "undefined" ) { return response ; } return { ... response , partial_failure_error : this . decodeGoogleAdsFailureBuffer ( buffer ) , } ; } protected buildSearchRequestAndService ( gaql , options ? ) { const service = this . loadService ( "GoogleAdsServiceClient" ) ; const request = new services . SearchGoogleAdsRequest ( { customer_id : this . customerOptions . customer_id , query : gaql , ... options , } ) ; return { service , request } ; } protected buildSearchStreamRequestAndService ( gaql , options ? ) { const service = this . loadService ( "GoogleAdsServiceClient" ) ; const request = new services . SearchGoogleAdsStreamRequest ( { customer_id : this . customerOptions . customer_id , query : gaql , ... options , } ) ; return { service , request } ; } protected buildMutationRequestAndService < T > ( mutations , options ? ) { const service = this . loadService ( "GoogleAdsServiceClient" ) ; const mutateOperations = mutations . map ( ( mutation ) : services . MutateOperation => { const opKey = toSnakeCase ( `${ mutation . entity }Operation` ) ; const operation = { [ mutation . operation ?? "create" ] : mutation . resource , } ; if ( mutation . operation === "update" ) { operation . update_mask = getFieldMask ( mutation . resource ) ; } const mutateOperation = new services . MutateOperation ( { [ opKey ] : operation , } ) ; return mutateOperation ; } ) ; const request = new services . MutateGoogleAdsRequest ( { customer_id : this . customerOptions . customer_id , mutate_operations : mutateOperations , ... options , } ) ; return { service , request } ; } protected buildOperations < Op , Ent > ( type , entities , message ? ) { const ops = entities . map ( ( e ) => { const op = { [ type ] : e , operation : type , } ; if ( type === "update" ) { op . update_mask = getFieldMask ( message . toObject ( e , { defaults : false , } ) ) ; } return op ; } ) ; return ops as unknown as Op [ ] ; } protected buildRequest < Op , Req , Options > ( operations , options ? ) { const request = { customer_id : this . customerOptions . customer_id , operations , ... options , } ; return request as unknown as Req ; } }