@opengeni/api-router 0.16.5 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,28 +3,37 @@ import type { Settings } from "@opengeni/config";
3
3
  import {
4
4
  GOOGLE_DRIVE_CREDENTIAL_LABEL,
5
5
  GOOGLE_DRIVE_CREDENTIAL_ROLE,
6
- GOOGLE_DRIVE_METADATA_READONLY_SCOPE,
7
6
  GOOGLE_DRIVE_PROVIDER_DOMAIN,
8
7
  GOOGLE_DRIVE_READONLY_SCOPE,
9
8
  GoogleDriveBrowseItem,
10
9
  GoogleDriveBrowseResponse,
10
+ GoogleDriveConnectionLifecycle,
11
11
  GoogleDriveConnectionMetadata,
12
12
  GoogleDriveOAuthStartResponse,
13
13
  SaveGoogleDriveSourceRequest,
14
+ googleDriveOAuthScopeDecision,
15
+ googleDriveScopesAllowCapability,
16
+ type GoogleDriveDisconnectRequest,
17
+ type GoogleDriveLifecycleActionRequest,
14
18
  type GoogleDriveOAuthStartRequest,
15
19
  } from "@opengeni/contracts/google-drive";
16
20
  import { hasPermission, requireEnvironmentEncryption } from "@opengeni/core";
17
21
  import type { ApiRouteDeps } from "@opengeni/core";
18
22
  import {
19
23
  buildConnectionTokenResolver,
24
+ ConnectionDisconnectGenerationError,
25
+ ConnectionDisconnectIdempotencyError,
20
26
  consumeIntegrationOAuthStateNonce,
21
27
  createConnection,
22
28
  decryptEnvironmentValue,
29
+ disconnectConnectionIdempotently,
23
30
  encryptEnvironmentValue,
24
31
  getConnectionMetadata,
25
32
  getWorkspaceGrant,
26
33
  loadConnectionCredentialForBroker,
34
+ transitionConnectionState,
27
35
  updateConnection,
36
+ type PermanentConnectionRefreshFailure,
28
37
  } from "@opengeni/db";
29
38
  import { createSignedState, readSignedState } from "@opengeni/github";
30
39
  import { readResponseJsonBounded, type FetchLike } from "@opengeni/network";
@@ -43,6 +52,12 @@ const GOOGLE_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
43
52
  const GOOGLE_REQUEST_TIMEOUT_MS = 10_000;
44
53
  const GOOGLE_DRIVE_PAGE_SIZE = 100;
45
54
  const GOOGLE_DRIVE_RETURN_PATH = (workspaceId: string) => `/workspaces/${workspaceId}/capabilities`;
55
+ const GOOGLE_DRIVE_RECONSENT_ERROR_CODES = new Set([
56
+ "appNotAuthorizedToFile",
57
+ "authError",
58
+ "insufficientFilePermissions",
59
+ "insufficientPermissions",
60
+ ]);
46
61
 
47
62
  type GoogleDriveOAuthState = {
48
63
  accountId: string;
@@ -64,6 +79,8 @@ type GoogleTokenResponse = {
64
79
  scopes: string[];
65
80
  };
66
81
 
82
+ type GoogleDriveConnectionRecord = NonNullable<Awaited<ReturnType<typeof getConnectionMetadata>>>;
83
+
67
84
  export async function startGoogleDriveOAuth(
68
85
  deps: ApiRouteDeps,
69
86
  input: {
@@ -170,7 +187,11 @@ export async function completeGoogleDriveOAuthCallback(
170
187
  },
171
188
  fetchImpl,
172
189
  );
173
- if (!token.scopes.includes(GOOGLE_DRIVE_READONLY_SCOPE)) {
190
+ const scopeDecision = googleDriveOAuthScopeDecision(token.scopes);
191
+ if (
192
+ scopeDecision.accessMode !== "readonly" ||
193
+ !scopeDecision.capabilities.includes("recursive_source_sync")
194
+ ) {
174
195
  throw new GoogleDriveCallbackError("scope_not_granted");
175
196
  }
176
197
  const identity = await verifyGoogleDriveIdentity(token.accessToken, fetchImpl);
@@ -230,7 +251,8 @@ export async function completeGoogleDriveOAuthCallback(
230
251
  googleEmail: identity.emailAddress,
231
252
  googleDisplayName: identity.displayName,
232
253
  verifiedAt: new Date().toISOString(),
233
- accessMode: "readonly",
254
+ accessMode: scopeDecision.accessMode,
255
+ lifecycle: googleDriveLifecycle("active"),
234
256
  ...(previousMetadata?.selectedSources
235
257
  ? { selectedSources: previousMetadata.selectedSources }
236
258
  : previousMetadata?.selectedSource
@@ -283,6 +305,123 @@ export async function completeGoogleDriveOAuthCallback(
283
305
  }
284
306
  }
285
307
 
308
+ export async function transitionGoogleDriveLifecycle(
309
+ deps: ApiRouteDeps,
310
+ input: {
311
+ workspaceId: string;
312
+ subjectId: string;
313
+ connectionId: string;
314
+ payload: GoogleDriveLifecycleActionRequest;
315
+ },
316
+ ) {
317
+ const existing = await getConnectionMetadata(
318
+ deps.db,
319
+ input.workspaceId,
320
+ input.connectionId,
321
+ input.subjectId,
322
+ );
323
+ if (!existing) {
324
+ throw new HTTPException(404, { message: "Google Drive connection not found" });
325
+ }
326
+ const metadata = requireGoogleDriveConnection(existing, input.subjectId);
327
+ const lifecycle = effectiveGoogleDriveLifecycle(existing, metadata);
328
+ const targetState = input.payload.action === "pause" ? "paused" : "active";
329
+
330
+ // Natural convergence makes retried pause/resume requests idempotent even if
331
+ // the caller still carries the pre-transition version.
332
+ if (existing.status === "active" && lifecycle.state === targetState) {
333
+ return existing;
334
+ }
335
+ if (existing.status === "revoked") {
336
+ throw new HTTPException(409, {
337
+ message: "Google Drive is disconnected; connect it again instead",
338
+ });
339
+ }
340
+ if (input.payload.action === "pause" && lifecycle.state !== "active") {
341
+ throw new HTTPException(409, {
342
+ message: "Google Drive must be reconnected before it can be paused",
343
+ });
344
+ }
345
+ if (input.payload.action === "resume" && lifecycle.state !== "paused") {
346
+ throw new HTTPException(409, {
347
+ message: "Google Drive must be reconnected or re-consented before it can resume",
348
+ });
349
+ }
350
+ if (existing.status !== "active" || existing.version !== input.payload.expectedVersion) {
351
+ throw new HTTPException(409, { message: "Google Drive connection changed; try again" });
352
+ }
353
+
354
+ const updated = await transitionConnectionState(deps.db, {
355
+ workspaceId: input.workspaceId,
356
+ connectionId: existing.id,
357
+ visibleToSubjectId: input.subjectId,
358
+ expectedVersion: existing.version,
359
+ status: "active",
360
+ metadata: GoogleDriveConnectionMetadata.parse({
361
+ ...metadata,
362
+ lifecycle: googleDriveLifecycle(targetState),
363
+ }),
364
+ lastError: null,
365
+ updatedBySubjectId: input.subjectId,
366
+ });
367
+ if (!updated) {
368
+ const converged = await getConnectionMetadata(
369
+ deps.db,
370
+ input.workspaceId,
371
+ input.connectionId,
372
+ input.subjectId,
373
+ );
374
+ if (converged?.status === "active") {
375
+ const convergedMetadata = requireGoogleDriveConnection(converged, input.subjectId);
376
+ if (effectiveGoogleDriveLifecycle(converged, convergedMetadata).state === targetState) {
377
+ return converged;
378
+ }
379
+ }
380
+ throw new HTTPException(409, { message: "Google Drive connection changed; try again" });
381
+ }
382
+ return updated;
383
+ }
384
+
385
+ export async function disconnectGoogleDrive(
386
+ deps: ApiRouteDeps,
387
+ input: {
388
+ workspaceId: string;
389
+ subjectId: string;
390
+ connection: GoogleDriveConnectionRecord;
391
+ payload: GoogleDriveDisconnectRequest;
392
+ },
393
+ ) {
394
+ const metadata = requireGoogleDriveConnection(input.connection, input.subjectId);
395
+ try {
396
+ return await disconnectConnectionIdempotently(deps.db, {
397
+ accountId: input.connection.accountId,
398
+ workspaceId: input.workspaceId,
399
+ subjectId: input.subjectId,
400
+ connectionId: input.connection.id,
401
+ expectedVersion: input.payload.expectedVersion,
402
+ idempotencyKey: input.payload.idempotencyKey,
403
+ metadata: GoogleDriveConnectionMetadata.parse({
404
+ ...metadata,
405
+ lifecycle: googleDriveLifecycle("disconnected"),
406
+ }),
407
+ lastError: null,
408
+ updatedBySubjectId: input.subjectId,
409
+ });
410
+ } catch (error) {
411
+ if (error instanceof ConnectionDisconnectIdempotencyError) {
412
+ throw new HTTPException(409, {
413
+ message: "Google Drive disconnect key was already used for another operation",
414
+ });
415
+ }
416
+ if (error instanceof ConnectionDisconnectGenerationError) {
417
+ throw new HTTPException(409, {
418
+ message: "Google Drive connection changed; refresh before disconnecting",
419
+ });
420
+ }
421
+ throw error;
422
+ }
423
+ }
424
+
286
425
  export async function browseGoogleDrive(
287
426
  deps: ApiRouteDeps,
288
427
  input: {
@@ -302,7 +441,7 @@ export async function browseGoogleDrive(
302
441
  if (!connection) {
303
442
  throw new HTTPException(404, { message: "Google Drive connection not found" });
304
443
  }
305
- requireGoogleDriveConnection(connection, input.subjectId);
444
+ await requireGoogleDriveSourceConnection(deps, connection, input.subjectId);
306
445
  const parentId = validDriveId(input.parentId, "parentId");
307
446
  const currentItem = await resolveGoogleDriveBoundaryItem(deps, {
308
447
  workspaceId: input.workspaceId,
@@ -377,7 +516,7 @@ export async function saveGoogleDriveSource(
377
516
  if (!existing) {
378
517
  throw new HTTPException(404, { message: "Google Drive connection not found" });
379
518
  }
380
- requireGoogleDriveConnection(existing, input.subjectId);
519
+ await requireGoogleDriveSourceConnection(deps, existing, input.subjectId);
381
520
  const verifiedSources = [];
382
521
  for (const source of payload.sources) {
383
522
  const sourceId = validDriveId(source.id, "source.id");
@@ -405,8 +544,8 @@ export async function saveGoogleDriveSource(
405
544
  input.connectionId,
406
545
  input.subjectId,
407
546
  )) ?? existing;
408
- const latestMetadata = requireGoogleDriveConnection(latest, input.subjectId);
409
- const updated = await updateConnection(deps.db, {
547
+ const latestMetadata = await requireGoogleDriveSourceConnection(deps, latest, input.subjectId);
548
+ const updated = await transitionConnectionState(deps.db, {
410
549
  workspaceId: input.workspaceId,
411
550
  connectionId: latest.id,
412
551
  visibleToSubjectId: input.subjectId,
@@ -527,16 +666,7 @@ function requireGoogleDriveSettings(settings: Settings): {
527
666
  return { clientId, clientSecret };
528
667
  }
529
668
 
530
- function requireGoogleDriveConnection(
531
- connection: {
532
- subjectId: string | null;
533
- providerDomain: string;
534
- kind: string;
535
- grantedScopes: string[];
536
- metadata: Record<string, unknown>;
537
- },
538
- subjectId: string,
539
- ) {
669
+ function requireGoogleDriveConnection(connection: GoogleDriveConnectionRecord, subjectId: string) {
540
670
  const parsed = GoogleDriveConnectionMetadata.safeParse(connection.metadata);
541
671
  if (
542
672
  connection.subjectId !== subjectId ||
@@ -546,15 +676,94 @@ function requireGoogleDriveConnection(
546
676
  ) {
547
677
  throw new HTTPException(422, { message: "connection is not this user's Google Drive" });
548
678
  }
679
+ return parsed.data;
680
+ }
681
+
682
+ function googleDriveLifecycle(
683
+ state: GoogleDriveConnectionLifecycle["state"],
684
+ ): GoogleDriveConnectionLifecycle {
685
+ return GoogleDriveConnectionLifecycle.parse({
686
+ state,
687
+ recoverable: state !== "app_removed",
688
+ observedAt: new Date().toISOString(),
689
+ });
690
+ }
691
+
692
+ function effectiveGoogleDriveLifecycle(
693
+ connection: GoogleDriveConnectionRecord,
694
+ metadata: ReturnType<typeof requireGoogleDriveConnection>,
695
+ ): GoogleDriveConnectionLifecycle {
696
+ if (metadata.lifecycle) return metadata.lifecycle;
697
+ if (connection.status === "revoked") return googleDriveLifecycle("disconnected");
698
+ if (connection.status === "active") return googleDriveLifecycle("active");
699
+ return googleDriveLifecycle("reconnect_required");
700
+ }
701
+
702
+ async function transitionGoogleDriveConnectionLifecycle(
703
+ deps: ApiRouteDeps,
704
+ connection: GoogleDriveConnectionRecord,
705
+ subjectId: string,
706
+ lifecycle: GoogleDriveConnectionLifecycle,
707
+ status: "active" | "needs_reauth" | "error",
708
+ lastError: string | null,
709
+ ) {
710
+ const metadata = requireGoogleDriveConnection(connection, subjectId);
549
711
  if (
550
- !connection.grantedScopes.includes(GOOGLE_DRIVE_READONLY_SCOPE) &&
551
- !connection.grantedScopes.includes(GOOGLE_DRIVE_METADATA_READONLY_SCOPE)
712
+ connection.status === status &&
713
+ metadata.lifecycle?.state === lifecycle.state &&
714
+ metadata.lifecycle.recoverable === lifecycle.recoverable
552
715
  ) {
716
+ return connection;
717
+ }
718
+ return await transitionConnectionState(deps.db, {
719
+ workspaceId: connection.workspaceId,
720
+ connectionId: connection.id,
721
+ visibleToSubjectId: subjectId,
722
+ expectedVersion: connection.version,
723
+ status,
724
+ metadata: GoogleDriveConnectionMetadata.parse({ ...metadata, lifecycle }),
725
+ lastError,
726
+ updatedBySubjectId: subjectId,
727
+ });
728
+ }
729
+
730
+ async function requireGoogleDriveSourceConnection(
731
+ deps: ApiRouteDeps,
732
+ connection: GoogleDriveConnectionRecord,
733
+ subjectId: string,
734
+ ) {
735
+ const metadata = requireGoogleDriveConnection(connection, subjectId);
736
+ const lifecycle = effectiveGoogleDriveLifecycle(connection, metadata);
737
+ if (connection.status === "revoked") {
738
+ throw new HTTPException(409, { message: "Google Drive is disconnected" });
739
+ }
740
+ if (lifecycle.state === "paused") {
741
+ throw new HTTPException(409, { message: "Google Drive is paused" });
742
+ }
743
+ if (connection.status !== "active" || lifecycle.state !== "active") {
553
744
  throw new HTTPException(401, {
554
- message: "Google Drive needs to be reconnected with metadata access",
745
+ message:
746
+ lifecycle.state === "reconsent_required"
747
+ ? "Google Drive needs permission re-consent"
748
+ : lifecycle.state === "app_removed"
749
+ ? "Google Drive app access is unavailable"
750
+ : "Google Drive needs to be reconnected",
555
751
  });
556
752
  }
557
- return parsed.data;
753
+ if (!googleDriveScopesAllowCapability(connection.grantedScopes, "recursive_source_sync")) {
754
+ await transitionGoogleDriveConnectionLifecycle(
755
+ deps,
756
+ connection,
757
+ subjectId,
758
+ googleDriveLifecycle("reconsent_required"),
759
+ "needs_reauth",
760
+ "google_drive_reconsent_required",
761
+ );
762
+ throw new HTTPException(401, {
763
+ message: "Google Drive needs permission re-consent for selected-source read access",
764
+ });
765
+ }
766
+ return metadata;
558
767
  }
559
768
 
560
769
  function readGoogleDriveOAuthState(
@@ -695,6 +904,99 @@ async function verifyGoogleDriveIdentity(accessToken: string, fetchImpl: FetchLi
695
904
  };
696
905
  }
697
906
 
907
+ function googleDriveRefreshFailureLifecycle(failure: PermanentConnectionRefreshFailure): {
908
+ lifecycle: GoogleDriveConnectionLifecycle;
909
+ status: "needs_reauth" | "error";
910
+ lastError: string;
911
+ } {
912
+ const code = failure.oauthErrorCode?.toLowerCase() ?? null;
913
+ if (code === "invalid_client" || code === "unauthorized_client") {
914
+ return {
915
+ lifecycle: googleDriveLifecycle("app_removed"),
916
+ status: "error",
917
+ lastError: "google_drive_app_removed",
918
+ };
919
+ }
920
+ if (code === "invalid_scope" || code === "insufficient_scope") {
921
+ return {
922
+ lifecycle: googleDriveLifecycle("reconsent_required"),
923
+ status: "needs_reauth",
924
+ lastError: "google_drive_reconsent_required",
925
+ };
926
+ }
927
+ if (code === "invalid_grant") {
928
+ return {
929
+ lifecycle: googleDriveLifecycle("token_revoked"),
930
+ status: "needs_reauth",
931
+ lastError: "google_drive_token_revoked",
932
+ };
933
+ }
934
+ return {
935
+ lifecycle: googleDriveLifecycle("reconnect_required"),
936
+ status: "needs_reauth",
937
+ lastError: "google_drive_reconnect_required",
938
+ };
939
+ }
940
+
941
+ async function transitionGoogleDrivePermanentRefreshFailure(
942
+ deps: ApiRouteDeps,
943
+ failure: PermanentConnectionRefreshFailure,
944
+ ): Promise<boolean> {
945
+ if (failure.providerDomain !== GOOGLE_DRIVE_PROVIDER_DOMAIN || !failure.subjectId) {
946
+ return false;
947
+ }
948
+ const connection = await getConnectionMetadata(
949
+ deps.db,
950
+ failure.workspaceId,
951
+ failure.connectionId,
952
+ failure.subjectId,
953
+ );
954
+ if (!connection || connection.version !== failure.connectionVersion) {
955
+ return true;
956
+ }
957
+ const transition = googleDriveRefreshFailureLifecycle(failure);
958
+ await transitionGoogleDriveConnectionLifecycle(
959
+ deps,
960
+ connection,
961
+ failure.subjectId,
962
+ transition.lifecycle,
963
+ transition.status,
964
+ transition.lastError,
965
+ );
966
+ return true;
967
+ }
968
+
969
+ async function transitionGoogleDriveProviderResponseFailure(
970
+ deps: ApiRouteDeps,
971
+ input: {
972
+ workspaceId: string;
973
+ subjectId: string;
974
+ connectionId: string;
975
+ connectionVersion: number;
976
+ lifecycle: GoogleDriveConnectionLifecycle;
977
+ status: "needs_reauth" | "error";
978
+ lastError: string;
979
+ },
980
+ ): Promise<void> {
981
+ const latest = await getConnectionMetadata(
982
+ deps.db,
983
+ input.workspaceId,
984
+ input.connectionId,
985
+ input.subjectId,
986
+ );
987
+ if (!latest || latest.version !== input.connectionVersion || latest.status !== "active") {
988
+ return;
989
+ }
990
+ await transitionGoogleDriveConnectionLifecycle(
991
+ deps,
992
+ latest,
993
+ input.subjectId,
994
+ input.lifecycle,
995
+ input.status,
996
+ input.lastError,
997
+ );
998
+ }
999
+
698
1000
  async function googleDriveApiRequest(
699
1001
  deps: ApiRouteDeps,
700
1002
  input: {
@@ -705,7 +1007,21 @@ async function googleDriveApiRequest(
705
1007
  label: string;
706
1008
  },
707
1009
  ): Promise<unknown> {
708
- const resolver = buildConnectionTokenResolver(deps.db, deps.settings);
1010
+ const current = await getConnectionMetadata(
1011
+ deps.db,
1012
+ input.workspaceId,
1013
+ input.connectionId,
1014
+ input.subjectId,
1015
+ );
1016
+ if (!current) {
1017
+ throw new HTTPException(404, { message: "Google Drive connection not found" });
1018
+ }
1019
+ await requireGoogleDriveSourceConnection(deps, current, input.subjectId);
1020
+ const resolver = buildConnectionTokenResolver(deps.db, deps.settings, undefined, {
1021
+ ...(deps.googleDriveFetch ? { refreshTransport: { fetchImpl: deps.googleDriveFetch } } : {}),
1022
+ transitionPermanentRefreshFailure: async (failure) =>
1023
+ await transitionGoogleDrivePermanentRefreshFailure(deps, failure),
1024
+ });
709
1025
  const resolve = async (forceRefresh: boolean) =>
710
1026
  await resolver({
711
1027
  workspaceId: input.workspaceId,
@@ -725,6 +1041,10 @@ async function googleDriveApiRequest(
725
1041
  if (credential.status !== "ok") {
726
1042
  throw new HTTPException(401, { message: "Google Drive needs to be reconnected" });
727
1043
  }
1044
+ let providerConnectionVersion = credential.connectionVersion;
1045
+ if (providerConnectionVersion === undefined) {
1046
+ throw new Error("Google Drive credential resolver omitted the connection version");
1047
+ }
728
1048
  const fetchImpl = deps.googleDriveFetch ?? fetch;
729
1049
  let response = await providerFetch(fetchImpl, input.url, {
730
1050
  headers: { ...credential.headers, accept: "application/json" },
@@ -735,16 +1055,52 @@ async function googleDriveApiRequest(
735
1055
  if (credential.status !== "ok") {
736
1056
  throw new HTTPException(401, { message: "Google Drive needs to be reconnected" });
737
1057
  }
1058
+ providerConnectionVersion = credential.connectionVersion;
1059
+ if (providerConnectionVersion === undefined) {
1060
+ throw new Error("Google Drive credential resolver omitted the connection version");
1061
+ }
738
1062
  response = await providerFetch(fetchImpl, input.url, {
739
1063
  headers: { ...credential.headers, accept: "application/json" },
740
1064
  });
741
1065
  }
742
1066
  if (!response.ok) {
743
- await response.body?.cancel().catch(() => undefined);
1067
+ if (response.status === 401) {
1068
+ await response.body?.cancel().catch(() => undefined);
1069
+ await transitionGoogleDriveProviderResponseFailure(deps, {
1070
+ workspaceId: input.workspaceId,
1071
+ subjectId: input.subjectId,
1072
+ connectionId: input.connectionId,
1073
+ connectionVersion: providerConnectionVersion,
1074
+ lifecycle: googleDriveLifecycle("reconnect_required"),
1075
+ status: "needs_reauth",
1076
+ lastError: "google_drive_reconnect_required",
1077
+ });
1078
+ throw new HTTPException(401, { message: "Google Drive needs to be reconnected" });
1079
+ }
1080
+ const providerErrorCode =
1081
+ response.status === 403 ? await readGoogleDriveProviderErrorCode(response) : null;
1082
+ if (response.status !== 403) {
1083
+ await response.body?.cancel().catch(() => undefined);
1084
+ }
1085
+ if (
1086
+ response.status === 403 &&
1087
+ providerErrorCode &&
1088
+ GOOGLE_DRIVE_RECONSENT_ERROR_CODES.has(providerErrorCode)
1089
+ ) {
1090
+ await transitionGoogleDriveProviderResponseFailure(deps, {
1091
+ workspaceId: input.workspaceId,
1092
+ subjectId: input.subjectId,
1093
+ connectionId: input.connectionId,
1094
+ connectionVersion: providerConnectionVersion,
1095
+ lifecycle: googleDriveLifecycle("reconsent_required"),
1096
+ status: "needs_reauth",
1097
+ lastError: "google_drive_reconsent_required",
1098
+ });
1099
+ }
744
1100
  throw new HTTPException(response.status === 403 ? 403 : 502, {
745
1101
  message:
746
1102
  response.status === 403
747
- ? "Google Drive denied metadata access; reconnect and approve the requested scope"
1103
+ ? "Google Drive denied metadata access; re-consent may be required"
748
1104
  : "Google Drive metadata request failed",
749
1105
  });
750
1106
  }
@@ -767,6 +1123,25 @@ async function providerFetch(
767
1123
  }
768
1124
  }
769
1125
 
1126
+ async function readGoogleDriveProviderErrorCode(response: Response): Promise<string | null> {
1127
+ try {
1128
+ const payload = objectRecord(
1129
+ await readResponseJsonBounded<unknown>(
1130
+ response,
1131
+ GOOGLE_RESPONSE_MAX_BYTES,
1132
+ "Google Drive error response",
1133
+ ),
1134
+ );
1135
+ const error = objectRecord(payload.error);
1136
+ const first = Array.isArray(error.errors) ? objectRecord(error.errors[0]) : {};
1137
+ const code = optionalString(first.reason) ?? optionalString(error.status);
1138
+ return code && /^[A-Za-z0-9_.-]{1,64}$/.test(code) ? code : null;
1139
+ } catch {
1140
+ await response.body?.cancel().catch(() => undefined);
1141
+ return null;
1142
+ }
1143
+ }
1144
+
770
1145
  function parseDriveItem(value: unknown): GoogleDriveBrowseItem | null {
771
1146
  const item = objectRecord(value);
772
1147
  const id = optionalString(item.id);