tenantId}", now()->addMinutes(50), function () { $res = Http::asForm()->timeout(config('signature.graph.timeout')) ->post("https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/token", [ 'client_id' => config('signature.graph.client_id'), 'client_secret' => config('signature.graph.client_secret'), 'scope' => 'https://graph.microsoft.com/.default', 'grant_type' => 'client_credentials', ]); if (! $res->successful()) { throw new RuntimeException('Jeton Graph refusé : '.$res->json('error_description', $res->body())); } return $res->json('access_token'); }); } /** GET sur une URL relative (/users…) ou absolue (@odata.nextLink). */ public function get(string $url, array $headers = []): array { $full = str_starts_with($url, 'https://') ? $url : config('signature.graph.base_url').$url; $res = Http::withToken($this->token()) ->withHeaders($headers) ->timeout(config('signature.graph.timeout')) // Nouvel essai sur limitation (429) ou indisponibilité temporaire de Graph. ->retry(3, 2000, fn ($e) => $e instanceof RequestException && in_array($e->response->status(), [429, 503, 504], true), throw: false) ->get($full); if (! $res->successful()) { throw new RuntimeException("Graph {$res->status()} sur {$url} : ".$res->body()); } return $res->json(); } /** * Parcourt toutes les pages d'une collection. * Retourne [éléments, deltaLink|null]. */ public function all(string $url, array $headers = []): array { $items = []; $delta = null; $next = $url; while ($next) { $page = $this->get($next, $headers); array_push($items, ...($page['value'] ?? [])); $next = $page['@odata.nextLink'] ?? null; $delta = $page['@odata.deltaLink'] ?? $delta; } return [$items, $delta]; } }