- 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>
78 lines
2.9 KiB
JavaScript
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}` +
|
|
`×tamp=${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 '';
|
|
}
|