UNPKG

google-ads-api

Version:

Google Ads API Client Library for Node.js

212 lines (211 loc) 8.85 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Service = exports.FAILURE_KEY = void 0; const google_gax_1 = require("google-gax"); const google_auth_library_1 = require("google-auth-library"); const protos_1 = require("./protos"); const utils_1 = require("./utils"); const version_1 = require("./version"); const ttlcache_1 = __importDefault(require("@isaacs/ttlcache")); // Make sure to update this version number when upgrading exports.FAILURE_KEY = `google.ads.googleads.${version_1.googleAdsVersion}.errors.googleadsfailure-bin`; // A global service cache to avoid re-initialising services const serviceCache = new ttlcache_1.default({ max: 1_000, ttl: 10 * 60 * 1000, // 10 minutes dispose: async (service) => { // Close connections when services are removed from the cache await service.close(); }, }); // A global access token cache used by REST calls. Issued tokens expire after 1 hour, so we cache them for 50 minutes. const accessTokenCache = new ttlcache_1.default({ max: 100_000, ttl: 50 * 60 * 1000, // 50 minutes }); class Service { clientOptions; customerOptions; hooks; constructor(clientOptions, customerOptions, hooks) { this.clientOptions = clientOptions; this.customerOptions = customerOptions; this.hooks = hooks ?? {}; // @ts-expect-error All fields don't need to be set here this.serviceCache = {}; } get credentials() { return { customer_id: this.customerOptions.customer_id, login_customer_id: this.customerOptions.login_customer_id, linked_customer_id: this.customerOptions.linked_customer_id, }; } get 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; } // Used only by gRPC calls getCredentials() { const sslCreds = google_gax_1.grpc.credentials.createSsl(); const authClient = new google_auth_library_1.UserRefreshClient(this.clientOptions.client_id, this.clientOptions.client_secret, this.customerOptions.refresh_token); const credentials = google_gax_1.grpc.credentials.combineChannelCredentials(sslCreds, google_gax_1.grpc.credentials.createFromGoogleCredential(authClient)); return credentials; } // Used only by REST calls async getAccessToken() { const cachedToken = accessTokenCache.get(this.customerOptions.refresh_token); if (cachedToken) { return cachedToken; } const oAuth2Client = new google_auth_library_1.OAuth2Client(this.clientOptions.client_id, this.clientOptions.client_secret); oAuth2Client.setCredentials({ refresh_token: this.customerOptions.refresh_token, }); const { token } = await oAuth2Client.getAccessToken(); if (typeof token !== "string") { throw new Error("Failed to retrieve access token"); } accessTokenCache.set(this.customerOptions.refresh_token, token); return token; } loadService(service) { const serviceCacheKey = `${service}_${this.customerOptions.refresh_token}`; if (serviceCache.has(serviceCacheKey)) { return serviceCache.get(serviceCacheKey); } // eslint-disable-next-line @typescript-eslint/no-var-requires const { [service]: protoService } = require("google-ads-node"); if (typeof protoService === "undefined") { throw new Error(`Service "${service}" could not be found`); } // Initialising services can take a few ms, so we cache when possible. const client = new protoService({ sslCreds: this.getCredentials(), }); serviceCache.set(serviceCacheKey, client); return client; } getGoogleAdsError(error) { // @ts-expect-error No type exists for GA query error if (typeof error?.metadata?.internalRepr.get(exports.FAILURE_KEY) === "undefined") { return error; } // @ts-expect-error No type exists for GA query error const [buffer] = error.metadata.internalRepr.get(exports.FAILURE_KEY); return this.decodeGoogleAdsFailureBuffer(buffer); } decodeGoogleAdsFailureBuffer(buffer) { const googleAdsFailure = protos_1.errors.GoogleAdsFailure.decode(buffer); return googleAdsFailure; } decodePartialFailureError(response) { if (typeof response?.partial_failure_error === "undefined" || !response?.partial_failure_error) { return response; } const { details } = response.partial_failure_error; const buffer = details?.find((d) => d.type_url.includes("errors.GoogleAdsFailure"))?.value; if (typeof buffer === "undefined") { return response; } // Update the partial failure field with the decoded error details return { ...response, partial_failure_error: this.decodeGoogleAdsFailureBuffer(buffer), }; } buildSearchRequestAndService(gaql, options) { const service = this.loadService("GoogleAdsServiceClient"); const request = new protos_1.services.SearchGoogleAdsRequest({ customer_id: this.customerOptions.customer_id, query: gaql, ...options, }); return { service, request }; } buildSearchStreamRequestAndService(gaql, options) { const service = this.loadService("GoogleAdsServiceClient"); const request = new protos_1.services.SearchGoogleAdsStreamRequest({ customer_id: this.customerOptions.customer_id, query: gaql, ...options, }); return { service, request }; } buildMutationRequestAndService(mutations, options) { const service = this.loadService("GoogleAdsServiceClient"); const mutateOperations = mutations.map((mutation) => { const opKey = (0, utils_1.toSnakeCase)(`${mutation.entity}Operation`); const operation = { [mutation.operation ?? "create"]: mutation.resource, }; if (mutation.operation === "create" && //@ts-ignore mutation?.exempt_policy_violation_keys?.length) { //@ts-ignore operation.exempt_policy_violation_keys = mutation.exempt_policy_violation_keys; } else if (mutation.operation === "update") { // @ts-expect-error Resource operations should have updateMask defined operation.update_mask = (0, utils_1.getFieldMask)(mutation.resource); } const mutateOperation = new protos_1.services.MutateOperation({ [opKey]: operation, }); return mutateOperation; }); const request = new protos_1.services.MutateGoogleAdsRequest({ customer_id: this.customerOptions.customer_id, mutate_operations: mutateOperations, ...options, }); return { service, request }; } buildOperations(type, entities, message) { const ops = entities.map((e) => { const op = { [type]: e, operation: type, }; //@ts-ignore if (type === "create" && e?.exempt_policy_violation_keys?.length) { // @ts-expect-error Field required for policy violation exemptions op.exempt_policy_violation_keys = e.exempt_policy_violation_keys; //@ts-ignore delete e.exempt_policy_violation_keys; } else if (type === "update") { // @ts-expect-error Field required for updates op.update_mask = (0, utils_1.getFieldMask)( // @ts-expect-error Message types have a toObject method message.toObject(e, { defaults: false, })); } return op; }); return ops; } buildRequest(operations, options) { const request = { customer_id: this.customerOptions.customer_id, operations, ...options, }; return request; } } exports.Service = Service;