wbxcallprov/src/integrations/google.js
jmcqueen a05dbb2733 Route Google API calls through remote agent with insecure TLS
- validateAddress and getTimeZone now use proxyRequest instead of native
  fetch so the request originates from the on-prem IP, satisfying the
  API_KEY_IP_ADDRESS_BLOCKED restriction on the Google key
- Both calls carry insecure:true because the agent's network path runs
  SSL-inspecting proxies that substitute an internal-CA chain (throws
  SELF_SIGNED_CERT_IN_CHAIN otherwise); scoped per-request, consistent
  with SIW
- Fix latent bug in getTimeZone: use GET (per Google docs) instead of POST
- Rename README section to "Remote agent (SIW + Google)" and document
  the SSL-inspection nuance

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 15:06:58 -04:00

78 lines
2.9 KiB
JavaScript

import { config } from '../config.js';
import { proxyRequest } from '../services/websocket.js';
// All Google API calls are routed through the on-prem remote agent so the
// request originates from the whitelisted IP subnet. The API key is
// restricted by IP; calls direct from the cloud bot IP get 403 with
// API_KEY_IP_ADDRESS_BLOCKED. The remote agent runs inside the corporate
// network, so its egress IP matches the key's allow-list.
//
// `insecure: true` is set even though Google's public certs are trusted,
// because the corporate network the agent lives on intercepts outbound
// HTTPS with an SSL-inspecting proxy that presents an internal-CA chain.
// Without the flag Node throws SELF_SIGNED_CERT_IN_CHAIN on any outbound
// TLS. Trust is already delegated to that proxy by being on this network,
// so the scoped per-request bypass is consistent with SIW.
/**
* Google Address Validation API. Returns the parsed response body.
*/
export async function validateAddress(street, city, state, postalCode, country) {
const body = {
address: {
regionCode: country,
locality: city,
addressLines: [street],
administrativeArea: state,
postalCode,
},
};
const url = `https://addressvalidation.googleapis.com/v1:validateAddress?key=${encodeURIComponent(config.google.apiKey)}`;
const response = await proxyRequest({
method: 'POST',
url,
headers: { 'Content-Type': 'application/json' },
body,
insecure: true,
});
return response?.data;
}
/**
* Google Time Zone API for a given lat/lon at "now". Uses GET as documented
* by Google — parameters are entirely in the query string.
*/
export async function getTimeZone(latitude, longitude) {
const timestamp = Math.floor(Date.now() / 1000);
const url =
`https://maps.googleapis.com/maps/api/timezone/json?location=${latitude}%2C${longitude}` +
`&timestamp=${timestamp}&key=${encodeURIComponent(config.google.apiKey)}`;
const response = await proxyRequest({
method: 'GET',
url,
insecure: true,
});
return response?.data;
}
/**
* Reduce a Google addressComponents array to a flat "num street, city, state zip" string.
*/
export function formatE911Address(addressData) {
const parts = {};
for (const component of addressData.result.address.addressComponents ?? []) {
parts[component.componentType] = component.componentName?.text ?? '';
}
return `${parts.street_number ?? ''} ${parts.route ?? ''}, ${parts.locality ?? ''}, ${
parts.administrative_area_level_1 ?? ''
} ${parts.postal_code ?? ''}`.trim();
}
export function formatSuite(addressData) {
for (const component of addressData.result.address.addressComponents ?? []) {
if (component.componentType === 'subpremise') {
return component.componentName?.text ?? '';
}
}
return '';
}