tenantpilot/worker/jobs/graphFetch.ts
Ahmed Darrazi 2eaf325770
All checks were successful
Trigger Cloudarix Deploy / call-webhook (push) Successful in 4s
Use Graph beta for beta-only endpoints
2025-12-09 21:56:38 +01:00

92 lines
2.6 KiB
TypeScript

import getGraphAccessToken from './graphAuth';
import { withRetry, isTransientError } from '../utils/retry';
type GraphRecord = Record<string, unknown>;
// Certain Intune resources exist only on the beta Graph surface.
const BETA_ENDPOINTS = new Set([
'/deviceManagement/configurationPolicies',
'/deviceManagement/intents',
]);
function baseUrlFor(endpoint: string) {
for (const beta of BETA_ENDPOINTS) {
if (endpoint.startsWith(beta)) return 'https://graph.microsoft.com/beta';
}
return 'https://graph.microsoft.com/v1.0';
}
/**
* Fetch a Graph endpoint with pagination support for @odata.nextLink
* Returns an array of items aggregated across pages.
*/
export async function fetchWithPagination(
endpoint: string,
token: string,
baseUrl = 'https://graph.microsoft.com/v1.0'
): Promise<GraphRecord[]> {
const results: GraphRecord[] = [];
// Normalize URL
let url = endpoint.startsWith('http') ? endpoint : `${baseUrl}${endpoint.startsWith('/') ? '' : '/'}${endpoint}`;
while (url) {
const res = await withRetry(
async () => {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/json',
},
});
// Handle rate limiting (429)
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 60000;
await new Promise((resolve) => setTimeout(resolve, delay));
throw new Error(`429 Rate limit exceeded, retrying after ${delay}ms`);
}
if (!response.ok) {
const txt = await response.text();
const error = new Error(`Graph fetch failed: ${response.status} ${response.statusText} - ${txt}`);
throw error;
}
return response;
},
{
maxAttempts: 3,
initialDelayMs: 1000,
shouldRetry: (error) => isTransientError(error),
}
);
const json = await res.json();
if (Array.isArray(json.value)) {
results.push(...json.value);
} else if (json.value !== undefined) {
// Some endpoints may return a single value
results.push(json.value as GraphRecord);
}
const next = json['@odata.nextLink'];
if (next) url = next;
else break;
}
return results;
}
/**
* Convenience function: obtains a Graph token and fetches pages for the given endpoint.
*/
export async function fetchFromGraph(endpoint: string) {
const token = await getGraphAccessToken();
const baseUrl = baseUrlFor(endpoint);
return fetchWithPagination(endpoint, token, baseUrl);
}
export default fetchFromGraph;